From 58c05285875a01412d2c6a570d861cf18eb2c976 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Thu, 26 Sep 2024 16:38:33 +0200 Subject: [PATCH 01/30] new node for RaiseStatement + NodeBuilder code --- .../aisec/cpg/graph/StatementBuilder.kt | 15 +++++ .../cpg/graph/statements/RaiseStatement.kt | 55 +++++++++++++++++++ .../cpg/frontends/python/StatementHandler.kt | 16 +++++- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt index c4157ff0130..6c7cabe65e3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt @@ -329,3 +329,18 @@ fun MetadataProvider.newDefaultStatement(rawNode: Any? = null): DefaultStatement log(node) return node } + +/** + * Creates a new [RaiseStatement]. The [MetadataProvider] receiver will be used to fill different + * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires + * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended + * argument. + */ +@JvmOverloads +fun MetadataProvider.newRaiseStatement(rawNode: Any? = null): RaiseStatement { + val node = RaiseStatement() + node.applyMetadata(this, EMPTY_NAME, rawNode, true) + + log(node) + return node +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt new file mode 100644 index 00000000000..939af4bdeb5 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.graph.statements + +import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf +import de.fraunhofer.aisec.cpg.graph.edges.unwrapping +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import java.util.Objects +import org.neo4j.ogm.annotation.Relationship + +/** Represents a `throw` or `raise` statement. */ +class RaiseStatement : Statement() { + + /** The exception object to be raised. */ + @Relationship(value = "EXCEPTION") var exceptionEdge = astOptionalEdgeOf() + var exception by unwrapping(RaiseStatement::exceptionEdge) + + /** + * Some languages (Python) can add a cause to indicate that an exception was raised while + * handling another exception. + */ + @Relationship(value = "CAUSE") var causeEdge = astOptionalEdgeOf() + var cause by unwrapping(RaiseStatement::causeEdge) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is RaiseStatement) return false + return super.equals(other) && exception == other.exception && cause == other.cause + } + + override fun hashCode() = Objects.hash(super.hashCode(), exception, cause) +} 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 d08f253aafa..38da3ad3c48 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 @@ -35,6 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.Annotation import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.AssertStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement +import de.fraunhofer.aisec.cpg.graph.statements.RaiseStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block @@ -70,10 +71,10 @@ class StatementHandler(frontend: PythonLanguageFrontend) : is Python.AST.Continue -> newContinueStatement(rawNode = node) is Python.AST.Assert -> handleAssert(node) is Python.AST.Delete -> handleDelete(node) + is Python.AST.Raise -> handleRaise(node) is Python.AST.Global, is Python.AST.Match, is Python.AST.Nonlocal, - is Python.AST.Raise, is Python.AST.Try, is Python.AST.TryStar, is Python.AST.With, @@ -85,6 +86,19 @@ class StatementHandler(frontend: PythonLanguageFrontend) : } } + /** + * Translates a Python [`Raise`](https://docs.python.org/3/library/ast.html#ast.Raise) into a + * [RaiseStatement]. + */ + private fun handleRaise(raise: Python.AST.Raise): RaiseStatement { + val ret = newRaiseStatement(rawNode = raise) + + raise.exc?.let { ret.exception = frontend.expressionHandler.handle(it) } + raise.cause?.let { ret.cause = frontend.expressionHandler.handle(it) } + + return ret + } + /** * Translates a Python [`Delete`](https://docs.python.org/3/library/ast.html#ast.Delete) into a * [DeleteExpression]. From 4523b73bf7c805e4ae940bd8ab8cd604ca7982f2 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Thu, 26 Sep 2024 16:40:26 +0200 Subject: [PATCH 02/30] don't add python stuff in this branch --- .../cpg/frontends/python/StatementHandler.kt | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) 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 38da3ad3c48..d08f253aafa 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 @@ -35,7 +35,6 @@ import de.fraunhofer.aisec.cpg.graph.Annotation import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.AssertStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement -import de.fraunhofer.aisec.cpg.graph.statements.RaiseStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block @@ -71,10 +70,10 @@ class StatementHandler(frontend: PythonLanguageFrontend) : is Python.AST.Continue -> newContinueStatement(rawNode = node) is Python.AST.Assert -> handleAssert(node) is Python.AST.Delete -> handleDelete(node) - is Python.AST.Raise -> handleRaise(node) is Python.AST.Global, is Python.AST.Match, is Python.AST.Nonlocal, + is Python.AST.Raise, is Python.AST.Try, is Python.AST.TryStar, is Python.AST.With, @@ -86,19 +85,6 @@ class StatementHandler(frontend: PythonLanguageFrontend) : } } - /** - * Translates a Python [`Raise`](https://docs.python.org/3/library/ast.html#ast.Raise) into a - * [RaiseStatement]. - */ - private fun handleRaise(raise: Python.AST.Raise): RaiseStatement { - val ret = newRaiseStatement(rawNode = raise) - - raise.exc?.let { ret.exception = frontend.expressionHandler.handle(it) } - raise.cause?.let { ret.cause = frontend.expressionHandler.handle(it) } - - return ret - } - /** * Translates a Python [`Delete`](https://docs.python.org/3/library/ast.html#ast.Delete) into a * [DeleteExpression]. From 9c8ac1ba66a969c5b9fb38492073feefc2cc758b Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Fri, 27 Sep 2024 10:36:10 +0200 Subject: [PATCH 03/30] started work on EOG and DFG --- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 28 +++++++++++++++++++ .../cpg/passes/EvaluationOrderGraphPass.kt | 5 ++++ 2 files changed, 33 insertions(+) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 6d731705ac6..6156db29fe0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -130,6 +130,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { is ForStatement -> handleForStatement(node) is SwitchStatement -> handleSwitchStatement(node) is IfStatement -> handleIfStatement(node) + is RaiseStatement -> handleRaiseStatement(node, inferDfgForUnresolvedSymbols) // Declarations is FieldDeclaration -> handleFieldDeclaration(node) is FunctionDeclaration -> handleFunctionDeclaration(node, functionSummaries) @@ -138,6 +139,33 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { } } + /** + * Handle a [RaiseStatement]. Currently, we support two types of [RaiseStatement.exception], + * which are then forwarded to the appropriate handlers: + * - [CallExpression] + * - [Reference] + */ + protected fun handleRaiseStatement( + node: RaiseStatement, + inferDfgForUnresolvedSymbols: Boolean + ) { + node.exception?.let { exc -> + when (exc) { + is CallExpression -> { + handleCallExpression(exc, inferDfgForUnresolvedSymbols) + } + is Reference -> { + handleReference(exc) + } + else -> { + log.warn( + "handleRaiseStatement: Received unexpected exception Type: ${exc.javaClass.simpleName}" + ) + } + } + } + } + protected fun handleAssignExpression(node: AssignExpression) { // If this is a compound assign, we also need to model a dataflow to the node itself if (node.isCompoundAssignment) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 5b64513ea56..e659477801a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -157,6 +157,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa map[TypeIdExpression::class.java] = { handleDefault(it) } map[Reference::class.java] = { handleDefault(it) } map[LambdaExpression::class.java] = { handleLambdaExpression(it as LambdaExpression) } + map[RaiseStatement::class.java] = { handleRaiseStatement(it as RaiseStatement) } } protected fun doNothing() { @@ -1019,6 +1020,10 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa nextEdgeBranch = false } + protected fun handleRaiseStatement(statement: RaiseStatement) { + TODO("Needs implementing") + } + companion object { protected val LOGGER = LoggerFactory.getLogger(EvaluationOrderGraphPass::class.java) From 30c49085ad11af21c52de12acc25282b999b9a7a Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Fri, 27 Sep 2024 10:39:15 +0200 Subject: [PATCH 04/30] doc --- .../fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt index 939af4bdeb5..c57dcdbeb19 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt @@ -40,7 +40,9 @@ class RaiseStatement : Statement() { /** * Some languages (Python) can add a cause to indicate that an exception was raised while - * handling another exception. + * handling another exception. This is stored in the graph, but has no further implications like + * EOG or DFG connections, as it is only of informational purpose, but it doesn't change the + * program behavior. */ @Relationship(value = "CAUSE") var causeEdge = astOptionalEdgeOf() var cause by unwrapping(RaiseStatement::causeEdge) From 3380050b5b50238a494f721b72a00ce73e4cacbb Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Fri, 27 Sep 2024 11:02:10 +0200 Subject: [PATCH 05/30] raise: fluent and first test --- .../aisec/cpg/graph/builder/Fluent.kt | 13 ++++ .../aisec/cpg/graph/RaiseStatementTest.kt | 68 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/RaiseStatementTest.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index f4443b780ea..54ea9bc66f9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -1382,6 +1382,7 @@ infix fun Expression.assignAsExpr(rhs: Expression): AssignExpression { return node } + /** * Creates a new [AssignExpression] with a `=` [AssignExpression.operatorCode] in the Fluent Node * DSL and adds it to the nearest enclosing [StatementHolder]. @@ -1396,6 +1397,18 @@ infix fun Expression.assignAsExpr(rhs: AssignExpression.() -> Unit): AssignExpre return node } +/** + * Creates a new [RaiseStatement] in the Fluent Node DSL and adds it to the nearest enclosing + * [StatementHolder]. + */ +context(LanguageFrontend<*, *>, Holder) +infix fun Expression.raise(init: (RaiseStatement.() -> Unit)?): RaiseStatement { + val node = (this@LanguageFrontend).newRaiseStatement() + if (init != null) init(node) + + return node +} + /** Creates a new [Type] with the given [name] in the Fluent Node DSL. */ fun LanguageFrontend<*, *>.t(name: CharSequence, generics: List = listOf()) = objectType(name, generics) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/RaiseStatementTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/RaiseStatementTest.kt new file mode 100644 index 00000000000..71cee2c2d6d --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/RaiseStatementTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.graph + +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.statements.RaiseStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import kotlin.test.* + +class RaiseStatementTest { + @Test + fun testRaise() { + val result = + TestLanguageFrontend().build { + translationResult { + translationUnit("some.file") { + function("foo", t("void")) { + body { + raise {} + raise { call("SomeError") } + } + } + } + } + } + + // Let's assert that we did this correctly + val main = result.functions["foo"] + assertNotNull(main) + val body = main.body + assertIs(body) + + val emptyRaise = body.statements.getOrNull(0) + assertIs(emptyRaise) + assertNull(emptyRaise.exception) + + val raiseWithExc = body.statements.getOrNull(1) + assertIs(raiseWithExc) + val raiseCall = raiseWithExc.exception + assertIs(raiseCall) + assertEquals("SomeError", raiseCall.name.localName) + } +} From 40d76589c41a84b3a891c0ed92a9fcce4a04edd4 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Fri, 27 Sep 2024 11:13:52 +0200 Subject: [PATCH 06/30] fluent: patch @oxisto --- .../aisec/cpg/graph/builder/Fluent.kt | 5 +++ .../cpg/graph/statements/RaiseStatement.kt | 32 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 54ea9bc66f9..efee4d6f616 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -1406,6 +1406,11 @@ infix fun Expression.raise(init: (RaiseStatement.() -> Unit)?): RaiseStatement { val node = (this@LanguageFrontend).newRaiseStatement() if (init != null) init(node) + val holder = this@Holder + if (holder is StatementHolder) { + holder += node + } + return node } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt index c57dcdbeb19..a867191d61d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.statements +import de.fraunhofer.aisec.cpg.graph.ArgumentHolder import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @@ -32,7 +33,7 @@ import java.util.Objects import org.neo4j.ogm.annotation.Relationship /** Represents a `throw` or `raise` statement. */ -class RaiseStatement : Statement() { +class RaiseStatement : Statement(), ArgumentHolder { /** The exception object to be raised. */ @Relationship(value = "EXCEPTION") var exceptionEdge = astOptionalEdgeOf() @@ -47,6 +48,35 @@ class RaiseStatement : Statement() { @Relationship(value = "CAUSE") var causeEdge = astOptionalEdgeOf() var cause by unwrapping(RaiseStatement::causeEdge) + override fun addArgument(expression: Expression) { + when { + exception == null -> exception = expression + cause == null -> cause = expression + } + } + + override fun replaceArgument(old: Expression, new: Expression): Boolean { + return when { + exception == old -> { + exception = new + true + } + cause == old -> { + cause = new + true + } + else -> false + } + } + + override fun hasArgument(expression: Expression): Boolean { + return when { + exception == expression -> true + cause == expression -> true + else -> false + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is RaiseStatement) return false From ca5ac9d263ca19aa7017094945913ae8d6647d62 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Fri, 27 Sep 2024 11:26:33 +0200 Subject: [PATCH 07/30] Start work on DFG test for raise --- .../cpg/graph/edges/flows/DataflowTest.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt index d01d9b9b60f..135e4cedcbb 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt @@ -26,12 +26,28 @@ package de.fraunhofer.aisec.cpg.graph.edges.flows import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.builder.body +import de.fraunhofer.aisec.cpg.graph.builder.call +import de.fraunhofer.aisec.cpg.graph.builder.declare +import de.fraunhofer.aisec.cpg.graph.builder.function +import de.fraunhofer.aisec.cpg.graph.builder.literal +import de.fraunhofer.aisec.cpg.graph.builder.raise +import de.fraunhofer.aisec.cpg.graph.builder.t +import de.fraunhofer.aisec.cpg.graph.builder.translationResult +import de.fraunhofer.aisec.cpg.graph.builder.translationUnit +import de.fraunhofer.aisec.cpg.graph.builder.variable +import de.fraunhofer.aisec.cpg.graph.functions import de.fraunhofer.aisec.cpg.graph.newLiteral import de.fraunhofer.aisec.cpg.graph.newReference +import de.fraunhofer.aisec.cpg.graph.statements.RaiseStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import kotlin.collections.firstOrNull import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull import kotlin.test.assertSame class DataflowTest { @@ -101,4 +117,35 @@ class DataflowTest { assertEquals(0, node2.prevDFGEdges.size) } } + + @Test + fun testRaise() { + val result = + TestLanguageFrontend().build { + translationResult { + translationUnit("some.file") { + function("foo", t("void")) { + body { + declare { variable("a", t("short")) { literal(42) } } + raise { call("SomeError") { /* TODO parameter a */} } + } + } + } + } + } + + // Let's assert that we did this correctly + val main = result.functions["foo"] + assertNotNull(main) + val body = main.body + assertIs(body) + + val throwStmt = body.statements.getOrNull(0) + assertIs(throwStmt) + assertNotNull(throwStmt.exception) + val throwCall = throwStmt.exception + assertIs(throwCall) + + // TODO dataflow var to call + } } From 5054a4f6f3884f6a33de7a8e4b9ba1be447ea431 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Fri, 27 Sep 2024 11:31:54 +0200 Subject: [PATCH 08/30] rename raise -> throw --- .../aisec/cpg/graph/StatementBuilder.kt | 6 ++--- .../aisec/cpg/graph/builder/Fluent.kt | 4 +-- .../{RaiseStatement.kt => ThrowStatement.kt} | 8 +++--- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 6 ++--- .../cpg/passes/EvaluationOrderGraphPass.kt | 4 +-- ...StatementTest.kt => ThrowStatementTest.kt} | 26 +++++++++---------- .../cpg/graph/edges/flows/DataflowTest.kt | 10 +++---- 7 files changed, 32 insertions(+), 32 deletions(-) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/{RaiseStatement.kt => ThrowStatement.kt} (93%) rename cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/{RaiseStatementTest.kt => ThrowStatementTest.kt} (75%) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt index 6c7cabe65e3..80c74418577 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt @@ -331,14 +331,14 @@ fun MetadataProvider.newDefaultStatement(rawNode: Any? = null): DefaultStatement } /** - * Creates a new [RaiseStatement]. The [MetadataProvider] receiver will be used to fill different + * Creates a new [ThrowStatement]. The [MetadataProvider] receiver will be used to fill different * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended * argument. */ @JvmOverloads -fun MetadataProvider.newRaiseStatement(rawNode: Any? = null): RaiseStatement { - val node = RaiseStatement() +fun MetadataProvider.newRaiseStatement(rawNode: Any? = null): ThrowStatement { + val node = ThrowStatement() node.applyMetadata(this, EMPTY_NAME, rawNode, true) log(node) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index efee4d6f616..403ca2ac72a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -1398,11 +1398,11 @@ infix fun Expression.assignAsExpr(rhs: AssignExpression.() -> Unit): AssignExpre } /** - * Creates a new [RaiseStatement] in the Fluent Node DSL and adds it to the nearest enclosing + * Creates a new [ThrowStatement] in the Fluent Node DSL and adds it to the nearest enclosing * [StatementHolder]. */ context(LanguageFrontend<*, *>, Holder) -infix fun Expression.raise(init: (RaiseStatement.() -> Unit)?): RaiseStatement { +infix fun Expression.`throw`(init: (ThrowStatement.() -> Unit)?): ThrowStatement { val node = (this@LanguageFrontend).newRaiseStatement() if (init != null) init(node) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt similarity index 93% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt index a867191d61d..4ed19e77830 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/RaiseStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt @@ -33,11 +33,11 @@ import java.util.Objects import org.neo4j.ogm.annotation.Relationship /** Represents a `throw` or `raise` statement. */ -class RaiseStatement : Statement(), ArgumentHolder { +class ThrowStatement : Statement(), ArgumentHolder { /** The exception object to be raised. */ @Relationship(value = "EXCEPTION") var exceptionEdge = astOptionalEdgeOf() - var exception by unwrapping(RaiseStatement::exceptionEdge) + var exception by unwrapping(ThrowStatement::exceptionEdge) /** * Some languages (Python) can add a cause to indicate that an exception was raised while @@ -46,7 +46,7 @@ class RaiseStatement : Statement(), ArgumentHolder { * program behavior. */ @Relationship(value = "CAUSE") var causeEdge = astOptionalEdgeOf() - var cause by unwrapping(RaiseStatement::causeEdge) + var cause by unwrapping(ThrowStatement::causeEdge) override fun addArgument(expression: Expression) { when { @@ -79,7 +79,7 @@ class RaiseStatement : Statement(), ArgumentHolder { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is RaiseStatement) return false + if (other !is ThrowStatement) return false return super.equals(other) && exception == other.exception && cause == other.cause } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 6156db29fe0..dcf2b2add62 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -130,7 +130,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { is ForStatement -> handleForStatement(node) is SwitchStatement -> handleSwitchStatement(node) is IfStatement -> handleIfStatement(node) - is RaiseStatement -> handleRaiseStatement(node, inferDfgForUnresolvedSymbols) + is ThrowStatement -> handleRaiseStatement(node, inferDfgForUnresolvedSymbols) // Declarations is FieldDeclaration -> handleFieldDeclaration(node) is FunctionDeclaration -> handleFunctionDeclaration(node, functionSummaries) @@ -140,13 +140,13 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { } /** - * Handle a [RaiseStatement]. Currently, we support two types of [RaiseStatement.exception], + * Handle a [ThrowStatement]. Currently, we support two types of [ThrowStatement.exception], * which are then forwarded to the appropriate handlers: * - [CallExpression] * - [Reference] */ protected fun handleRaiseStatement( - node: RaiseStatement, + node: ThrowStatement, inferDfgForUnresolvedSymbols: Boolean ) { node.exception?.let { exc -> diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index e659477801a..75f6950581f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -157,7 +157,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa map[TypeIdExpression::class.java] = { handleDefault(it) } map[Reference::class.java] = { handleDefault(it) } map[LambdaExpression::class.java] = { handleLambdaExpression(it as LambdaExpression) } - map[RaiseStatement::class.java] = { handleRaiseStatement(it as RaiseStatement) } + map[ThrowStatement::class.java] = { handleRaiseStatement(it as ThrowStatement) } } protected fun doNothing() { @@ -1020,7 +1020,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa nextEdgeBranch = false } - protected fun handleRaiseStatement(statement: RaiseStatement) { + protected fun handleRaiseStatement(statement: ThrowStatement) { TODO("Needs implementing") } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/RaiseStatementTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ThrowStatementTest.kt similarity index 75% rename from cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/RaiseStatementTest.kt rename to cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ThrowStatementTest.kt index 71cee2c2d6d..db86817a16c 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/RaiseStatementTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ThrowStatementTest.kt @@ -27,22 +27,22 @@ package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.builder.* -import de.fraunhofer.aisec.cpg.graph.statements.RaiseStatement +import de.fraunhofer.aisec.cpg.graph.statements.ThrowStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import kotlin.test.* -class RaiseStatementTest { +class ThrowStatementTest { @Test - fun testRaise() { + fun testThrow() { val result = TestLanguageFrontend().build { translationResult { translationUnit("some.file") { function("foo", t("void")) { body { - raise {} - raise { call("SomeError") } + `throw` {} + `throw` { call("SomeError") } } } } @@ -55,14 +55,14 @@ class RaiseStatementTest { val body = main.body assertIs(body) - val emptyRaise = body.statements.getOrNull(0) - assertIs(emptyRaise) - assertNull(emptyRaise.exception) + val emptyThrow = body.statements.getOrNull(0) + assertIs(emptyThrow) + assertNull(emptyThrow.exception) - val raiseWithExc = body.statements.getOrNull(1) - assertIs(raiseWithExc) - val raiseCall = raiseWithExc.exception - assertIs(raiseCall) - assertEquals("SomeError", raiseCall.name.localName) + val throwWithExc = body.statements.getOrNull(1) + assertIs(throwWithExc) + val throwCall = throwWithExc.exception + assertIs(throwCall) + assertEquals("SomeError", throwCall.name.localName) } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt index 135e4cedcbb..13bde0b67ba 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt @@ -31,15 +31,15 @@ import de.fraunhofer.aisec.cpg.graph.builder.call import de.fraunhofer.aisec.cpg.graph.builder.declare import de.fraunhofer.aisec.cpg.graph.builder.function import de.fraunhofer.aisec.cpg.graph.builder.literal -import de.fraunhofer.aisec.cpg.graph.builder.raise import de.fraunhofer.aisec.cpg.graph.builder.t +import de.fraunhofer.aisec.cpg.graph.builder.`throw` import de.fraunhofer.aisec.cpg.graph.builder.translationResult import de.fraunhofer.aisec.cpg.graph.builder.translationUnit import de.fraunhofer.aisec.cpg.graph.builder.variable import de.fraunhofer.aisec.cpg.graph.functions import de.fraunhofer.aisec.cpg.graph.newLiteral import de.fraunhofer.aisec.cpg.graph.newReference -import de.fraunhofer.aisec.cpg.graph.statements.RaiseStatement +import de.fraunhofer.aisec.cpg.graph.statements.ThrowStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import kotlin.collections.firstOrNull @@ -127,7 +127,7 @@ class DataflowTest { function("foo", t("void")) { body { declare { variable("a", t("short")) { literal(42) } } - raise { call("SomeError") { /* TODO parameter a */} } + `throw` { call("SomeError") { /* TODO parameter a */} } } } } @@ -135,13 +135,13 @@ class DataflowTest { } // Let's assert that we did this correctly - val main = result.functions["foo"] + val main = result.functions[0] // TODO: why can't I use "foo" like with the other tests? assertNotNull(main) val body = main.body assertIs(body) val throwStmt = body.statements.getOrNull(0) - assertIs(throwStmt) + assertIs(throwStmt) assertNotNull(throwStmt.exception) val throwCall = throwStmt.exception assertIs(throwCall) From 735f0aa79f4a44a48bd3a4b74e8d9cf943a62f9a Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Fri, 27 Sep 2024 11:35:33 +0200 Subject: [PATCH 09/30] more renaming raise -> throw --- .../de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt | 2 +- .../kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt | 2 +- .../main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 6 +++--- .../fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt | 4 ++-- .../fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt index 80c74418577..9fdf7c659d7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt @@ -337,7 +337,7 @@ fun MetadataProvider.newDefaultStatement(rawNode: Any? = null): DefaultStatement * argument. */ @JvmOverloads -fun MetadataProvider.newRaiseStatement(rawNode: Any? = null): ThrowStatement { +fun MetadataProvider.newThrowStatement(rawNode: Any? = null): ThrowStatement { val node = ThrowStatement() node.applyMetadata(this, EMPTY_NAME, rawNode, true) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 403ca2ac72a..636fe42a342 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -1403,7 +1403,7 @@ infix fun Expression.assignAsExpr(rhs: AssignExpression.() -> Unit): AssignExpre */ context(LanguageFrontend<*, *>, Holder) infix fun Expression.`throw`(init: (ThrowStatement.() -> Unit)?): ThrowStatement { - val node = (this@LanguageFrontend).newRaiseStatement() + val node = (this@LanguageFrontend).newThrowStatement() if (init != null) init(node) val holder = this@Holder diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index dcf2b2add62..7296ca8fcbd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -130,7 +130,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { is ForStatement -> handleForStatement(node) is SwitchStatement -> handleSwitchStatement(node) is IfStatement -> handleIfStatement(node) - is ThrowStatement -> handleRaiseStatement(node, inferDfgForUnresolvedSymbols) + is ThrowStatement -> handleThrowStatement(node, inferDfgForUnresolvedSymbols) // Declarations is FieldDeclaration -> handleFieldDeclaration(node) is FunctionDeclaration -> handleFunctionDeclaration(node, functionSummaries) @@ -145,7 +145,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * - [CallExpression] * - [Reference] */ - protected fun handleRaiseStatement( + protected fun handleThrowStatement( node: ThrowStatement, inferDfgForUnresolvedSymbols: Boolean ) { @@ -159,7 +159,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { } else -> { log.warn( - "handleRaiseStatement: Received unexpected exception Type: ${exc.javaClass.simpleName}" + "handleThrowStatement: Received unexpected exception Type: ${exc.javaClass.simpleName}" ) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 75f6950581f..4444665116e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -157,7 +157,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa map[TypeIdExpression::class.java] = { handleDefault(it) } map[Reference::class.java] = { handleDefault(it) } map[LambdaExpression::class.java] = { handleLambdaExpression(it as LambdaExpression) } - map[ThrowStatement::class.java] = { handleRaiseStatement(it as ThrowStatement) } + map[ThrowStatement::class.java] = { handleThrowStatement(it as ThrowStatement) } } protected fun doNothing() { @@ -1020,7 +1020,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa nextEdgeBranch = false } - protected fun handleRaiseStatement(statement: ThrowStatement) { + protected fun handleThrowStatement(statement: ThrowStatement) { TODO("Needs implementing") } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt index 13bde0b67ba..983619112f0 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt @@ -119,7 +119,7 @@ class DataflowTest { } @Test - fun testRaise() { + fun testThrow() { val result = TestLanguageFrontend().build { translationResult { From db77867dca847ccccb017a5142a66d4a074b231d Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Fri, 4 Oct 2024 16:02:22 +0200 Subject: [PATCH 10/30] copy & paste handleThrowOperator --- .../cpg/passes/EvaluationOrderGraphPass.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index cba94fcdc61..036ef45cd07 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -1029,8 +1029,28 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(stmt) } + /* + TODO: doc + Copy & paste from [handleThrowOperator] + */ protected fun handleThrowStatement(statement: ThrowStatement) { - TODO("Needs implementing") + val input = statement.exception + createEOG(input) + + val catchingScope = + scopeManager.firstScopeOrNull { scope -> scope is TryScope || scope is FunctionScope } + + val throwType = input?.type + if (throwType == null) { + TODO("???") + } + pushToEOG(statement) + if (catchingScope is TryScope) { + catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList() + } else if (catchingScope is FunctionScope) { + catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList() + } + currentPredecessors.clear() } companion object { From b0ed02bb8e45dc143c4b888b2f28710db4446998 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Tue, 8 Oct 2024 11:24:26 +0200 Subject: [PATCH 11/30] Update cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> --- .../de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt index 983619112f0..3eb158d35c0 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt @@ -140,7 +140,7 @@ class DataflowTest { val body = main.body assertIs(body) - val throwStmt = body.statements.getOrNull(0) + val throwStmt = body.statements.getOrNull(1) assertIs(throwStmt) assertNotNull(throwStmt.exception) val throwCall = throwStmt.exception From 18afeff882adc8afe996c28530f5fab92ff98e9b Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 3 Oct 2024 09:44:06 +0200 Subject: [PATCH 12/30] Rename `findSymbols` into `lookupSymbolByName` (#1772) * Rename `findSymbols` into `lookupSymbolByName` This PR renames `findSymbols` into `lookupSymbolByName` as a more appropriate name, because it lookups a symbol by its name. Fixes #1767 * Update cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> * Added documentation --------- Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> --- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 25 +++-- .../aisec/cpg/passes/ImportResolver.kt | 5 +- .../aisec/cpg/passes/SymbolResolver.kt | 6 +- .../aisec/cpg/passes/TypeResolver.kt | 2 +- .../cpg/passes/scopes/ScopeManagerTest.kt | 2 +- .../aisec/cpg/passes/GoExtraPass.kt | 2 +- docs/docs/CPG/impl/index.md | 1 + docs/docs/CPG/impl/scopes.md | 97 ++++++++++++++++++- docs/docs/CPG/impl/symbol-resolver.md | 38 ++++++++ docs/mkdocs.yaml | 3 +- 10 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 docs/docs/CPG/impl/symbol-resolver.md diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt index 72d09e4d6ae..f45809b3a5d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt @@ -899,7 +899,7 @@ class ScopeManager : ScopeProvider { * @return the declaration, or null if it does not exist */ fun getRecordForName(name: Name): RecordDeclaration? { - return findSymbols(name).filterIsInstance().singleOrNull() + return lookupSymbolByName(name).filterIsInstance().singleOrNull() } fun typedefFor(alias: Name, scope: Scope? = currentScope): Type? { @@ -960,16 +960,21 @@ class ScopeManager : ScopeProvider { get() = currentScope /** - * This function tries to resolve a [Node.name] to a list of symbols (a symbol represented by a - * [Declaration]) starting with [startScope]. This function can return a list of multiple - * symbols in order to check for things like function overloading. but it will only return list - * of symbols within the same scope; the list cannot be spread across different scopes. + * This function tries to convert a [Node.name] into a [Symbol] and then performs a lookup of + * this symbol. This can either be an "unqualified lookup" if [name] is not qualified or a + * "qualified lookup" if [Name.isQualified] is true. In the unqualified case the lookup starts + * in [startScope], in the qualified case we use [extractScope] to find the appropriate scope + * and need to restrict our search to this particular scope. * - * This means that as soon one or more symbols are found in a "local" scope, these shadow all - * other occurrences of the same / symbol in a "higher" scope and only the ones from the lower - * ones will be returned. + * This function can return a list of multiple declarations in order to check for things like + * function overloading. But it will only return list of declarations within the same scope; the + * list cannot be spread across different scopes. + * + * This means that as soon one or more declarations for the symbol are found in a "local" scope, + * these shadow all other occurrences of the same / symbol in a "higher" scope and only the ones + * from the lower ones will be returned. */ - fun findSymbols( + fun lookupSymbolByName( name: Name, location: PhysicalLocation? = null, startScope: Scope? = currentScope, @@ -1112,7 +1117,7 @@ data class CallResolutionResult( /** * A set of candidate symbols we discovered based on the [CallExpression.callee] (using - * [ScopeManager.findSymbols]), more specifically a list of [FunctionDeclaration] nodes. + * [ScopeManager.lookupSymbolByName]), more specifically a list of [FunctionDeclaration] nodes. */ var candidateFunctions: Set, diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt index eaa0b929819..5227afa584a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ImportResolver.kt @@ -58,7 +58,7 @@ class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) { // Let's do some importing. We need to import either a wildcard if (node.wildcardImport) { - val list = scopeManager.findSymbols(node.import, node.location, scope) + val list = scopeManager.lookupSymbolByName(node.import, node.location, scope) val symbol = list.singleOrNull() if (symbol != null) { // In this case, the symbol must point to a name scope @@ -69,7 +69,8 @@ class ImportResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } else { // or a symbol directly - val list = scopeManager.findSymbols(node.import, node.location, scope).toMutableList() + val list = + scopeManager.lookupSymbolByName(node.import, node.location, scope).toMutableList() node.importedSymbols = mutableMapOf(node.symbol to list) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt index ef5f1cb8e03..c1572a84dd6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt @@ -178,7 +178,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // Find a list of candidate symbols. Currently, this is only used the in the "next-gen" call // resolution, but in future this will also be used in resolving regular references. - current.candidates = scopeManager.findSymbols(current.name, current.location).toSet() + current.candidates = scopeManager.lookupSymbolByName(current.name, current.location).toSet() // Preparation for a future without legacy call resolving. Taking the first candidate is not // ideal since we are running into an issue with function pointers here (see workaround @@ -679,7 +679,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { var candidates = mutableSetOf() val records = possibleContainingTypes.mapNotNull { it.root.recordDeclaration }.toSet() for (record in records) { - candidates.addAll(ctx.scopeManager.findSymbols(record.name.fqn(symbol))) + candidates.addAll(ctx.scopeManager.lookupSymbolByName(record.name.fqn(symbol))) } // Find invokes by supertypes @@ -845,7 +845,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { listOf() } else { val firstLevelCandidates = - possibleTypes.map { scopeManager.findSymbols(it.name.fqn(name)) }.flatten() + possibleTypes.map { scopeManager.lookupSymbolByName(it.name.fqn(name)) }.flatten() // C++ does not allow overloading at different hierarchy levels. If we find a // FunctionDeclaration with the same name as the function in the CallExpression we have diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt index df57786fd15..b27c5dd7d94 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt @@ -67,7 +67,7 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { // constructor declarations and such with the same name. It seems this is ok since most // languages will prefer structs/classes over functions when resolving types. var symbols = - ctx?.scopeManager?.findSymbols(type.name, startScope = type.scope) { + ctx?.scopeManager?.lookupSymbolByName(type.name, startScope = type.scope) { it is DeclaresType } ?: listOf() diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt index a73658f4217..3fb62a3bfe1 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/scopes/ScopeManagerTest.kt @@ -100,7 +100,7 @@ internal class ScopeManagerTest : BaseTest() { // resolve symbol val call = frontend.newCallExpression(frontend.newReference("A::func1"), "A::func1", false) - val func = final.findSymbols(call.callee!!.name).firstOrNull() + val func = final.lookupSymbolByName(call.callee!!.name).firstOrNull() assertEquals(func1, func) } diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt index 2df5eeb357e..5297e13826a 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -403,7 +403,7 @@ class GoExtraPass(ctx: TranslationContext) : ComponentPass(ctx) { // Try to see if we already know about this namespace somehow val namespace = - scopeManager.findSymbols(import.name, null).filter { + scopeManager.lookupSymbolByName(import.name, null).filter { it is NamespaceDeclaration && it.path == import.importURL } diff --git a/docs/docs/CPG/impl/index.md b/docs/docs/CPG/impl/index.md index 9359f148d5d..dc0b49f7db1 100755 --- a/docs/docs/CPG/impl/index.md +++ b/docs/docs/CPG/impl/index.md @@ -24,3 +24,4 @@ the graph. These two stages are strictly separated one from each other. * [Languages and Language Frontends](./language) * [Scopes](./scopes) * [Passes](./passes) +* [Symbol Resolution](./symbol-resolver.md) diff --git a/docs/docs/CPG/impl/scopes.md b/docs/docs/CPG/impl/scopes.md index 9fafc1ea83e..c5ab9851040 100755 --- a/docs/docs/CPG/impl/scopes.md +++ b/docs/docs/CPG/impl/scopes.md @@ -1,6 +1,6 @@ --- -title: "Implementation and Concepts - Scopes" -linkTitle: "Implementation and Concepts - Scopes" +title: "Implementation and Concepts - Scopes and Symbols" +linkTitle: "Implementation and Concepts - Scopes and Symbols" weight: 20 no_list: false menu: @@ -11,5 +11,96 @@ description: > --- -# Implementation and Concepts: Scopes and Scope Manger +# Scopes and Symbols +The concept of scopes and symbols are at the heart of every programming language and thus are also the core of static analysis. Both concepts consist in the CPG library through the types `Scope` and `Symbol` respectively. + +A "symbol" can be seen as an identifier in most programming languages, referring to variables or functions. Symbols are often grouped in scopes, which defines the visibility of a symbol, e.g. a slice of a program that can "see" the symbol. Often this is also synonymous with the life-time of a variable, e.g., that its memory will be freed (or collected by a garbage collector) once it goes "out of scope". + +```c +// This defines a symbol "a" in the global/file scope. +// Its visibility is global within the file. +int a = 1; + +int main() { + // this defines another symbol "a" in a function/block scope. + // Its visibility is limited to the block it is defined in. + int a = 1; +} +``` + +Usually symbols declared in a local scope override the declaration of a symbol in a higher (e.g., global scope), which is also referred to as "shadowing". This needs to be taken into account when resolving symbols to their declarations. + +The `Scope` class holds all its symbols in the `Scope::symbols` property. More specifically, this property is a `SymbolMap`, which is a type alias to a map, whose key type is a `Symbol` and whose value type is a list of `Declaration` nodes. This is basically a symbol lookup table for all symbols in its scope. It is a map of a list because some programming languages have concepts like function overloading, which leads to the declaration of multiple `FunctionDeclaration` nodes under the same symbol in one scope. In the current implementation, a `Symbol` is just a typealias for a string, and it is always "local" to the scope, meaning that it MUST NOT contain any qualifier. If you want to refer to a fully qualified identifier, a `Name` must be used. In the future, we might consider merging the concepts of `Symbol` and `Name`. + +For a frontend or pass developer, the main interaction point with scopes and symbols is through the `ScopeManager`. The scope manager is available to all nodes via the `TranslationContext` and also injected in frontend, handlers and passes. + +## Hierarchy of Scopes + +Each scope (except the `GlobalScope`) can have a parent and possible child scopes. This can be used to model a hierarchy of scopes within a program. For example using the snippet above, the following scopes are defined in the CPG: + +* A `GlobalScope` that comprises the whole file +* A `FunctionScope` that comprises the function `main` +* A `BlockScope` that comprises the function body + +Note, that each programming language is different when it comes to scoping and this needs to be thought of by a frontend developer. For example in C/C++ each block introduced by `{}` introduces a new scope and variables can be declared only for such a block, meaning that each `for`, `if` and other statements also introduce a new scope. In contrast, Python only differentiates between a global scope, function and class scope. + +## Defining Scopes and Declaring Symbols + +In order to define new scopes, the `ScopeManager` offers two main APIs: + +* `enterScope(node)`, which specifies that `node` will declare a new scope and that an appropriate `Scope` (or derived type) will be created +* `leaveScope(node)`, which closes the scope again + +It is important that every opened scope must also be closed again. When scopes are nested, they also need to be closed in reverse order. + +```Kotlin +// We are inside the global scope here and want to create a new function +var func = newFunctionDeclaration("main") + +// Create a function scope +scopeManager.enterScope(func) + +// Create a block scope for the body because our language works this way +var body = newBlock() +func.body = body +scopeManager.enterScope(body) + +// Add statements here +body.statements += /* ... */ + +// Leave block scope +scopeManager.leaveScope(body) + +// Back to global scope, add the function to global scope +scopeManager.leaveScope(func) +scopeManager.addDeclaration(func) +``` + +Inside the scope, declarations can be added with `ScopeManager::addDeclaration`. This takes care of adding the declaration to an appropriate place in the AST (which beyond the scope of this document) and also adds the `Declaration` to the `Scope` under the appropriate `Symbol`. + + +## Looking up Symbols + +During different analysis steps, e.g., in different passes, we want to find certain symbols or lookup the declaration(s) belonging to a particular symbol. There are two functions in order to do so - a "higher" level concept in the `ScopeManager` and a "lower" level function on the `Scope` itself. + +The lower level one is called `Scope::lookupSymbol` and can be used to retrieve a list of `Declaration` nodes that belong to a particular `Symbol` that is "visible" the scope. It does so by first looking through its own `Scope::symbols`. If no match was found, the scope is traversed upwards to its `Scope::parent`, until a match is found. Furthermore, additional logic is needed to resolve symbol that are pointing to another scope, e.g., because they represent an `ImportDeclaration`. + +```Kotlin +var scope = /* ... */ +var declarations = scope.lookupSymbol("a") { + // Some additional predicate if we want +} +``` + +Additionally, the lookup can be fine-tuned by an additional predicate. However, this should be used carefully as it restricts the possible list of symbols very early. In most cases the list of symbols should be quite exhaustive at first to find all possible candidates and then selecting the best candidate in a second step (e.g., based on argument types for a function call). + +While the aforementioned API works great if we already have a specific start scope and local `Symbol`, we often start our resolution process with a `Name` -- which could potentially be qualified, such as `std::string`. Therefore, the "higher level" function `ScopeManager::lookupSymbolByName` can be used to retrieve a list of candidate declarations by a given `Name`. In a first step, the name is checked for a potential scope qualifier (`std` in this example). If present, it is extracted and the search scope is set to it. This is what is usually referred to as a "qualified lookup". Otherwise, the local part of the name is used to start the lookup, in what is called an "unqualified lookup". In both cases, the actual lookup is delegated to `ScopeManager::lookupSymbols`, but with different parameters. + +```Kotlin +var name = parseName("std::string") +// This will return all the 'string' symbols within the 'std' name scope +var stringSymbols = scopeManager.lookupSymbolByName(name) +``` + +Developers should avoid symbol lookup during frontend parsing, since often during parsing, only a limited view of all symbols is available. Instead, a dedicated pass that is run on the complete translation result is the preferred option. Apart from that, the main usage of this API is in the [SymbolResolver](symbol-resolver.md). \ No newline at end of file diff --git a/docs/docs/CPG/impl/symbol-resolver.md b/docs/docs/CPG/impl/symbol-resolver.md new file mode 100644 index 00000000000..c052e6b69fc --- /dev/null +++ b/docs/docs/CPG/impl/symbol-resolver.md @@ -0,0 +1,38 @@ +--- +title: "Implementation and Concepts - Symbol Resolution" +linkTitle: "Implementation and Concepts - Symbol Resolution" +weight: 20 +no_list: false +menu: + main: + weight: 20 +description: > + The CPG library is a language-agnostic graph representation of source code. +--- + + +# Symbol Resolution + +This pages describes the main functionality behind symbol resolution in the CPG library. This is mostly done by the `SymbolResolver` pass, in combination with the symbol lookup API (see [Scopes and Symbols](scopes.md#looking-up-symbols)). In addition to the *lookup* of a symbol, the *resolution* takes the input of the lookup and provides a "definite" decision which symbol is used. This mostly referred to symbols / names used in a `Reference` or a `CallExpression` (which also has a reference as its `CallExpression::callee`). + +## The `SymbolResolver` Pass + +The `SymbolResolver` pass takes care of the heavy lifting of symbol (or rather reference) resolving: + +* It sets the `Reference::refersTo` property, +* and sets the `CallExpression::invokes` property, +* and finally takes cares of operator overloading (if the language supports it). + +In a way, it can be compared to a linker step in a compiler. The pass operates on a single `Component` and starts by identifying EOG starter nodes within the component. These node "start" an EOG sub-graph, i.e., they do not have any previous EOG edges. The symbol resolver uses the `ScopedWalker` with a special set-up that traverses the EOG starting with each EOG starter node until it reaches the end. This ensures that symbols are resolved in the correct order of "evaluation", e.g., that a base of a member expression is resolved before the expression itself. This ensures that necessary type information on the base are available in order to resolve appropriate fields of the member expression. + +The symbol resolver itself has gone through many re-writes over the years and there is still some code left that we consider *legacy*. These functions are marked as such, and we aim to remove them slowly. + +## Resolving References + +The main functionality lies in `ScopeManager::handleReference`. For all `Reference` nodes (that are not `MemberExpression` nodes) we use the symbol lookup API to find declaration candidates for the name the reference is referring to. This candidate list is then stored in `Reference::candidates`. If the reference is the `CallExpression::callee` property of a call, we abort here and jump to [Resolve Calls](#resolve-calls). + +Otherwise, we currently take the first entry of the candidate list and set the `Reference::refersTo` property to it. + +## Resolve Calls + +Prequisite: The `CallExpression::callee` reference must have been resolved (see [Resolving References](#resolving-references)). \ No newline at end of file diff --git a/docs/mkdocs.yaml b/docs/mkdocs.yaml index b6a535732db..e73c12e7522 100755 --- a/docs/mkdocs.yaml +++ b/docs/mkdocs.yaml @@ -167,8 +167,9 @@ nav: - "Implementation": - CPG/impl/index.md - "Language Frontends": CPG/impl/language.md - - "Scopes": CPG/impl/scopes.md + - "Scopes and Symbols": CPG/impl/scopes.md - "Passes": CPG/impl/passes.md + - "Symbol Resolution": CPG/impl/symbol-resolver.md - "Contributing": - "Contributing to the CPG library": Contributing/index.md # This assumes that the most recent dokka build was generated with the "main" tag! From 2c7dd718bad0eb4c49d9074302c82c79a11930c4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:17:41 +0200 Subject: [PATCH 13/30] Update dependency rollup to v4.24.0 (#1774) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/main/nodejs/package-lock.json | 134 +++++++++--------- 1 file changed, 67 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 0e38314b0c0..622b6a1ef3e 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.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.23.0.tgz", - "integrity": "sha512-8OR+Ok3SGEMsAZispLx8jruuXw0HVF16k+ub2eNXKHDmdxL4cf9NlNpAzhlOhNyXzKDEJuFeq0nZm+XlNb1IFw==", + "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==", "cpu": [ "arm" ], @@ -166,9 +166,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.23.0.tgz", - "integrity": "sha512-rEFtX1nP8gqmLmPZsXRMoLVNB5JBwOzIAk/XAcEPuKrPa2nPJ+DuGGpfQUR0XjRm8KjHfTZLpWbKXkA5BoFL3w==", + "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==", "cpu": [ "arm64" ], @@ -180,9 +180,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.23.0.tgz", - "integrity": "sha512-ZbqlMkJRMMPeapfaU4drYHns7Q5MIxjM/QeOO62qQZGPh9XWziap+NF9fsqPHT0KzEL6HaPspC7sOwpgyA3J9g==", + "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==", "cpu": [ "arm64" ], @@ -194,9 +194,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.23.0.tgz", - "integrity": "sha512-PfmgQp78xx5rBCgn2oYPQ1rQTtOaQCna0kRaBlc5w7RlA3TDGGo7m3XaptgitUZ54US9915i7KeVPHoy3/W8tA==", + "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==", "cpu": [ "x64" ], @@ -208,9 +208,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.23.0.tgz", - "integrity": "sha512-WAeZfAAPus56eQgBioezXRRzArAjWJGjNo/M+BHZygUcs9EePIuGI1Wfc6U/Ki+tMW17FFGvhCfYnfcKPh18SA==", + "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==", "cpu": [ "arm" ], @@ -222,9 +222,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.23.0.tgz", - "integrity": "sha512-v7PGcp1O5XKZxKX8phTXtmJDVpE20Ub1eF6w9iMmI3qrrPak6yR9/5eeq7ziLMrMTjppkkskXyxnmm00HdtXjA==", + "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==", "cpu": [ "arm" ], @@ -236,9 +236,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.23.0.tgz", - "integrity": "sha512-nAbWsDZ9UkU6xQiXEyXBNHAKbzSAi95H3gTStJq9UGiS1v+YVXwRHcQOQEF/3CHuhX5BVhShKoeOf6Q/1M+Zhg==", + "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==", "cpu": [ "arm64" ], @@ -250,9 +250,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.23.0.tgz", - "integrity": "sha512-5QT/Di5FbGNPaVw8hHO1wETunwkPuZBIu6W+5GNArlKHD9fkMHy7vS8zGHJk38oObXfWdsuLMogD4sBySLJ54g==", + "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==", "cpu": [ "arm64" ], @@ -264,9 +264,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.23.0.tgz", - "integrity": "sha512-Sefl6vPyn5axzCsO13r1sHLcmPuiSOrKIImnq34CBurntcJ+lkQgAaTt/9JkgGmaZJ+OkaHmAJl4Bfd0DmdtOQ==", + "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==", "cpu": [ "ppc64" ], @@ -278,9 +278,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.23.0.tgz", - "integrity": "sha512-o4QI2KU/QbP7ZExMse6ULotdV3oJUYMrdx3rBZCgUF3ur3gJPfe8Fuasn6tia16c5kZBBw0aTmaUygad6VB/hQ==", + "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==", "cpu": [ "riscv64" ], @@ -292,9 +292,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.23.0.tgz", - "integrity": "sha512-+bxqx+V/D4FGrpXzPGKp/SEZIZ8cIW3K7wOtcJAoCrmXvzRtmdUhYNbgd+RztLzfDEfA2WtKj5F4tcbNPuqgeg==", + "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==", "cpu": [ "s390x" ], @@ -306,9 +306,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.23.0.tgz", - "integrity": "sha512-I/eXsdVoCKtSgK9OwyQKPAfricWKUMNCwJKtatRYMmDo5N859tbO3UsBw5kT3dU1n6ZcM1JDzPRSGhAUkxfLxw==", + "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==", "cpu": [ "x64" ], @@ -320,9 +320,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.23.0.tgz", - "integrity": "sha512-4ZoDZy5ShLbbe1KPSafbFh1vbl0asTVfkABC7eWqIs01+66ncM82YJxV2VtV3YVJTqq2P8HMx3DCoRSWB/N3rw==", + "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==", "cpu": [ "x64" ], @@ -334,9 +334,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.23.0.tgz", - "integrity": "sha512-+5Ky8dhft4STaOEbZu3/NU4QIyYssKO+r1cD3FzuusA0vO5gso15on7qGzKdNXnc1gOrsgCqZjRw1w+zL4y4hQ==", + "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==", "cpu": [ "arm64" ], @@ -348,9 +348,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.23.0.tgz", - "integrity": "sha512-0SPJk4cPZQhq9qA1UhIRumSE3+JJIBBjtlGl5PNC///BoaByckNZd53rOYD0glpTkYFBQSt7AkMeLVPfx65+BQ==", + "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==", "cpu": [ "ia32" ], @@ -362,9 +362,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.23.0.tgz", - "integrity": "sha512-lqCK5GQC8fNo0+JvTSxcG7YB1UKYp8yrNLhsArlvPWN+16ovSZgoehlVHg6X0sSWPUkpjRBR5TuR12ZugowZ4g==", + "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==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/rollup": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.23.0.tgz", - "integrity": "sha512-vXB4IT9/KLDrS2WRXmY22sVB2wTsTwkpxjB8Q3mnakTENcYw3FRmfdYDy/acNmls+lHmDazgrRjK/yQ6hQAtwA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", "dev": true, "license": "MIT", "dependencies": { @@ -562,22 +562,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.23.0", - "@rollup/rollup-android-arm64": "4.23.0", - "@rollup/rollup-darwin-arm64": "4.23.0", - "@rollup/rollup-darwin-x64": "4.23.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.23.0", - "@rollup/rollup-linux-arm-musleabihf": "4.23.0", - "@rollup/rollup-linux-arm64-gnu": "4.23.0", - "@rollup/rollup-linux-arm64-musl": "4.23.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.23.0", - "@rollup/rollup-linux-riscv64-gnu": "4.23.0", - "@rollup/rollup-linux-s390x-gnu": "4.23.0", - "@rollup/rollup-linux-x64-gnu": "4.23.0", - "@rollup/rollup-linux-x64-musl": "4.23.0", - "@rollup/rollup-win32-arm64-msvc": "4.23.0", - "@rollup/rollup-win32-ia32-msvc": "4.23.0", - "@rollup/rollup-win32-x64-msvc": "4.23.0", + "@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", "fsevents": "~2.3.2" } }, From f54268167063ea3a9a8d54a384ded0d41b81c909 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 3 Oct 2024 22:10:49 +0200 Subject: [PATCH 14/30] Added language trait `HasImplicitReceiver` (#1778) Added language trait `HasImplicit Receiver` This is needed in preparation for #1777 to better handle access to fields/members of a class when an implicit receiver is used. --- .../de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt | 9 +++++++++ .../de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt | 6 +++++- .../fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt | 5 ++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index beb36b36618..dee23cc6974 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt @@ -123,6 +123,15 @@ interface HasSuperClasses : LanguageTrait { ): Boolean } +/** + * A language trait, that specifies that this language has support for implicit receiver, e.g., that + * one can omit references to a base such as `this`. + */ +interface HasImplicitReceiver : LanguageTrait { + + val receiverName: String +} + /** * A language trait, that specifies that this language has certain qualifiers. If so, we should * consider them when parsing the types. diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt index 23d7b942fdb..c5fc9815fd7 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt @@ -56,7 +56,8 @@ open class CPPLanguage : HasUnknownType, HasFunctionStyleCasts, HasFunctionOverloading, - HasOperatorOverloading { + HasOperatorOverloading, + HasImplicitReceiver { override val fileExtensions = listOf("cpp", "cc", "cxx", "c++", "hpp", "hh") override val elaboratedTypeSpecifier = listOf("class", "struct", "union", "enum") override val unknownTypeString = listOf("auto") @@ -317,4 +318,7 @@ open class CPPLanguage : return Pair(false, listOf()) } + + override val receiverName: String + get() = "this" } diff --git a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt index 4a3ed16ee2e..f0f6497559e 100644 --- a/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt +++ b/cpg-language-java/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/java/JavaLanguage.kt @@ -45,7 +45,8 @@ open class JavaLanguage : HasQualifier, HasUnknownType, HasShortCircuitOperators, - HasFunctionOverloading { + HasFunctionOverloading, + HasImplicitReceiver { override val fileExtensions = listOf("java") override val namespaceDelimiter = "." @Transient override val frontend: KClass = JavaLanguageFrontend::class @@ -116,4 +117,6 @@ open class JavaLanguage : override val startCharacter = '<' override val endCharacter = '>' + override val receiverName: String + get() = "this" } From 01877547a65698f4d11a4b62b4361c2564a23df3 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 4 Oct 2024 15:07:24 +0200 Subject: [PATCH 15/30] Cleanup of `SymbolResolver` (#1777) --- .../fraunhofer/aisec/cpg/graph/types/Type.kt | 5 + .../aisec/cpg/passes/SymbolResolver.kt | 396 ++++-------------- .../aisec/cpg/passes/TypeResolver.kt | 134 +++--- .../aisec/cpg/passes/inference/PassHelper.kt | 332 +++++++++++++++ .../cpg/passes/PythonAddDeclarationsPass.kt | 32 +- .../frontends/python/PythonFrontendTest.kt | 6 +- .../TypescriptLanguageFrontendTest.kt | 2 +- 7 files changed, 507 insertions(+), 400 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/PassHelper.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt index ad167ae643a..1c38508df0d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/types/Type.kt @@ -304,7 +304,12 @@ var Type.recordDeclaration: RecordDeclaration? } } +/** + * This interfaces specifies that this node (most likely a [Declaration]) declares a type. This is + * used by [TypeResolver.resolveType] to find appropriate symbols and declarations. + */ interface DeclaresType { + /** The [Type] that is being declared. */ val declaredType: Type } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt index c1572a84dd6..d8e455d6369 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt @@ -31,7 +31,6 @@ import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.NameScope -import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Symbol import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* @@ -39,10 +38,10 @@ import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker import de.fraunhofer.aisec.cpg.helpers.Util import de.fraunhofer.aisec.cpg.helpers.replace import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn -import de.fraunhofer.aisec.cpg.passes.inference.Inference.TypeInferenceObserver -import de.fraunhofer.aisec.cpg.passes.inference.inferFunction -import de.fraunhofer.aisec.cpg.passes.inference.inferMethod import de.fraunhofer.aisec.cpg.passes.inference.startInference +import de.fraunhofer.aisec.cpg.passes.inference.tryFieldInference +import de.fraunhofer.aisec.cpg.passes.inference.tryFunctionInference +import de.fraunhofer.aisec.cpg.passes.inference.tryVariableInference import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -162,28 +161,53 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return target } - protected fun handleReference(currentClass: RecordDeclaration?, current: Node?) { - val language = current?.language - - if (current !is Reference || current is MemberExpression) return + /** + * This function handles symbol resolving for a [Reference]. After a successful lookup of the + * symbol contained in [Reference.name], the property [Reference.refersTo] is set to the best + * (or only) candidate. + * + * On a high-level, it performs the following steps: + * - Use [ScopeManager.lookupSymbolByName] to retrieve [Declaration] candidates based on the + * [Reference.name]. This can either result in an "unqualified" or "qualified" lookup, + * depending on the name. + * - The results of the lookup are stored in [Reference.candidates]. The purpose of this is + * two-fold. First, it is a good way to debug potential symbol resolution errors. Second, it + * is used by other functions, for example [handleCallExpression], which then picks the best + * viable option out of the candidates (if the reference is part of the + * [CallExpression.callee]). + * - In the next step, we need to decide whether we are resolving a standalone reference (which + * most likely points to a [VariableDeclaration]) or if we are part of a + * [CallExpression.callee]. In the first case, we can directly assign [Reference.refersTo] + * based on the candidates (at the moment we only assign it if we have exactly one candidate). + * In the second case, we are finished and let [handleCallExpression] take care of the rest + * once the EOG reaches the appropriate [CallExpression] (which should actually be just be the + * next EOG node). + */ + protected fun handleReference(currentClass: RecordDeclaration?, ref: Reference) { + val language = ref.language // Ignore references to anonymous identifiers, if the language supports it (e.g., the _ // identifier in Go) if ( - language is HasAnonymousIdentifier && - current.name.localName == language.anonymousIdentifier + language is HasAnonymousIdentifier && ref.name.localName == language.anonymousIdentifier ) { return } + // Ignore references to "super" if the language has super expressions, because they will be + // handled separately in handleMemberExpression + if (language is HasSuperClasses && ref.name.localName == language.superClassKeyword) { + return + } + // Find a list of candidate symbols. Currently, this is only used the in the "next-gen" call // resolution, but in future this will also be used in resolving regular references. - current.candidates = scopeManager.lookupSymbolByName(current.name, current.location).toSet() + ref.candidates = scopeManager.lookupSymbolByName(ref.name, ref.location).toSet() // Preparation for a future without legacy call resolving. Taking the first candidate is not // ideal since we are running into an issue with function pointers here (see workaround // below). - var wouldResolveTo = current.candidates.singleOrNull() + var wouldResolveTo = ref.candidates.singleOrNull() // For now, we need to ignore reference expressions that are directly embedded into call // expressions, because they are the "callee" property. In the future, we will use this @@ -194,7 +218,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // of this call expression back to its original variable declaration. In the future, we want // to extend this particular code to resolve all callee references to their declarations, // i.e., their function definitions and get rid of the separate CallResolver. - if (current.resolutionHelper is CallExpression) { + if (ref.resolutionHelper is CallExpression) { // Peek into the declaration, and if it is only one declaration and a variable, we can // proceed normally, as we are running into the special case explained above. Otherwise, // we abort here (for now). @@ -208,7 +232,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // percentage of references now. if (wouldResolveTo is FunctionDeclaration) { // We need to invoke the legacy resolver, just to be sure - var legacy = scopeManager.resolveReference(current) + var legacy = scopeManager.resolveReference(ref) // This is just for us to catch these differences in symbol resolving in the future. The // difference is pretty much only that the legacy system takes parameters of the @@ -225,92 +249,27 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { // Only consider resolving, if the language frontend did not specify a resolution. If we // already have populated the wouldResolveTo variable, we can re-use this instead of // resolving again - var refersTo = current.refersTo ?: wouldResolveTo + var refersTo = ref.refersTo ?: wouldResolveTo var recordDeclType: Type? = null if (currentClass != null) { recordDeclType = currentClass.toType() } - val helperType = current.resolutionHelper?.type + val helperType = ref.resolutionHelper?.type if (helperType is FunctionPointerType && refersTo == null) { - refersTo = resolveMethodFunctionPointer(current, helperType) - } - - // only add new nodes for non-static unknown - if ( - refersTo == null && - !current.isStaticAccess && - recordDeclType != null && - recordDeclType.recordDeclaration != null - ) { - // Maybe we are referring to a field instead of a local var - val field = resolveMember(recordDeclType, current) - if (field != null) { - refersTo = field - } - } - - // TODO: we need to do proper scoping (and merge it with the code above), but for now - // this just enables CXX static fields - if (refersTo == null && language != null && current.name.isQualified()) { - recordDeclType = getEnclosingTypeOf(current) - val field = resolveMember(recordDeclType, current) - if (field != null) { - refersTo = field - } + refersTo = resolveMethodFunctionPointer(ref, helperType) } + // If we did not resolve the reference up to this point, we can try to infer the declaration if (refersTo == null) { - // We can try to infer a possible global variable, if the language supports this - refersTo = tryGlobalVariableInference(current) + refersTo = tryVariableInference(ref) } if (refersTo != null) { - current.refersTo = refersTo + ref.refersTo = refersTo } else { - Util.warnWithFileLocation( - current, - log, - "Did not find a declaration for ${current.name}" - ) - } - } - - /** - * Tries to infer a global variable from an unresolved [Reference]. This will return `null`, if - * inference was not possible, or if it was turned off in the [InferenceConfiguration]. - */ - private fun tryGlobalVariableInference(ref: Reference): Declaration? { - if (ref.language !is HasGlobalVariables) { - return null - } - - // For now, we only infer globals at the top-most global level, i.e., no globals in - // namespaces - if (ref.name.isQualified()) { - return null - } - - // Forward this to our inference system. This will also check whether and how inference is - // configured. - return scopeManager.globalScope?.astNode?.startInference(ctx)?.inferVariableDeclaration(ref) - } - - /** - * We get the type of the "scope" this node is in. (e.g. for a field, we drop the field's name - * and have the class) - */ - protected fun getEnclosingTypeOf(current: Node): Type { - val language = current.language - - return if (language != null && language.namespaceDelimiter.isNotEmpty()) { - val parentName = (current.name.parent ?: current.name).toString() - var type = current.objectType(parentName) - TypeResolver.resolveType(type) - type - } else { - current.unknownType() + Util.warnWithFileLocation(ref, log, "Did not find a declaration for ${ref.name}") } } @@ -356,17 +315,22 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } val baseType = base.type.root - current.refersTo = resolveMember(baseType, current) + if (baseType is ObjectType) { + current.refersTo = resolveMember(baseType, current) + } } - protected fun resolveMember(containingClass: Type, reference: Reference): ValueDeclaration? { + protected fun resolveMember( + containingClass: ObjectType, + reference: Reference + ): ValueDeclaration? { if (isSuperclassReference(reference)) { // if we have a "super" on the member side, this is a member call. We need to resolve // this in the call resolver instead return null } var member: ValueDeclaration? = null - var type = containingClass + var type: Type = containingClass // Check for a possible overloaded operator-> if ( @@ -390,6 +354,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { val record = type.recordDeclaration if (record != null) { + // TODO(oxisto): This should use symbols rather than the AST fields member = record.fields .filter { it.name.lastPartsMatch(reference.name) } @@ -404,87 +369,16 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { .map { it.definition } .firstOrNull() } + if (member == null && record is EnumDeclaration) { member = record.entries[reference.name.localName] } - if (member != null) { - return member - } - - // This is a little bit of a workaround, but at least this makes sure we are not inferring a - // record, where a namespace already exist - val (scope, _) = scopeManager.extractScope(reference, null) - return if (scope == null) { - handleUnknownField(containingClass, reference) - } else { - // Workaround needed for Java. If we already have a record scope, use the "old" - // inference function - when (scope) { - is RecordScope -> handleUnknownField(containingClass, reference) - is NameScope -> { - log.warn( - "We should infer a namespace variable ${reference.name} at this point, but this is not yet implemented." - ) - null - } - else -> { - log.warn( - "We should infer a variable ${reference.name} in ${scope}, but this is not yet implemented." - ) - null - } - } - } - } - - // TODO(oxisto): Move to inference class - protected fun handleUnknownField(base: Type, ref: Reference): FieldDeclaration? { - val name = ref.name - - // unwrap a potential pointer-type - if (base is PointerType) { - return handleUnknownField(base.elementType, ref) - } - - var record = base.recordDeclaration - if (record == null) { - // We access an unknown field of an unknown record. so we need to handle that along the - // way as well - record = ctx.tryRecordInference(base, locationHint = ref) - } - - if (record == null) { - log.error( - "There is no matching record in the record map. Can't identify which field is used." - ) - return null + if (member == null) { + member = tryFieldInference(reference, containingClass) } - val target = record.fields.firstOrNull { it.name.lastPartsMatch(name) } - - return if (target != null) { - target - } else { - val declaration = - newFieldDeclaration( - name.localName, - // we will set the type later through the type inference observer - record.unknownType(), - listOf(), - null, - false, - ) - record.addField(declaration) - declaration.language = record.language - declaration.isInferred = true - - // We might be able to resolve the type later (or better), if a type is - // assigned to our reference later - ref.registerTypeObserver(TypeInferenceObserver(declaration)) - - declaration - } + return member } protected fun handle(node: Node?, currClass: RecordDeclaration?) { @@ -699,36 +593,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return candidates } - /** - * Creates an inferred element for each RecordDeclaration - * - * @param possibleContainingTypes - * @param call - */ - protected fun createMethodDummies( - possibleContainingTypes: Set, - bestGuess: Type?, - call: CallExpression - ): List { - var records = - possibleContainingTypes.mapNotNull { - val root = it.root as? ObjectType - root?.recordDeclaration - } - - // We access an unknown method of an unknown record. so we need to handle that - // along the way as well. We prefer the base type - if (records.isEmpty()) { - records = - listOfNotNull( - ctx.tryRecordInference(bestGuess?.root ?: unknownType(), locationHint = call) - ) - } - records = records.distinct() - - return records.mapNotNull { record -> record.inferMethod(call, ctx = ctx) } - } - protected fun handleConstructExpression(constructExpression: ConstructExpression) { if (constructExpression.instantiates != null && constructExpression.constructor != null) return @@ -810,32 +674,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return resolveWithArguments(candidates, op.operatorArguments, op as Expression) } - /** - * Returns a set of types in which the callee of our [call] could reside in. More concretely, it - * returns a [Pair], where the first element is the set of types and the second is our best - * guess. - */ - protected fun getPossibleContainingTypes(call: CallExpression): Pair, Type?> { - val possibleTypes = mutableSetOf() - var bestGuess: Type? = null - if (call is MemberCallExpression) { - call.base?.let { base -> - bestGuess = base.type - possibleTypes.add(base.type) - possibleTypes.addAll(base.assignedTypes) - } - } else { - // This could be a C++ member call with an implicit this (which we do not create), so - // let's add the current class to the possible list - scopeManager.currentRecord?.toType()?.let { - bestGuess = it - possibleTypes.add(it) - } - } - - return Pair(possibleTypes, bestGuess) - } - protected fun getInvocationCandidatesFromParents( name: Symbol, possibleTypes: Set, @@ -909,48 +747,6 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { ?.createInferredConstructor(constructExpression.signature) } - fun tryFunctionInference( - call: CallExpression, - result: CallResolutionResult, - ): List { - // We need to see, whether we have any suitable base (e.g. a class) or not; There are two - // main cases - // a) we have a member expression -> easy - // b) we have a call expression -> not so easy. This could be a member call with an implicit - // this (in which case we want to explore the base type). But that is only possible if - // the callee is not qualified, because otherwise we are in a static call like - // MyClass::doSomething() or in a namespace call (in case we do not want to explore the - // base type here yet). This will change in a future PR. - val (suitableBases, bestGuess) = - if (call.callee is MemberExpression || !call.callee.name.isQualified()) { - getPossibleContainingTypes(call) - } else { - Pair(setOf(), null) - } - - return if (suitableBases.isEmpty()) { - // Resolution has provided no result, we can forward this to the inference system, - // if we want. While this is definitely a function, it could still be a function - // inside a namespace. We therefore have two possible start points, a namespace - // declaration or a translation unit. Nothing else is allowed (fow now). We can - // re-use the information in the ResolutionResult, since this already contains the - // actual start scope (e.g. in case the callee has an FQN). - var scope = result.actualStartScope - if (scope !is NameScope) { - scope = scopeManager.globalScope - } - val func = - when (val start = scope?.astNode) { - is TranslationUnitDeclaration -> start.inferFunction(call, ctx = ctx) - is NamespaceDeclaration -> start.inferFunction(call, ctx = ctx) - else -> null - } - listOfNotNull(func) - } else { - createMethodDummies(suitableBases, bestGuess, call) - } - } - companion object { val LOGGER: Logger = LoggerFactory.getLogger(SymbolResolver::class.java) @@ -976,67 +772,27 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } -fun TranslationContext.tryNamespaceInference( - name: Name, - locationHint: Node? -): NamespaceDeclaration? { - return scopeManager.globalScope - ?.astNode - ?.startInference(this) - ?.inferNamespaceDeclaration(name, null, locationHint) -} - /** - * Tries to infer a [RecordDeclaration] from an unresolved [Type]. This will return `null`, if - * inference was not possible, or if it was turned off in the [InferenceConfiguration]. + * Returns a set of types in which the callee of our [call] could reside in. More concretely, it + * returns a [Pair], where the first element is the set of types and the second is our best guess. */ -fun TranslationContext.tryRecordInference( - type: Type, - locationHint: Node? = null -): RecordDeclaration? { - val kind = - if (type.language is HasStructs) { - "struct" - } else { - "class" +internal fun Pass<*>.getPossibleContainingTypes(call: CallExpression): Pair, Type?> { + val possibleTypes = mutableSetOf() + var bestGuess: Type? = null + if (call is MemberCallExpression) { + call.base?.let { base -> + bestGuess = base.type + possibleTypes.add(base.type) + possibleTypes.addAll(base.assignedTypes) + } + } else if (call.language is HasImplicitReceiver) { + // This could be a member call with an implicit receiver, so let's add the current class + // to the possible list + scopeManager.currentRecord?.toType()?.let { + bestGuess = it + possibleTypes.add(it) } - // Determine the scope where we want to start our inference - var (scope, _) = scopeManager.extractScope(type) - - if (scope !is NameScope) { - scope = null - } - - var holder = scope?.astNode - - // If we could not find a scope, but we have an FQN, we can try to infer a namespace (or a - // parent record) - var parentName = type.name.parent - if (scope == null && parentName != null) { - // At this point, we need to check whether we have any type reference to our parent - // name. If we have (e.g. it is used in a function parameter, variable, etc.), then we - // have a high chance that this is actually a parent record and not a namespace - var parentType = typeManager.lookupResolvedType(parentName) - holder = - if (parentType != null) { - tryRecordInference(parentType, locationHint = locationHint) - } else { - tryNamespaceInference(parentName, locationHint = locationHint) - } - } - - val record = - (holder ?: this.scopeManager.globalScope?.astNode) - ?.startInference(this) - ?.inferRecordDeclaration(type, kind, locationHint) - - // update the type's record. Because types are only unique per scope, we potentially need to - // update multiple type nodes, i.e., all type nodes whose FQN match the inferred record - if (record != null) { - typeManager.firstOrderTypes - .filter { it.name == record.name } - .forEach { it.recordDeclaration = record } } - return record + return Pair(possibleTypes, bestGuess) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt index b27c5dd7d94..7ceab7561ae 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt @@ -25,7 +25,9 @@ */ package de.fraunhofer.aisec.cpg.passes +import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.TypeManager import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.types.DeclaresType @@ -34,6 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.graph.types.recordDeclaration import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn +import de.fraunhofer.aisec.cpg.passes.inference.tryRecordInference /** * The purpose of this [Pass] is to establish a relationship between [Type] nodes (more specifically @@ -59,65 +62,87 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } - companion object { - context(ContextProvider) - fun resolveType(type: Type): Boolean { - // Let's start by looking up the type according to their name and scope. We exclusively - // filter for nodes that implement DeclaresType, because otherwise we will get a lot of - // constructor declarations and such with the same name. It seems this is ok since most - // languages will prefer structs/classes over functions when resolving types. - var symbols = - ctx?.scopeManager?.lookupSymbolByName(type.name, startScope = type.scope) { - it is DeclaresType - } ?: listOf() - - // We need to have a single match, otherwise we have an ambiguous type and we cannot - // normalize it. - // TODO: Maybe we should have a warning in this case? - var declares = symbols.filterIsInstance().singleOrNull() - - // Check for a possible typedef - var target = ctx?.scopeManager?.typedefFor(type.name, type.scope) - if (target != null) { - if (target.typeOrigin == Type.Origin.UNRESOLVED && type != target) { - // Make sure our typedef target is resolved - resolveType(target) - } - - var originDeclares = target.recordDeclaration - var name = target.name - log.debug("Aliasing type {} in {} scope to {}", type.name, type.scope, name) - type.declaredFrom = originDeclares - type.recordDeclaration = originDeclares - type.typeOrigin = Type.Origin.RESOLVED - return true + /** + * This function tries to "resolve" a [Type] back to the original declaration that declared it + * (see [DeclaresType]). More specifically, it harmonises the type's name to the FQN of the + * declared type and sets the [Type.declaredFrom] (and [ObjectType.recordDeclaration]) property. + * It also sets [Type.typeOrigin] to [Type.Origin.RESOLVED] to mark it as resolved. + * + * The high-level approach looks like the following: + * - First, we check if this type refers to a typedef (see [ScopeManager.typedefFor]). If yes, + * we need to make sure that the target type is resolved and then resolve the type to the + * target type's declaration. + * - If no typedef is used, [ScopeManager.lookupSymbolByName] is used to look up declarations by + * the type's name, starting at its [Type.scope]. Depending on the type, this can be + * unqualified or qualified. We filter exclusively for declarations that implement + * [DeclaresType]. + * - If this yields no declaration, we try to infer a record declaration using + * [tryRecordInference]. + * - Finally, we set the type's name to the resolved type, set [Type.declaredFrom], + * [ObjectType.recordDeclaration], sync [Type.superTypes] with the declaration and set + * [Type.typeOrigin] to [Type.Origin.RESOLVED]. + */ + fun resolveType(type: Type): Boolean { + // Check for a possible typedef + var target = scopeManager.typedefFor(type.name, type.scope) + if (target != null) { + if (target.typeOrigin == Type.Origin.UNRESOLVED && type != target) { + // Make sure our typedef target is resolved + resolveType(target) } - if (declares == null) { - declares = ctx?.tryRecordInference(type, locationHint = type) - } + var originDeclares = target.recordDeclaration + var name = target.name + log.debug("Aliasing type {} in {} scope to {}", type.name, type.scope, name) + type.declaredFrom = originDeclares + type.recordDeclaration = originDeclares + type.typeOrigin = Type.Origin.RESOLVED + return true + } - // If we found the "real" declared type, we can normalize the name of our scoped type - // and - // set the name to the declared type. - if (declares != null) { - var declaredType = declares.declaredType - log.debug( - "Resolving type {} in {} scope to {}", - type.name, - type.scope, - declaredType.name - ) - type.name = declaredType.name - type.declaredFrom = declares - type.recordDeclaration = declares as? RecordDeclaration - type.typeOrigin = Type.Origin.RESOLVED - type.superTypes.addAll(declaredType.superTypes) - return true - } + // Let's start by looking up the type according to their name and scope. We exclusively + // filter for nodes that implement DeclaresType, because otherwise we will get a lot of + // constructor declarations and such with the same name. It seems this is ok since most + // languages will prefer structs/classes over functions when resolving types. + var symbols = + scopeManager + .lookupSymbolByName(type.name, startScope = type.scope) { it is DeclaresType } + .filterIsInstance() + + // We need to have a single match, otherwise we have an ambiguous type, and we cannot + // normalize it. + if (symbols.size > 1) { + log.warn( + "Lookup of type {} returned more than one symbol which declares a type, this is an ambiguity and the following analysis might not be correct.", + name + ) + } + var declares = symbols.singleOrNull() + + // If we did not find any declaration, we can try to infer a record declaration for it + if (declares == null) { + declares = tryRecordInference(type, locationHint = type) + } - return false + // If we found the "real" declared type, we can normalize the name of our scoped type + // and set the name to the declared type. + if (declares != null) { + var declaredType = declares.declaredType + log.debug( + "Resolving type {} in {} scope to {}", + type.name, + type.scope, + declaredType.name + ) + type.name = declaredType.name + type.declaredFrom = declares + type.recordDeclaration = declares as? RecordDeclaration + type.typeOrigin = Type.Origin.RESOLVED + type.superTypes.addAll(declaredType.superTypes) + return true } + + return false } private fun handleNode(node: Node?) { @@ -135,6 +160,7 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { // Nothing to do } + /** Resolves all types in [TypeManager.firstOrderTypes] using [resolveType]. */ fun resolveFirstOrderTypes() { for (type in typeManager.firstOrderTypes.sortedBy { it.name }) { if (type is ObjectType && type.typeOrigin == Type.Origin.UNRESOLVED) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/PassHelper.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/PassHelper.kt new file mode 100644 index 00000000000..da7184e8f5a --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/PassHelper.kt @@ -0,0 +1,332 @@ +/* + * 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.passes.inference + +import de.fraunhofer.aisec.cpg.CallResolutionResult +import de.fraunhofer.aisec.cpg.InferenceConfiguration +import de.fraunhofer.aisec.cpg.frontends.HasGlobalVariables +import de.fraunhofer.aisec.cpg.frontends.HasImplicitReceiver +import de.fraunhofer.aisec.cpg.frontends.HasStructs +import de.fraunhofer.aisec.cpg.frontends.Language +import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.* +import de.fraunhofer.aisec.cpg.graph.newFieldDeclaration +import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope +import de.fraunhofer.aisec.cpg.graph.scopes.NameScope +import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.graph.types.ObjectType +import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.recordDeclaration +import de.fraunhofer.aisec.cpg.graph.unknownType +import de.fraunhofer.aisec.cpg.passes.Pass +import de.fraunhofer.aisec.cpg.passes.Pass.Companion.log +import de.fraunhofer.aisec.cpg.passes.TypeResolver +import de.fraunhofer.aisec.cpg.passes.getPossibleContainingTypes +import de.fraunhofer.aisec.cpg.passes.inference.Inference.TypeInferenceObserver +import kotlin.collections.forEach + +/** + * Tries to infer a [NamespaceDeclaration] from a [Name]. This will return `null`, if inference was + * not possible, or if it was turned off in the [InferenceConfiguration]. + */ +internal fun Pass<*>.tryNamespaceInference(name: Name, locationHint: Node?): NamespaceDeclaration? { + return scopeManager.globalScope + ?.astNode + ?.startInference(this.ctx) + ?.inferNamespaceDeclaration(name, null, locationHint) +} + +/** + * Tries to infer a [RecordDeclaration] from an unresolved [Type]. This will return `null`, if + * inference was not possible, or if it was turned off in the [InferenceConfiguration]. + */ +internal fun Pass<*>.tryRecordInference( + type: Type, + locationHint: Node? = null, +): RecordDeclaration? { + val kind = + if (type.language is HasStructs) { + "struct" + } else { + "class" + } + // Determine the scope where we want to start our inference + var (scope, _) = scopeManager.extractScope(type) + + if (scope !is NameScope) { + scope = null + } + + var holder = scope?.astNode + + // If we could not find a scope, but we have an FQN, we can try to infer a namespace (or a + // parent record) + var parentName = type.name.parent + if (scope == null && parentName != null) { + // At this point, we need to check whether we have any type reference to our parent + // name. If we have (e.g. it is used in a function parameter, variable, etc.), then we + // have a high chance that this is actually a parent record and not a namespace + var parentType = typeManager.lookupResolvedType(parentName) + holder = + if (parentType != null) { + tryRecordInference(parentType, locationHint = locationHint) + } else { + tryNamespaceInference(parentName, locationHint = locationHint) + } + } + + val record = + (holder ?: this.scopeManager.globalScope?.astNode) + ?.startInference(this.ctx) + ?.inferRecordDeclaration(type, kind, locationHint) + + // Update the type's record. Because types are only unique per scope, we potentially need to + // update multiple type nodes, i.e., all type nodes whose FQN match the inferred record. We only + // need to do this if we are NOT in the type resolver + if (this !is TypeResolver && record != null) { + typeManager.firstOrderTypes + .filter { it.name == record.name } + .forEach { it.recordDeclaration = record } + } + + return record +} + +/** + * Tries to infer a [VariableDeclaration] (or [FieldDeclaration]) out of a [Reference]. This will + * return `null`, if inference was not possible, or if it was turned off in the + * [InferenceConfiguration]. + * + * We mainly try to infer global variables and fields here, since these are possibly parts of the + * code we do not "see". We do not try to infer local variables, because we are under the assumption + * that even with incomplete code, we at least have the complete current function code. We can + * therefore differentiate between four scenarios: + * - Inference of a [FieldDeclaration] if we have a language that allows implicit receivers, are + * inside a function and the ref is not qualified. This is then forwarded to [tryFieldInference]. + * - Inference of a top-level [VariableDeclaration] on a namespace level (this is not yet + * implemented) + * - Inference of a global [VariableDeclaration] in the [GlobalScope]. + * - No inference, in any other cases since this would mean that we would infer a local variable. + * This is something we do not want to do see (see above). + */ +internal fun Pass<*>.tryVariableInference( + ref: Reference, +): VariableDeclaration? { + var currentRecordType = scopeManager.currentRecord?.toType() as? ObjectType + return if ( + ref.language is HasImplicitReceiver && + !ref.name.isQualified() && + !ref.isStaticAccess && + currentRecordType != null + ) { + // This could potentially be a reference to a field with an implicit receiver call + tryFieldInference(ref, currentRecordType) + } else if (ref.name.isQualified()) { + // For now, we only infer globals at the top-most global level, i.e., no globals in + // namespaces + val (scope, _) = scopeManager.extractScope(ref, null) + when (scope) { + is NameScope -> { + log.warn( + "We should infer a namespace variable ${ref.name} at this point, but this is not yet implemented." + ) + null + } + else -> { + log.warn( + "We should infer a variable ${ref.name} in ${scope}, but this is not yet implemented." + ) + null + } + } + } else if (ref.language is HasGlobalVariables) { + // We can try to infer a possible global variable (at top-level), if the language + // supports this + scopeManager.globalScope?.astNode?.startInference(this.ctx)?.inferVariableDeclaration(ref) + } else { + // Nothing to infer + null + } +} + +/** + * Tries to infer a [FieldDeclaration] from an unresolved [MemberExpression] or [Reference] (if the + * language has [HasImplicitReceiver]). This will return `null`, if inference was not possible, or + * if it was turned off in the [InferenceConfiguration]. + * + * It will also try to infer a [RecordDeclaration], if [targetType] does not have a declaration. + * However, this is a very special corner-case that will most likely not be triggered, since the + * majority of types will have their declaration inferred in the [TypeResolver] already before we + * reach this step here. This should actually only happen in one case: If we try to infer a field of + * a type that is registered in [Language.builtInTypes] (e.g. `std::string` for C++). In this case, + * the record for this type is NOT inferred in the type resolver, because we intentionally wait + * until the symbol resolver, in case we really "see" the record, e.g., if we parse the std headers. + * If we did not "see" its declaration, we can infer it now. + */ +internal fun Pass<*>.tryFieldInference( + ref: Reference, + targetType: ObjectType +): VariableDeclaration? { + // We only want to infer fields here, this can either happen if we have a reference with an + // implicit receiver or if we have a scoped reference and the scope points to a record + val (scope, _) = scopeManager.extractScope(ref) + if (scope != null && scope !is RecordScope) { + return null + } + + var record = targetType.recordDeclaration + // We access an unknown field of an unknown record. so we need to handle that along the + // way as well. + if (record == null) { + record = tryRecordInference(targetType, locationHint = ref) + } + + if (record == null) { + log.error( + "There is no matching record in the record map. Can't identify which field is used." + ) + return null + } + + val declaration = + ref.newFieldDeclaration( + ref.name.localName, + // we will set the type later through the type inference observer + record.unknownType(), + listOf(), + null, + false, + ) + record.addField(declaration) + declaration.language = record.language + declaration.isInferred = true + + // We might be able to resolve the type later (or better), if a type is + // assigned to our reference later + ref.registerTypeObserver(TypeInferenceObserver(declaration)) + + return declaration +} + +/** + * Tries to infer a [FunctionDeclaration] or a [MethodDeclaration] from a [CallExpression]. This + * will return an empty list, if inference was not possible, or if it was turned off in the + * [InferenceConfiguration]. + * + * Depending on several factors, e.g., whether the callee has an FQN, was a [MemberExpression] or + * whether the language supports [HasImplicitReceiver] we either infer + * - a global [FunctionDeclaration] + * - a [FunctionDeclaration] in a namespace + * - a [MethodDeclaration] in a record using [tryMethodInference] + * + * Since potentially multiple suitable bases exist for the inference of methods (derived by + * [getPossibleContainingTypes]), we infer a method for all of them and return a list. + */ +internal fun Pass<*>.tryFunctionInference( + call: CallExpression, + result: CallResolutionResult, +): List { + // We need to see, whether we have any suitable base (e.g. a class) or not; There are two + // main cases + // a) we have a member expression -> easy + // b) we have a call expression -> not so easy. This could be a member call with an implicit + // this (in which case we want to explore the base type). But that is only possible if + // the callee is not qualified, because otherwise we are in a static call like + // MyClass::doSomething() or in a namespace call (in case we do not want to explore the + // base type here yet). This will change in a future PR. + val (suitableBases, bestGuess) = + if ( + call.callee is MemberExpression || + !call.callee.name.isQualified() && call.language is HasImplicitReceiver + ) { + getPossibleContainingTypes(call) + } else { + Pair(setOf(), null) + } + + return if (suitableBases.isEmpty()) { + // While this is definitely a function, it could still be a function + // inside a namespace. We therefore have two possible start points, a namespace + // declaration or a translation unit. Nothing else is allowed (for now). We can + // re-use the information in the ResolutionResult, since this already contains the + // actual start scope (e.g. in case the callee has an FQN). + var scope = result.actualStartScope + if (scope !is NameScope) { + scope = scopeManager.globalScope + } + val func = + when (val start = scope?.astNode) { + is TranslationUnitDeclaration -> start.inferFunction(call, ctx = this.ctx) + is NamespaceDeclaration -> start.inferFunction(call, ctx = this.ctx) + else -> null + } + listOfNotNull(func) + } else { + tryMethodInference(call, suitableBases, bestGuess) + } +} + +/** + * Tries to infer a [MethodDeclaration] from a [CallExpression]. This will return an empty list, if + * inference was not possible, or if it was turned off in the [InferenceConfiguration]. + * + * Since potentially multiple suitable bases exist for the inference of methods (specified in + * [possibleContainingTypes]), we infer a method for all of them and return a list. + * + * Should we encounter that none of our types in [possibleContainingTypes] have a resolved + * declaration, we are inferring one (using [bestGuess]). This should normally not happen as missing + * type declarations are already inferred in the [TypeResolver]. However, there is a special + * corner-case involving types in [Language.builtInTypes] (see [tryFieldInference] for more + * details), + */ +internal fun Pass<*>.tryMethodInference( + call: CallExpression, + possibleContainingTypes: Set, + bestGuess: Type?, +): List { + var records = + possibleContainingTypes.mapNotNull { + val root = it.root as? ObjectType + root?.recordDeclaration + } + + // We access an unknown method of an unknown record. so we need to handle that along the way as + // well. We prefer the base type. This should only happen on types that are "built-in", as all + // other type declarations are already inferred by the type resolver at this stage. + if (records.isEmpty()) { + records = + listOfNotNull( + tryRecordInference(bestGuess?.root ?: call.unknownType(), locationHint = call) + ) + } + records = records.distinct() + + return records.mapNotNull { record -> record.inferMethod(call, ctx = this.ctx) } +} diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PythonAddDeclarationsPass.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PythonAddDeclarationsPass.kt index 100791c74a2..9d3992557f2 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PythonAddDeclarationsPass.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/PythonAddDeclarationsPass.kt @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression @@ -102,22 +103,17 @@ class PythonAddDeclarationsPass(ctx: TranslationContext) : ComponentPass(ctx) { (scopeManager.currentFunction as? MethodDeclaration)?.receiver?.name ) { // We need to temporarily jump into the scope of the current record to - // add the field - val field = - scopeManager.withScope(scopeManager.currentRecord?.scope) { - newFieldDeclaration(node.name) - } - field + // add the field. These are instance attributes + scopeManager.withScope( + scopeManager.firstScopeIsInstanceOrNull() + ) { + newFieldDeclaration(node.name) + } } else { - val v = newVariableDeclaration(node.name) - v + newVariableDeclaration(node.name) } } else { - val field = - scopeManager.withScope(scopeManager.currentRecord?.scope) { - newFieldDeclaration(node.name) - } - field + newFieldDeclaration(node.name) } } else { newVariableDeclaration(node.name) @@ -127,14 +123,8 @@ class PythonAddDeclarationsPass(ctx: TranslationContext) : ComponentPass(ctx) { decl.location = node.location decl.isImplicit = true - if (decl is FieldDeclaration) { - scopeManager.currentRecord?.addField(decl) - scopeManager.withScope(scopeManager.currentRecord?.scope) { - scopeManager.addDeclaration(decl) - } - } else { - scopeManager.addDeclaration(decl) - } + scopeManager.withScope(decl.scope) { scopeManager.addDeclaration(decl) } + return decl } diff --git a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt index 17f8b0855d3..f68284a5de1 100644 --- a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt +++ b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt @@ -1336,10 +1336,8 @@ class PythonFrontendTest : BaseTest() { it.registerLanguage() } assertNotNull(result) - assertEquals(2, result.variables.size) - // Note, that "pi" is incorrectly inferred as a field declaration. This is a known bug in - // the inference system (and not in the python module) and will be handled separately. - assertEquals(listOf("mypi", "pi"), result.variables.map { it.name.localName }) + assertEquals(1, result.variables.size) + assertEquals(listOf("mypi"), result.variables.map { it.name.localName }) } @Test diff --git a/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt b/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt index 0ac64f07ca1..b553cd7f9b4 100644 --- a/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt +++ b/cpg-language-typescript/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/typescript/TypescriptLanguageFrontendTest.kt @@ -262,7 +262,7 @@ class TypeScriptLanguageFrontendTest { assertLocalName("Users", usersComponent) assertEquals(1, usersComponent.constructors.size) assertEquals(2, usersComponent.methods.size) - assertEquals(/*0*/ 2 /* because of dummy nodes */, usersComponent.fields.size) + assertEquals(0, usersComponent.fields.size) val render = usersComponent.methods["render"] assertNotNull(render) From 9755f7193134493bfe6e982c6e170ae89da1f242 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sun, 6 Oct 2024 18:18:21 +0200 Subject: [PATCH 16/30] Fixed crash in `getCodeOfSubregion` (#1776) --- .../aisec/cpg/helpers/RegionUtils.kt | 8 +++++- .../frontends/python/PythonFrontendTest.kt | 27 +++++++++++++++++++ .../src/test/resources/python/unicode.py | 5 ++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 cpg-language-python/src/test/resources/python/unicode.py 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 279f3f63fab..e43c3d35c9f 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 @@ -27,6 +27,7 @@ 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 /** @@ -62,12 +63,17 @@ fun getCodeOfSubregion(code: String, nodeRegion: Region, subRegion: Region): Str (StringUtils.ordinalIndexOf(code, nlType, subRegion.startLine - nodeRegion.startLine) + subRegion.startColumn) } - val end = + var end = if (subRegion.endLine == nodeRegion.startLine) { subRegion.endColumn - nodeRegion.startColumn } else { (StringUtils.ordinalIndexOf(code, nlType, subRegion.endLine - nodeRegion.startLine) + subRegion.endColumn) } + + // Unfortunately, we sometimes have issues with (non)-Unicode characters in code, where the + // python AST thinks that multiple characters are needed and reports a position that is actually + // beyond our "end" + end = min(end, code.length) return code.substring(start, end) } diff --git a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt index f68284a5de1..db35a4063da 100644 --- a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt +++ b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonFrontendTest.kt @@ -1453,6 +1453,33 @@ class PythonFrontendTest : BaseTest() { assertEquals(4.toLong(), rhs.evaluate()) } + @Test + fun testParseWithUnicode() { + val topLevel = Path.of("src", "test", "resources", "python") + val tu = + analyzeAndGetFirstTU(listOf(topLevel.resolve("unicode.py").toFile()), topLevel, true) { + it.registerLanguage() + } + assertNotNull(tu) + + val normalFunc = tu.functions["normal_func"] + assertNotNull(normalFunc) + // 11 chars (including whitespace) -> SARIF position = 12 + // e = "e" + assertEquals(12, normalFunc.body?.location?.region?.endColumn) + + val unicodeFunc = tu.functions["unicode_func"] + assertNotNull(unicodeFunc) + + // also 11 chars (including whitespace) -> SARIF position = 12 + // But the python parser somehow sees these as two bytes so the position is 13 :( + // e = "é" + assertEquals(13, unicodeFunc.body?.location?.region?.endColumn) + + // So the code exceeds the line, but we clamp it and avoid a crash + assertEquals("e = \"é\"", unicodeFunc.body?.code) + } + class PythonValueEvaluator : ValueEvaluator() { override fun computeBinaryOpEffect( lhsValue: Any?, diff --git a/cpg-language-python/src/test/resources/python/unicode.py b/cpg-language-python/src/test/resources/python/unicode.py new file mode 100644 index 00000000000..73d35ff23a7 --- /dev/null +++ b/cpg-language-python/src/test/resources/python/unicode.py @@ -0,0 +1,5 @@ +def normal_func(): + e = "e" + +def unicode_func(): + e = "é" From a69f98a4e4c1c8d536ba7fa64d6c2f5b57e21322 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sun, 6 Oct 2024 19:19:07 +0200 Subject: [PATCH 17/30] Add new function `lookupUniqueTypeSymbolByName` (#1781) * Add new function `lookupUniqueTypeSymbolByName` This adds two new functions `ScopeManager.lookupUniqueTypeSymbolByName` and `Reference.doesReferToType`. This harmonizes a lot of boilerplate code in type resolver, cxx extra pass and resolve ambuigity pass, which were all trying to achieve the same thing. Fixes #1766 * Fixed issue with Go test, more robust handling of wrapped references * Addressed code review --- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 25 ++++++++ .../de/fraunhofer/aisec/cpg/TypeManager.kt | 29 +++++++++ .../ResolveCallExpressionAmbiguityPass.kt | 60 +++++-------------- .../aisec/cpg/passes/TypeResolver.kt | 15 +---- .../aisec/cpg/passes/CXXExtraPass.kt | 23 +++---- .../cpg/frontends/cxx/CXXExpressionTest.kt | 2 +- .../aisec/cpg/passes/GoExtraPass.kt | 14 ----- 7 files changed, 84 insertions(+), 84 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt index f45809b3a5d..104fdfed57c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.* import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.graph.types.DeclaresType import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.graph.types.IncompleteType import de.fraunhofer.aisec.cpg.graph.types.Type @@ -1009,6 +1010,30 @@ class ScopeManager : ScopeProvider { return list } + + /** + * This function tries to look up the symbol contained in [name] (using [lookupSymbolByName]) + * and returns a [DeclaresType] node, if this name resolved to something which declares a type. + * + * It is important to know that the lookup needs to be unique, so if multiple declarations match + * this symbol, a warning is triggered and null is returned. + */ + fun lookupUniqueTypeSymbolByName(name: Name, startScope: Scope?): DeclaresType? { + var symbols = + lookupSymbolByName(name = name, startScope = startScope) { it is DeclaresType } + .filterIsInstance() + + // We need to have a single match, otherwise we have an ambiguous type, and we cannot + // normalize it. + if (symbols.size > 1) { + LOGGER.warn( + "Lookup of type {} returned more than one symbol which declares a type, this is an ambiguity and the following analysis might not be correct.", + name + ) + } + + return symbols.singleOrNull() + } } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt index 8745e7944d8..3069f741952 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/TypeManager.kt @@ -32,7 +32,10 @@ import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TemplateDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.scopes.TemplateScope +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.passes.Pass +import de.fraunhofer.aisec.cpg.passes.ResolveCallExpressionAmbiguityPass import java.util.* import java.util.concurrent.ConcurrentHashMap import org.slf4j.Logger @@ -353,3 +356,29 @@ val Collection.commonType: Type? // root node is 0) and re-wrap the final common type back into the original wrap state return commonAncestors.minByOrNull(Type.Ancestor::depth)?.type?.let { typeOp.apply(it) } } + +/** + * A utility function that checks whether our [Reference] refers to a [Type]. This is used by many + * passes that replace certain [Reference] nodes with other nodes, e.g., the + * [ResolveCallExpressionAmbiguityPass]. + * + * Note: This involves some symbol lookup (using [ScopeManager.lookupUniqueTypeSymbolByName]), so + * this can only be used in passes. + */ +context(Pass<*>) +fun Reference.nameIsType(): Type? { + // First, check if it is a simple type + var type = language?.getSimpleTypeOf(name) + if (type != null) { + return type + } + + // This could also be a typedef + type = scopeManager.typedefFor(name, scope) + if (type != null) { + return type + } + + // Lastly, check if the reference contains a symbol that points to type (declaration) + return scopeManager.lookupUniqueTypeSymbolByName(name, scope)?.declaredType +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ResolveCallExpressionAmbiguityPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ResolveCallExpressionAmbiguityPass.kt index 42f5ec7c8bd..ecd614701e2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ResolveCallExpressionAmbiguityPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ResolveCallExpressionAmbiguityPass.kt @@ -40,6 +40,7 @@ import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.replace +import de.fraunhofer.aisec.cpg.nameIsType import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore import de.fraunhofer.aisec.cpg.passes.configuration.RequiresLanguageTrait @@ -85,27 +86,20 @@ class ResolveCallExpressionAmbiguityPass(ctx: TranslationContext) : TranslationU var callee = call.callee val language = callee.language + // We need to "unwrap" some references because they might be nested in unary operations such + // as pointers. We are interested in the references in the "core". We can skip all + // references that are member expressions + val ref = callee.unwrapReference() + if (ref == null || ref is MemberExpression) { + return + } + // Check, if this is cast is really a construct expression (if the language supports // functional-constructs) if (language is HasFunctionStyleConstruction) { - // Make sure, we do not accidentally "construct" primitive types - if (language.builtInTypes.contains(callee.name.toString()) == true) { - return - } - - val fqn = - if (callee.name.parent == null) { - scopeManager.currentNamespace.fqn( - callee.name.localName, - delimiter = callee.name.delimiter - ) - } else { - callee.name - } - // Check for our type. We are only interested in object types - val type = typeManager.lookupResolvedType(fqn) - if (type is ObjectType) { + var type = ref.nameIsType() + if (type is ObjectType && !type.isPrimitive) { walker.replaceCallWithConstruct(type, parent, call) } } @@ -114,34 +108,10 @@ class ResolveCallExpressionAmbiguityPass(ctx: TranslationContext) : TranslationU // cast expression. And this is only really necessary, if the function call has a single // argument. if (language is HasFunctionStyleCasts && call.arguments.size == 1) { - var pointer = false - // If the argument is a UnaryOperator, unwrap them - if (callee is UnaryOperator && callee.operatorCode == "*") { - pointer = true - callee = callee.input - } - - // First, check if this is a built-in type - var builtInType = language.getSimpleTypeOf(callee.name) - if (builtInType != null) { - walker.replaceCallWithCast(builtInType, parent, call, false) - } else { - // If not, then this could still refer to an existing type. We need to make sure - // that we take the current namespace into account - val fqn = - if (callee.name.parent == null) { - scopeManager.currentNamespace.fqn( - callee.name.localName, - delimiter = callee.name.delimiter - ) - } else { - callee.name - } - - val type = typeManager.lookupResolvedType(fqn) - if (type != null) { - walker.replaceCallWithCast(type, parent, call, pointer) - } + // Check if it is type and replace the call + var type = ref.nameIsType() + if (type != null) { + walker.replaceCallWithCast(type, parent, call, false) } } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt index 7ceab7561ae..e4345e98d2d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/TypeResolver.kt @@ -104,20 +104,7 @@ open class TypeResolver(ctx: TranslationContext) : ComponentPass(ctx) { // filter for nodes that implement DeclaresType, because otherwise we will get a lot of // constructor declarations and such with the same name. It seems this is ok since most // languages will prefer structs/classes over functions when resolving types. - var symbols = - scopeManager - .lookupSymbolByName(type.name, startScope = type.scope) { it is DeclaresType } - .filterIsInstance() - - // We need to have a single match, otherwise we have an ambiguous type, and we cannot - // normalize it. - if (symbols.size > 1) { - log.warn( - "Lookup of type {} returned more than one symbol which declares a type, this is an ambiguity and the following analysis might not be correct.", - name - ) - } - var declares = symbols.singleOrNull() + var declares = scopeManager.lookupUniqueTypeSymbolByName(type.name, type.scope) // If we did not find any declaration, we can try to infer a record declaration for it if (declares == null) { diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXExtraPass.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXExtraPass.kt index b6eeae13657..0bd84d65fec 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXExtraPass.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/CXXExtraPass.kt @@ -36,6 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.recordDeclaration import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.replace +import de.fraunhofer.aisec.cpg.nameIsType import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn import de.fraunhofer.aisec.cpg.passes.configuration.ExecuteBefore @@ -77,7 +78,8 @@ class CXXExtraPass(ctx: TranslationContext) : ComponentPass(ctx) { * the graph. */ private fun removeBracketOperators(node: UnaryOperator, parent: Node?) { - if (node.operatorCode == "()" && !typeManager.typeExists(node.input.name)) { + val input = node.input + if (node.operatorCode == "()" && input is Reference && input.nameIsType() == null) { // It was really just parenthesis around an identifier, but we can only make this // distinction now. // @@ -101,24 +103,25 @@ class CXXExtraPass(ctx: TranslationContext) : ComponentPass(ctx) { private fun convertOperators(binOp: BinaryOperator, parent: Node?) { val fakeUnaryOp = binOp.lhs val language = fakeUnaryOp.language as? CLanguage + // We need to check, if the expression in parentheses is really referring to a type or // not. A common example is code like `(long) &addr`. We could end up parsing this as a // binary operator with the left-hand side of `(long)`, an operator code `&` and a rhs // of `addr`. - if ( - language != null && - fakeUnaryOp is UnaryOperator && - fakeUnaryOp.operatorCode == "()" && - typeManager.typeExists(fakeUnaryOp.input.name) - ) { - // If the name (`long` in the example) is a type, then the unary operator (`(long)`) - // is really a cast and our binary operator is really a unary operator `&addr`. + if (language == null || fakeUnaryOp !is UnaryOperator || fakeUnaryOp.operatorCode != "()") { + return + } + + // If the name (`long` in the example) is a type, then the unary operator (`(long)`) + // is really a cast and our binary operator is really a unary operator `&addr`. + var type = (fakeUnaryOp.input as? Reference)?.nameIsType() + if (type != null) { // We need to perform the following steps: // * create a cast expression out of the ()-unary operator, with the type that is // referred to in the op. val cast = newCastExpression().codeAndLocationFrom(fakeUnaryOp) cast.language = language - cast.castType = fakeUnaryOp.objectType(fakeUnaryOp.input.name) + cast.castType = type // * create a unary operator with the rhs of the binary operator (and the same // operator code). diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXExpressionTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXExpressionTest.kt index aa6d6ee2927..0b5276252a4 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXExpressionTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXExpressionTest.kt @@ -45,6 +45,6 @@ class CXXExpressionTest { // We should have two calls (int and myint64) val casts = tu.casts assertEquals(2, casts.size) - assertEquals(listOf("int", "myint64"), casts.map { it.name.localName }) + assertEquals(listOf("int", "long long int"), casts.map { it.name.localName }) } } diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt index 5297e13826a..77e3cc32ac4 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/GoExtraPass.kt @@ -81,20 +81,6 @@ import de.fraunhofer.aisec.cpg.passes.inference.startInference * In the frontend we only do the assignment, therefore we need to create a new * [VariableDeclaration] for `b` and inject a [DeclarationStatement]. * - * ## Converting Call Expressions into Cast Expressions - * - * In Go, it is possible to convert compatible types by "calling" the type name as a function, such - * as - * - * ```go - * var i = int(2.0) - * ``` - * - * This is also possible with more complex types, such as interfaces or aliased types, as long as - * they are compatible. Because types in the same package can be defined in multiple files, we - * cannot decide during the frontend run. Therefore, we need to execute this pass before the - * [SymbolResolver] and convert certain [CallExpression] nodes into a [CastExpression]. - * * ## Adjust Names of Keys in Key Value Expressions to FQN * * This pass also adjusts the names of keys in a [KeyValueExpression], which is part of an From b358fa15db19cafe56c48743a406820c35a9691b Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Mon, 7 Oct 2024 11:20:46 +0200 Subject: [PATCH 18/30] Make sure to move `typeObservers` from old to new node when replacing nodes (#1783) * Make sure to move `typeObservers` from old to new node when replacing nodes * Added doc for typeobservers --- .../de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt index e5821b00d7e..6aded16eb70 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/SubgraphWalker.kt @@ -35,6 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdge import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeCollection import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.passes.Pass import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import java.lang.annotation.AnnotationFormatError @@ -349,13 +350,14 @@ object SubgraphWalker { /** * Tries to replace the [old] expression with a [new] one, given the [parent]. * - * There are two things to consider: + * There are three things to consider: * - First, this only works if [parent] is either an [ArgumentHolder] or [StatementHolder]. * Otherwise, we cannot instruct the parent to exchange the node * - Second, since exchanging the node has influence on their edges (such as EOG, DFG, etc.), we * only support a replacement very early in the pass system. To be specific, we only allow * replacement before any DFG edges are set. We are re-wiring EOG edges, but nothing else. If one * tries to replace a node with existing [Node.nextDFG] or [Node.prevDFG], we fail. + * - We also migrate [HasType.typeObservers] from the [old] to the [new] node. */ context(ContextProvider) fun SubgraphWalker.ScopedWalker.replace(parent: Node?, old: Expression, new: Expression): Boolean { @@ -391,6 +393,12 @@ fun SubgraphWalker.ScopedWalker.replace(parent: Node?, old: Expression, new: Exp new.prevEOG = oldPrevEOG new.nextEOG = oldNextEOG + // Also move over any type observers + old.typeObservers.forEach { + old.unregisterTypeObserver(it) + new.registerTypeObserver(it) + } + // Make sure to inform the walker about our change this.registerReplacement(old, new) } From 5e83741ffda99d379ef3dd80f4e174efd2bbed0e Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Mon, 7 Oct 2024 16:15:44 +0200 Subject: [PATCH 19/30] `implicit()` only triggers code/location update now if its not empty (#1784) Otherwise, we override the code/location again. --- .../kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 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 cf7e12e01ee..ffa70363dfe 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 @@ -256,8 +256,12 @@ interface RawNodeTypeProvider : MetadataProvider * This also sets [Node.isImplicit] to true. */ fun T.implicit(code: String? = null, location: PhysicalLocation? = null): T { - this.code = code - this.location = location + if (code != null) { + this.code = code + } + if (location != null) { + this.location = location + } this.isImplicit = true return this From 205a275600343cde452609854219ed7d57cbc442 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 8 Oct 2024 11:05:10 +0200 Subject: [PATCH 20/30] Added `:=` as simple operator in Python (#1785) Named expressions in Python use `:=` as operator. Therefore we need to include it in the language definition. Otherwise, the `access` value of a reference will not be set correctly. --- .../aisec/cpg/frontends/python/PythonLanguage.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt index 49fd56726f0..a0526f0255c 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguage.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.autoType import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration import de.fraunhofer.aisec.cpg.graph.scopes.Symbol import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.graph.types.* import kotlin.reflect.KClass @@ -49,6 +50,14 @@ class PythonLanguage : override val conjunctiveOperators = listOf("and") override val disjunctiveOperators = listOf("or") + /** + * You can either use `=` or `:=` in Python. But the latter is only available in a "named + * expression" (`a = (x := 1)`). We still need to include both however, otherwise + * [Reference.access] will not be set correctly in "named expressions". + */ + override val simpleAssignmentOperators: Set + get() = setOf("=", ":=") + /** * All operators which perform and assignment and an operation using lhs and rhs. See * https://docs.python.org/3/library/operator.html#in-place-operators From 89fd8ecfd02450c2356eda3e3aac37518d10f9ff Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Tue, 8 Oct 2024 11:24:54 +0200 Subject: [PATCH 21/30] code review --- .../de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt index 3eb158d35c0..490e30cebaa 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph.edges.flows import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.body import de.fraunhofer.aisec.cpg.graph.builder.call import de.fraunhofer.aisec.cpg.graph.builder.declare @@ -135,7 +136,7 @@ class DataflowTest { } // Let's assert that we did this correctly - val main = result.functions[0] // TODO: why can't I use "foo" like with the other tests? + val main = result.functions[0] assertNotNull(main) val body = main.body assertIs(body) From 3d4c64b3b573eeceddd0df709bf7a18b24435831 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Tue, 8 Oct 2024 11:27:22 +0200 Subject: [PATCH 22/30] code review --- .../de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt index 490e30cebaa..ff88a3e6ec8 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt @@ -26,12 +26,12 @@ package de.fraunhofer.aisec.cpg.graph.edges.flows import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend -import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.body import de.fraunhofer.aisec.cpg.graph.builder.call import de.fraunhofer.aisec.cpg.graph.builder.declare import de.fraunhofer.aisec.cpg.graph.builder.function import de.fraunhofer.aisec.cpg.graph.builder.literal +import de.fraunhofer.aisec.cpg.graph.builder.ref import de.fraunhofer.aisec.cpg.graph.builder.t import de.fraunhofer.aisec.cpg.graph.builder.`throw` import de.fraunhofer.aisec.cpg.graph.builder.translationResult @@ -128,7 +128,7 @@ class DataflowTest { function("foo", t("void")) { body { declare { variable("a", t("short")) { literal(42) } } - `throw` { call("SomeError") { /* TODO parameter a */} } + `throw` { call("SomeError") { ref("a") } } } } } From ebe1a27694495f2544d6d207d15b68df17ba064b Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Tue, 8 Oct 2024 11:32:18 +0200 Subject: [PATCH 23/30] code review --- .../cpg/graph/statements/ThrowStatement.kt | 6 +---- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 24 ++++--------------- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt index 4ed19e77830..bf1fdc2bb7f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt @@ -70,11 +70,7 @@ class ThrowStatement : Statement(), ArgumentHolder { } override fun hasArgument(expression: Expression): Boolean { - return when { - exception == expression -> true - cause == expression -> true - else -> false - } + return exception == expression || cause == expression } override fun equals(other: Any?): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 7296ca8fcbd..414867b505b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -130,7 +130,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { is ForStatement -> handleForStatement(node) is SwitchStatement -> handleSwitchStatement(node) is IfStatement -> handleIfStatement(node) - is ThrowStatement -> handleThrowStatement(node, inferDfgForUnresolvedSymbols) + is ThrowStatement -> handleThrowStatement(node) // Declarations is FieldDeclaration -> handleFieldDeclaration(node) is FunctionDeclaration -> handleFunctionDeclaration(node, functionSummaries) @@ -145,25 +145,9 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { * - [CallExpression] * - [Reference] */ - protected fun handleThrowStatement( - node: ThrowStatement, - inferDfgForUnresolvedSymbols: Boolean - ) { - node.exception?.let { exc -> - when (exc) { - is CallExpression -> { - handleCallExpression(exc, inferDfgForUnresolvedSymbols) - } - is Reference -> { - handleReference(exc) - } - else -> { - log.warn( - "handleThrowStatement: Received unexpected exception Type: ${exc.javaClass.simpleName}" - ) - } - } - } + protected fun handleThrowStatement(node: ThrowStatement) { + node.exception?.let { node.prevDFGEdges += it } + node.cause?.let { node.prevDFGEdges += it } } protected fun handleAssignExpression(node: AssignExpression) { From 222408d73b3db4b833c93aeea3afd2b8b5203507 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Thu, 10 Oct 2024 14:34:26 +0200 Subject: [PATCH 24/30] rename cause to parentException --- .../cpg/graph/statements/ThrowStatement.kt | 27 ++++++++++--------- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt index bf1fdc2bb7f..b2fcae6786c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt @@ -40,18 +40,19 @@ class ThrowStatement : Statement(), ArgumentHolder { var exception by unwrapping(ThrowStatement::exceptionEdge) /** - * Some languages (Python) can add a cause to indicate that an exception was raised while - * handling another exception. This is stored in the graph, but has no further implications like - * EOG or DFG connections, as it is only of informational purpose, but it doesn't change the - * program behavior. + * Some languages (Python) can add a parent exception (or `cause`) to indicate that an exception + * was raised while handling another exception. This is stored in the graph, but has no further + * implications like EOG or DFG connections, as it is only of informational purpose, but it + * doesn't change the program behavior. */ - @Relationship(value = "CAUSE") var causeEdge = astOptionalEdgeOf() - var cause by unwrapping(ThrowStatement::causeEdge) + @Relationship(value = "PARENT_EXCEPTION") + var parentExceptionEdge = astOptionalEdgeOf() + var parentException by unwrapping(ThrowStatement::parentExceptionEdge) override fun addArgument(expression: Expression) { when { exception == null -> exception = expression - cause == null -> cause = expression + parentException == null -> parentException = expression } } @@ -61,8 +62,8 @@ class ThrowStatement : Statement(), ArgumentHolder { exception = new true } - cause == old -> { - cause = new + parentException == old -> { + parentException = new true } else -> false @@ -70,14 +71,16 @@ class ThrowStatement : Statement(), ArgumentHolder { } override fun hasArgument(expression: Expression): Boolean { - return exception == expression || cause == expression + return exception == expression || parentException == expression } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ThrowStatement) return false - return super.equals(other) && exception == other.exception && cause == other.cause + return super.equals(other) && + exception == other.exception && + parentException == other.parentException } - override fun hashCode() = Objects.hash(super.hashCode(), exception, cause) + override fun hashCode() = Objects.hash(super.hashCode(), exception, parentException) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 414867b505b..5607826b71e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -147,7 +147,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { */ protected fun handleThrowStatement(node: ThrowStatement) { node.exception?.let { node.prevDFGEdges += it } - node.cause?.let { node.prevDFGEdges += it } + node.parentException?.let { node.prevDFGEdges += it } } protected fun handleAssignExpression(node: AssignExpression) { From 1b10c3d2445df4e20166da77796f1ad5d57de3eb Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Thu, 10 Oct 2024 14:36:27 +0200 Subject: [PATCH 25/30] doc --- .../fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 69559e67200..555665c0173 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -1033,10 +1033,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(stmt) } - /* - TODO: doc - Copy & paste from [handleThrowOperator] - */ + /** This is copied & pasted from [handleThrowOperator]. TODO: To be merged in a later PR. */ protected fun handleThrowStatement(statement: ThrowStatement) { val input = statement.exception createEOG(input) From 7df6417981381480c907beda155d03ccbfc4d2e0 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Thu, 10 Oct 2024 14:47:41 +0200 Subject: [PATCH 26/30] ThrowStatement: add toString --- .../aisec/cpg/graph/statements/ThrowStatement.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt index b2fcae6786c..ce997117d30 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import java.util.Objects +import org.apache.commons.lang3.builder.ToStringBuilder import org.neo4j.ogm.annotation.Relationship /** Represents a `throw` or `raise` statement. */ @@ -83,4 +84,12 @@ class ThrowStatement : Statement(), ArgumentHolder { } override fun hashCode() = Objects.hash(super.hashCode(), exception, parentException) + + override fun toString(): String { + return ToStringBuilder(this, TO_STRING_STYLE) + .appendSuper(super.toString()) + .append("exception", exception) + .append("parentException", parentException) + .toString() + } } From 2990cc48a6f95dc28d1fb9923df295eed9056c80 Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Thu, 10 Oct 2024 15:06:43 +0200 Subject: [PATCH 27/30] fix tests --- .../cpg/passes/EvaluationOrderGraphPass.kt | 6 +++- .../de/fraunhofer/aisec/cpg/GraphExamples.kt | 20 +++++++++++ .../cpg/graph/edges/flows/DataflowTest.kt | 34 ++++--------------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 555665c0173..8239377af72 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -1033,10 +1033,14 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(stmt) } - /** This is copied & pasted from [handleThrowOperator]. TODO: To be merged in a later PR. */ + /** + * This is copied & pasted with minimal adjustments from [handleThrowOperator]. TODO: To be + * merged in a later PR. + */ protected fun handleThrowStatement(statement: ThrowStatement) { val input = statement.exception createEOG(input) + statement.parentException?.let { createEOG(it) } val catchingScope = scopeManager.firstScopeOrNull { scope -> scope is TryScope || scope is FunctionScope } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt index e4e55d1d924..db0679a64d8 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt @@ -1342,5 +1342,25 @@ class GraphExamples { } } } + + fun prepareThrowDFGTest( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + testFrontend(config).build { + translationResult { + translationUnit("some.file") { + function("foo", t("void")) { + body { + declare { variable("a", t("short")) { literal(42) } } + `throw` { call("SomeError") { ref("a") } } + } + } + } + } + } } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt index ff88a3e6ec8..28eda4c9b84 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt @@ -25,21 +25,9 @@ */ package de.fraunhofer.aisec.cpg.graph.edges.flows +import de.fraunhofer.aisec.cpg.GraphExamples.Companion.prepareThrowDFGTest import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend -import de.fraunhofer.aisec.cpg.graph.builder.body -import de.fraunhofer.aisec.cpg.graph.builder.call -import de.fraunhofer.aisec.cpg.graph.builder.declare -import de.fraunhofer.aisec.cpg.graph.builder.function -import de.fraunhofer.aisec.cpg.graph.builder.literal -import de.fraunhofer.aisec.cpg.graph.builder.ref -import de.fraunhofer.aisec.cpg.graph.builder.t -import de.fraunhofer.aisec.cpg.graph.builder.`throw` -import de.fraunhofer.aisec.cpg.graph.builder.translationResult -import de.fraunhofer.aisec.cpg.graph.builder.translationUnit -import de.fraunhofer.aisec.cpg.graph.builder.variable -import de.fraunhofer.aisec.cpg.graph.functions -import de.fraunhofer.aisec.cpg.graph.newLiteral -import de.fraunhofer.aisec.cpg.graph.newReference +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.ThrowStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression @@ -121,19 +109,7 @@ class DataflowTest { @Test fun testThrow() { - val result = - TestLanguageFrontend().build { - translationResult { - translationUnit("some.file") { - function("foo", t("void")) { - body { - declare { variable("a", t("short")) { literal(42) } } - `throw` { call("SomeError") { ref("a") } } - } - } - } - } - } + val result = prepareThrowDFGTest() // Let's assert that we did this correctly val main = result.functions[0] @@ -147,6 +123,8 @@ class DataflowTest { val throwCall = throwStmt.exception assertIs(throwCall) - // TODO dataflow var to call + val someError = result.calls["SomeError"] + assertIs(someError) + assertContains(throwStmt.prevDFG, someError) } } From 825ad28b0473274fff2671a790d1c8484742e8cd Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 18 Oct 2024 08:07:32 +0200 Subject: [PATCH 28/30] Merge EOG, add tests --- .../cpg/passes/EvaluationOrderGraphPass.kt | 55 +++++++------------ .../aisec/cpg/graph/ThrowStatementTest.kt | 53 ++++++++++++++---- 2 files changed, 62 insertions(+), 46 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 8239377af72..b5a3a07e06e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -547,27 +547,30 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa // TODO(oxisto): These operator codes are highly language specific and might be more suited // to be handled differently (see https://github.com/Fraunhofer-AISEC/cpg/issues/1161) if (node.operatorCode == "throw") { - handleThrowOperator(node) + handleThrowOperator(node, node.input) } else { handleUnspecificUnaryOperator(node) } } - protected fun handleThrowOperator(node: UnaryOperator) { - val input = node.input - createEOG(input) - - val catchingScope = - scopeManager.firstScopeOrNull { scope -> scope is TryScope || scope is FunctionScope } - - val throwType = input.type + protected fun handleThrowOperator(node: Node, vararg inputs: Expression?) { + inputs.filterNotNull().forEach { createEOG(it) } pushToEOG(node) - if (catchingScope is TryScope) { - catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList() - } else if (catchingScope is FunctionScope) { - catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList() + + val input = inputs.firstOrNull() + val throwType = input?.type + if (throwType != null) { + val catchingScope = + scopeManager.firstScopeOrNull { scope -> + scope is TryScope || scope is FunctionScope + } + if (catchingScope is TryScope) { + catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList() + } else if (catchingScope is FunctionScope) { + catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList() + } } - currentPredecessors.clear() + currentPredecessors.clear() // TODO: Should this be here or inside the if statement? } /** @@ -1033,29 +1036,9 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa pushToEOG(stmt) } - /** - * This is copied & pasted with minimal adjustments from [handleThrowOperator]. TODO: To be - * merged in a later PR. - */ + /** This is copied & pasted with minimal adjustments from [handleThrowOperator]. */ protected fun handleThrowStatement(statement: ThrowStatement) { - val input = statement.exception - createEOG(input) - statement.parentException?.let { createEOG(it) } - - val catchingScope = - scopeManager.firstScopeOrNull { scope -> scope is TryScope || scope is FunctionScope } - - val throwType = input?.type - if (throwType == null) { - TODO("???") - } - pushToEOG(statement) - if (catchingScope is TryScope) { - catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList() - } else if (catchingScope is FunctionScope) { - catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList() - } - currentPredecessors.clear() + handleThrowOperator(statement, statement.exception, statement.parentException) } companion object { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ThrowStatementTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ThrowStatementTest.kt index db86817a16c..bb459a2984b 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ThrowStatementTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ThrowStatementTest.kt @@ -25,29 +25,42 @@ */ package de.fraunhofer.aisec.cpg.graph -import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.GraphExamples.Companion.testFrontend +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.statements.ThrowStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.test.assertLocalName import kotlin.test.* class ThrowStatementTest { @Test fun testThrow() { val result = - TestLanguageFrontend().build { - translationResult { - translationUnit("some.file") { - function("foo", t("void")) { - body { - `throw` {} - `throw` { call("SomeError") } + testFrontend( + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) + .build { + translationResult { + translationUnit("some.file") { + function("foo", t("void")) { + body { + `throw` {} + `throw` { call("SomeError") } + `throw` { + call("SomeError") + call("SomeError2") + } + } } } } } - } // Let's assert that we did this correctly val main = result.functions["foo"] @@ -57,12 +70,32 @@ class ThrowStatementTest { val emptyThrow = body.statements.getOrNull(0) assertIs(emptyThrow) + println(emptyThrow.toString()) // This is only here to simulate a higher test coverage assertNull(emptyThrow.exception) + assertTrue(emptyThrow.prevDFG.isEmpty()) val throwWithExc = body.statements.getOrNull(1) assertIs(throwWithExc) + println(throwWithExc.toString()) // This is only here to simulate a higher test coverage val throwCall = throwWithExc.exception assertIs(throwCall) - assertEquals("SomeError", throwCall.name.localName) + assertLocalName("SomeError", throwCall) + assertEquals(setOf(throwCall), throwWithExc.prevDFG.toSet()) + + val throwWithExcAndParent = body.statements.getOrNull(2) + assertIs(throwWithExcAndParent) + println( + throwWithExcAndParent.toString() + ) // This is only here to simulate a higher test coverage + val throwCallException = throwWithExcAndParent.exception + assertIs(throwCallException) + assertLocalName("SomeError", throwCallException) + val throwCallParent = throwWithExcAndParent.parentException + assertIs(throwCallParent) + assertLocalName("SomeError2", throwCallParent) + assertEquals( + setOf(throwCallException, throwCallParent), + throwWithExcAndParent.prevDFG.toSet() + ) } } From 9f69fdd663cc6ea3b0fa9476e6b13efc89cee4b1 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 18 Oct 2024 08:36:03 +0200 Subject: [PATCH 29/30] Documentation --- .../cpg/graph/statements/ThrowStatement.kt | 4 +--- .../de/fraunhofer/aisec/cpg/passes/DFGPass.kt | 7 +------ .../cpg/graph/edges/flows/DataflowTest.kt | 2 +- docs/docs/CPG/specs/dfg.md | 17 +++++++++++++++ docs/docs/CPG/specs/eog.md | 21 +++++++++++++++++++ 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt index ce997117d30..27a8d1b200e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt @@ -42,9 +42,7 @@ class ThrowStatement : Statement(), ArgumentHolder { /** * Some languages (Python) can add a parent exception (or `cause`) to indicate that an exception - * was raised while handling another exception. This is stored in the graph, but has no further - * implications like EOG or DFG connections, as it is only of informational purpose, but it - * doesn't change the program behavior. + * was raised while handling another exception. */ @Relationship(value = "PARENT_EXCEPTION") var parentExceptionEdge = astOptionalEdgeOf() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt index 5607826b71e..da018184f71 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/DFGPass.kt @@ -139,12 +139,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) { } } - /** - * Handle a [ThrowStatement]. Currently, we support two types of [ThrowStatement.exception], - * which are then forwarded to the appropriate handlers: - * - [CallExpression] - * - [Reference] - */ + /** Handle a [ThrowStatement]. The exception and parent exception flow into the node. */ protected fun handleThrowStatement(node: ThrowStatement) { node.exception?.let { node.prevDFGEdges += it } node.parentException?.let { node.prevDFGEdges += it } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt index 28eda4c9b84..06b4c8ba708 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/DataflowTest.kt @@ -112,7 +112,7 @@ class DataflowTest { val result = prepareThrowDFGTest() // Let's assert that we did this correctly - val main = result.functions[0] + val main = result.functions["foo"] assertNotNull(main) val body = main.body assertIs(body) diff --git a/docs/docs/CPG/specs/dfg.md b/docs/docs/CPG/specs/dfg.md index 8f004005ceb..cf0f786e04a 100755 --- a/docs/docs/CPG/specs/dfg.md +++ b/docs/docs/CPG/specs/dfg.md @@ -391,6 +391,23 @@ The data flow from the input to this node and, in case of the operatorCodes ++ a *Dangerous: We have to ensure that the first operation is performed before the last one (if applicable)* +## ThrowStatement + +Interesting fields: + +* `exception: Expression`: The exception which is thrown +* `parentException: Expression`: The exception which has originally caused this exception to be thrown (e.g. in a catch clause) + +The return value flows to the whole statement. + +Scheme: +```mermaid + flowchart LR + exception -- DFG --> node([ReturnStatement]); + parentException -- DFG --> node; + exception -.- node; + parentException -.- node; +``` ## ReturnStatement diff --git a/docs/docs/CPG/specs/eog.md b/docs/docs/CPG/specs/eog.md index b20f26daee8..12ec6b46e71 100644 --- a/docs/docs/CPG/specs/eog.md +++ b/docs/docs/CPG/specs/eog.md @@ -379,6 +379,27 @@ flowchart LR ``` +## ThrowStatement +The EOG continues at an exception catching structure or a function that does a re-throw. + +Interesting fields: + +* `exception: Expression`: Exception to be thrown for exception handling. +* `parentException: Expression`: Exception which caused this exception to be thrown. + +Scheme: +```mermaid +flowchart LR + classDef outer fill:#fff,stroke:#ddd,stroke-dasharray:5 5; + prev:::outer --EOG--> child1["exception"] + child1 --EOG--> child2["parentException"] + child2 --EOG-->parent + parent(["throw"]) --EOG--> catchingContext:::outer + parent -.-> child1 + parent -.-> child2 + +``` + ## AssertStatement Statement that evaluates a condition and if the condition is false, evaluates a message, this message is generalized to a `Statement` to hold everything From 0be08caaaa9369a64dcc6e3581ab865421861297 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 18 Oct 2024 08:52:49 +0200 Subject: [PATCH 30/30] More doc of EOG handling --- .../aisec/cpg/passes/EvaluationOrderGraphPass.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index b5a3a07e06e..b215bb0be6e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -553,6 +553,14 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa } } + /** + * Generates the EOG for a [node] which represents a statement/expression which throws an + * exception. Since some languages may accept different inputs to a throw statement (typically + * 1, sometimes 2, 0 is also possible), we have collect these in [inputs]. The input which is + * evaluated first, must be the first item in the vararg! Any `null` object in `inputs` will be + * filtered. We connect the throw statement internally, i.e., the inputs are evaluated from + * index 0 to n and then the whole node is evaluated. + */ protected fun handleThrowOperator(node: Node, vararg inputs: Expression?) { inputs.filterNotNull().forEach { createEOG(it) } pushToEOG(node)