Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a throw or raise statement #1733

Merged
merged 34 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
58c0528
new node for RaiseStatement + NodeBuilder code
maximiliankaul Sep 26, 2024
4523b73
don't add python stuff in this branch
maximiliankaul Sep 26, 2024
616c413
Merge branch 'main' into mk/raiseStmt
maximiliankaul Sep 27, 2024
9c8ac1b
started work on EOG and DFG
maximiliankaul Sep 27, 2024
30c4908
doc
maximiliankaul Sep 27, 2024
3380050
raise: fluent and first test
maximiliankaul Sep 27, 2024
40d7658
fluent: patch @oxisto
maximiliankaul Sep 27, 2024
ca5ac9d
Start work on DFG test for raise
maximiliankaul Sep 27, 2024
5054a4f
rename raise -> throw
maximiliankaul Sep 27, 2024
735f0aa
more renaming raise -> throw
maximiliankaul Sep 27, 2024
9d5f5a5
Merge branch 'main' into mk/raiseStmt
maximiliankaul Oct 2, 2024
db77867
copy & paste handleThrowOperator
maximiliankaul Oct 4, 2024
b0ed02b
Update cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/f…
maximiliankaul Oct 8, 2024
18afeff
Rename `findSymbols` into `lookupSymbolByName` (#1772)
oxisto Oct 3, 2024
2c7dd71
Update dependency rollup to v4.24.0 (#1774)
renovate[bot] Oct 3, 2024
f542681
Added language trait `HasImplicitReceiver` (#1778)
oxisto Oct 3, 2024
0187754
Cleanup of `SymbolResolver` (#1777)
oxisto Oct 4, 2024
9755f71
Fixed crash in `getCodeOfSubregion` (#1776)
oxisto Oct 6, 2024
a69f98a
Add new function `lookupUniqueTypeSymbolByName` (#1781)
oxisto Oct 6, 2024
b358fa1
Make sure to move `typeObservers` from old to new node when replacing…
oxisto Oct 7, 2024
5e83741
`implicit()` only triggers code/location update now if its not empty …
oxisto Oct 7, 2024
205a275
Added `:=` as simple operator in Python (#1785)
oxisto Oct 8, 2024
89fd8ec
code review
maximiliankaul Oct 8, 2024
3d4c64b
code review
maximiliankaul Oct 8, 2024
ebe1a27
code review
maximiliankaul Oct 8, 2024
6422dc7
Merge branch 'main' into mk/raiseStmt
maximiliankaul Oct 10, 2024
222408d
rename cause to parentException
maximiliankaul Oct 10, 2024
1b10c3d
doc
maximiliankaul Oct 10, 2024
7df6417
ThrowStatement: add toString
maximiliankaul Oct 10, 2024
2990cc4
fix tests
maximiliankaul Oct 10, 2024
825ad28
Merge EOG, add tests
KuechA Oct 18, 2024
9f69fdd
Documentation
KuechA Oct 18, 2024
073d9f9
Merge branch 'main' into mk/raiseStmt
KuechA Oct 18, 2024
0be08ca
More doc of EOG handling
KuechA Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,18 @@ fun MetadataProvider.newLookupScopeStatement(
log(node)
return node
}

/**
* 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.newThrowStatement(rawNode: Any? = null): ThrowStatement {
val node = ThrowStatement()
node.applyMetadata(this, EMPTY_NAME, rawNode, true)

log(node)
return node
}
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,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].
Expand All @@ -1414,6 +1415,23 @@ infix fun Expression.assignAsExpr(rhs: AssignExpression.() -> Unit): AssignExpre
return node
}

/**
* Creates a new [ThrowStatement] in the Fluent Node DSL and adds it to the nearest enclosing
* [StatementHolder].
*/
context(LanguageFrontend<*, *>, Holder<out Node>)
infix fun Expression.`throw`(init: (ThrowStatement.() -> Unit)?): ThrowStatement {
val node = (this@LanguageFrontend).newThrowStatement()
if (init != null) init(node)

val holder = this@Holder
if (holder is StatementHolder) {
holder += node
}

return node
}

/** Creates a new [Type] with the given [name] in the Fluent Node DSL. */
fun LanguageFrontend<*, *>.t(name: CharSequence, generics: List<Type> = listOf()) =
objectType(name, generics)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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.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
import java.util.Objects
import org.apache.commons.lang3.builder.ToStringBuilder
import org.neo4j.ogm.annotation.Relationship

/** Represents a `throw` or `raise` statement. */
class ThrowStatement : Statement(), ArgumentHolder {

/** The exception object to be raised. */
@Relationship(value = "EXCEPTION") var exceptionEdge = astOptionalEdgeOf<Expression>()
var exception by unwrapping(ThrowStatement::exceptionEdge)

/**
* 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
KuechA marked this conversation as resolved.
Show resolved Hide resolved
* doesn't change the program behavior.
*/
@Relationship(value = "PARENT_EXCEPTION")
var parentExceptionEdge = astOptionalEdgeOf<Expression>()
var parentException by unwrapping(ThrowStatement::parentExceptionEdge)

override fun addArgument(expression: Expression) {
when {
exception == null -> exception = expression
parentException == null -> parentException = expression
}
}

override fun replaceArgument(old: Expression, new: Expression): Boolean {
return when {

Check warning on line 61 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt#L61

Added line #L61 was not covered by tests
exception == old -> {
exception = new
true

Check warning on line 64 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt#L63-L64

Added lines #L63 - L64 were not covered by tests
}
parentException == old -> {
parentException = new
true

Check warning on line 68 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt#L67-L68

Added lines #L67 - L68 were not covered by tests
}
else -> false

Check warning on line 70 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt#L70

Added line #L70 was not covered by tests
}
}

override fun hasArgument(expression: Expression): Boolean {
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 &&
parentException == other.parentException
}

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()

Check warning on line 93 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ThrowStatement.kt#L89-L93

Added lines #L89 - L93 were not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) {
is ForStatement -> handleForStatement(node)
is SwitchStatement -> handleSwitchStatement(node)
is IfStatement -> handleIfStatement(node)
is ThrowStatement -> handleThrowStatement(node)
// Declarations
is FieldDeclaration -> handleFieldDeclaration(node)
is FunctionDeclaration -> handleFunctionDeclaration(node, functionSummaries)
Expand All @@ -138,6 +139,17 @@ 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:
KuechA marked this conversation as resolved.
Show resolved Hide resolved
* - [CallExpression]
* - [Reference]
*/
protected fun handleThrowStatement(node: ThrowStatement) {
node.exception?.let { node.prevDFGEdges += it }
node.parentException?.let { node.prevDFGEdges += it }
}

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
map[LookupScopeStatement::class.java] = {
handleLookupScopeStatement(it as LookupScopeStatement)
}
map[ThrowStatement::class.java] = { handleThrowStatement(it as ThrowStatement) }
}

protected fun doNothing() {
Expand Down Expand Up @@ -1032,6 +1033,31 @@
pushToEOG(stmt)
}

/**
* 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 }

val throwType = input?.type
if (throwType == null) {
TODO("???")

Check warning on line 1050 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt#L1050

Added line #L1050 was not covered by tests
}
pushToEOG(statement)
if (catchingScope is TryScope) {
catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList()

Check warning on line 1054 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt#L1054

Added line #L1054 was not covered by tests
} else if (catchingScope is FunctionScope) {
catchingScope.catchesOrRelays[throwType] = currentPredecessors.toMutableList()
}
currentPredecessors.clear()
}

companion object {
protected val LOGGER = LoggerFactory.getLogger(EvaluationOrderGraphPass::class.java)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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") } }
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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.ThrowStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
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") }
}
}
}
}
}

// Let's assert that we did this correctly
val main = result.functions["foo"]
assertNotNull(main)
val body = main.body
assertIs<Block>(body)

val emptyThrow = body.statements.getOrNull(0)
assertIs<ThrowStatement>(emptyThrow)
assertNull(emptyThrow.exception)

val throwWithExc = body.statements.getOrNull(1)
assertIs<ThrowStatement>(throwWithExc)
val throwCall = throwWithExc.exception
assertIs<CallExpression>(throwCall)
assertEquals("SomeError", throwCall.name.localName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@
*/
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.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
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 {
Expand Down Expand Up @@ -101,4 +106,25 @@ class DataflowTest {
assertEquals(0, node2.prevDFGEdges.size)
}
}

@Test
fun testThrow() {
val result = prepareThrowDFGTest()

// Let's assert that we did this correctly
val main = result.functions[0]
KuechA marked this conversation as resolved.
Show resolved Hide resolved
assertNotNull(main)
val body = main.body
assertIs<Block>(body)

val throwStmt = body.statements.getOrNull(1)
assertIs<ThrowStatement>(throwStmt)
assertNotNull(throwStmt.exception)
val throwCall = throwStmt.exception
assertIs<CallExpression>(throwCall)

val someError = result.calls["SomeError"]
assertIs<CallExpression>(someError)
assertContains(throwStmt.prevDFG, someError)
}
}
Loading