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 all 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 @@
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
}

Check warning on line 373 in cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt

View check run for this annotation

Codecov / codecov/patch

cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/StatementBuilder.kt#L373

Added line #L373 was not covered by tests
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,93 @@
/*
* 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.
*/
@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 59 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#L59

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

Check warning on line 62 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-L62

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

Check warning on line 66 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#L65-L66

Added lines #L65 - L66 were not covered by tests
}
else -> false

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#L68

Added line #L68 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()
}
}
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,12 @@ class DFGPass(ctx: TranslationContext) : ComponentPass(ctx) {
}
}

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

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 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa
map[LookupScopeStatement::class.java] = {
handleLookupScopeStatement(it as LookupScopeStatement)
}
map[ThrowStatement::class.java] = { handleThrowStatement(it as ThrowStatement) }
}

protected fun doNothing() {
Expand Down Expand Up @@ -546,27 +547,38 @@ 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
/**
* 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)
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?
}

/**
Expand Down Expand Up @@ -1032,6 +1044,11 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa
pushToEOG(stmt)
}

/** This is copied & pasted with minimal adjustments from [handleThrowOperator]. */
protected fun handleThrowStatement(statement: ThrowStatement) {
handleThrowOperator(statement, statement.exception, statement.parentException)
}

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,101 @@
/*
* 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.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 =
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"]
assertNotNull(main)
val body = main.body
assertIs<Block>(body)

val emptyThrow = body.statements.getOrNull(0)
assertIs<ThrowStatement>(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<ThrowStatement>(throwWithExc)
println(throwWithExc.toString()) // This is only here to simulate a higher test coverage
val throwCall = throwWithExc.exception
assertIs<CallExpression>(throwCall)
assertLocalName("SomeError", throwCall)
assertEquals(setOf<Node>(throwCall), throwWithExc.prevDFG.toSet())

val throwWithExcAndParent = body.statements.getOrNull(2)
assertIs<ThrowStatement>(throwWithExcAndParent)
println(
throwWithExcAndParent.toString()
) // This is only here to simulate a higher test coverage
val throwCallException = throwWithExcAndParent.exception
assertIs<CallExpression>(throwCallException)
assertLocalName("SomeError", throwCallException)
val throwCallParent = throwWithExcAndParent.parentException
assertIs<CallExpression>(throwCallParent)
assertLocalName("SomeError2", throwCallParent)
assertEquals(
setOf<Node>(throwCallException, throwCallParent),
throwWithExcAndParent.prevDFG.toSet()
)
}
}
Loading
Loading