Skip to content

Commit

Permalink
Python: raise (#1741)
Browse files Browse the repository at this point in the history
* new node for RaiseStatement + NodeBuilder code

* don't add python stuff in this branch

* started work on EOG and DFG

* doc

* raise: fluent and first test

* fluent: patch @oxisto

* Start work on DFG test for raise

* rename raise -> throw

* more renaming raise -> throw

* python: raise

* copy & paste handleThrowOperator

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

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

* Update dependency rollup to v4.24.0 (#1774)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* 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.

* Cleanup of `SymbolResolver` (#1777)

* Fixed crash in `getCodeOfSubregion` (#1776)

* 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

* 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

* `implicit()` only triggers code/location update now if its not empty (#1784)

Otherwise, we override the code/location again.

* 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.

* code review

* code review

* code review

* rename cause to parentException

* doc

* ThrowStatement: add toString

* fix tests

* Enable raising the exception in the with statement

* Test

* Test++

---------

Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com>
Co-authored-by: Christian Banse <christian.banse@aisec.fraunhofer.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alexander Kuechler <alexander.kuechler@aisec.fraunhofer.de>
  • Loading branch information
5 people authored Oct 18, 2024
1 parent a12951b commit c9fa0e5
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,10 @@ val Node?.forLoops: List<ForStatement>
val Node?.trys: List<TryStatement>
get() = this.allChildren()

/** Returns all [ThrowStatement] child edges in this graph, starting with this [Node]. */
val Node?.throws: List<ThrowStatement>
get() = this.allChildren()

/** Returns all [ForEachStatement] child edges in this graph, starting with this [Node]. */
val Node?.forEachLoops: List<ForEachStatement>
get() = this.allChildren()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa
pushToEOG(stmt)
}

/** This is copied & pasted with minimal adjustments from [handleThrowOperator]. */
/** Calls [handleThrowOperator]. */
protected fun handleThrowStatement(statement: ThrowStatement) {
handleThrowOperator(statement, statement.exception, statement.parentException)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ class StatementHandler(frontend: PythonLanguageFrontend) :
is Python.AST.With -> handleWithStatement(node)
is Python.AST.Global -> handleGlobal(node)
is Python.AST.Nonlocal -> handleNonLocal(node)
is Python.AST.Raise -> handleRaise(node)
is Python.AST.Match,
is Python.AST.Raise,
is Python.AST.TryStar,
is Python.AST.AsyncWith ->
newProblemExpression(
Expand All @@ -86,6 +86,17 @@ class StatementHandler(frontend: PythonLanguageFrontend) :
}
}

/**
* Translates a Python [`Raise`](https://docs.python.org/3/library/ast.html#ast.Raise) into a
* [ThrowStatement].
*/
private fun handleRaise(node: Python.AST.Raise): ThrowStatement {
val ret = newThrowStatement(rawNode = node)
node.exc?.let { ret.exception = frontend.expressionHandler.handle(it) }
node.cause?.let { ret.parentException = frontend.expressionHandler.handle(it) }
return ret
}

/**
* Translates a Python [`With`](https://docs.python.org/3/library/ast.html#ast.With) into a
* [Block].
Expand Down Expand Up @@ -193,8 +204,7 @@ class StatementHandler(frontend: PythonLanguageFrontend) :
exitCallWithSysExec.addArgument(starOp)

val ifStmt = newIfStatement().implicit()
// TODO: Needs #1733 and 1741, then add:
// ifStmt.thenStatement = newThrowStatement().implicit()
ifStmt.thenStatement = newThrowStatement().implicit()
val neg = newUnaryOperator("not", false, false).implicit()
neg.input = exitCallWithSysExec
ifStmt.condition = neg
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* 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.frontends.python.statementHandler

import de.fraunhofer.aisec.cpg.TranslationResult
import de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.test.BaseTest
import de.fraunhofer.aisec.cpg.test.analyze
import de.fraunhofer.aisec.cpg.test.assertLocalName
import java.nio.file.Path
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ExceptionsTest : BaseTest() {

private lateinit var topLevel: Path
private lateinit var result: TranslationResult

@BeforeAll
fun setup() {
topLevel = Path.of("src", "test", "resources", "python")

result =
analyze(listOf(topLevel.resolve("raise.py").toFile()), topLevel, true) {
it.registerLanguage<PythonLanguage>()
}
assertNotNull(result)
}

@Test
fun testWithExceptionAndTry() {
val function = result.functions["raise_in_try"]
assertNotNull(function)
val throwStmt = function.throws.singleOrNull()
assertNotNull(throwStmt)

val exception = throwStmt.exception
assertIs<CallExpression>(exception)
assertLocalName("Exception", exception)
assertNull(throwStmt.parentException)
assertEquals(listOf<Node>(exception), throwStmt.prevEOG)

val catchClause =
function.trys.singleOrNull()?.catchClauses?.singleOrNull {
it.parameter?.type?.name?.localName == "Exception"
}
assertNotNull(catchClause)
assertEquals(listOf<Node>(catchClause), throwStmt.nextEOG)
}

@Test
fun testWithExceptionAndTry2() {
val function = result.functions["raise_in_try2"]
assertNotNull(function)
val throwStmt = function.throws.singleOrNull()
assertNotNull(throwStmt)

val exception = throwStmt.exception
assertIs<CallExpression>(exception)
assertLocalName("Exception", exception)
assertNull(throwStmt.parentException)
assertEquals(listOf<Node>(exception), throwStmt.prevEOG)

val catchClause =
function.trys.singleOrNull()?.catchClauses?.singleOrNull {
it.parameter?.type?.name?.localName == "Exception"
}
assertNotNull(catchClause)
assertEquals(listOf<Node>(catchClause), throwStmt.nextEOG)
}

@Test
fun testWithoutTry() {
val function = result.functions["raise_without_try"]
assertNotNull(function)
val throwStmt = function.throws.singleOrNull()
assertNotNull(throwStmt)

val exception = throwStmt.exception
assertIs<CallExpression>(exception)
assertLocalName("Exception", exception)
assertNull(throwStmt.parentException)
assertEquals(listOf<Node>(exception), throwStmt.prevEOG)

val functionBody = function.body
assertNotNull(functionBody)
assertEquals(listOf<Node>(functionBody), throwStmt.nextEOG.toList())
}

@Test
fun testWithParent() {
val function = result.functions["raise_with_parent"]
assertNotNull(function)
val throwStmt = function.throws.singleOrNull()
assertNotNull(throwStmt)

val exception = throwStmt.exception
assertIs<CallExpression>(exception)
assertLocalName("Exception", exception)
val parent = throwStmt.parentException
assertIs<CallExpression>(parent)
assertLocalName("A", parent)
assertEquals(listOf<Node>(parent), throwStmt.prevEOG)
assertEquals(listOf<Node>(exception), parent.prevEOG)

val functionBody = function.body
assertNotNull(functionBody)
assertEquals(listOf<Node>(functionBody), throwStmt.nextEOG.toList())
}

@Test
fun testEmpty() {
val function = result.functions["raise_empty"]
assertNotNull(function)
val throwStmt = function.throws.singleOrNull()
assertNotNull(throwStmt)

assertNull(throwStmt.exception)
assertNull(throwStmt.parentException)

val functionBody = function.body
assertNotNull(functionBody)
// TODO: This doesn't work yet. Needs fix in the EOG pass!
// assertEquals(listOf<Node>(functionBody), throwStmt.nextEOG.toList())
}
}
25 changes: 25 additions & 0 deletions cpg-language-python/src/test/resources/python/raise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
def raise_in_try():
try:
raise Exception()
except Exception:
print("Caught Exception")
except A:
print("Caught A")

def raise_in_try2():
try:
raise Exception()
except A:
print("Caught A")
except Exception:
print("Caught Exception")


def raise_without_try():
raise Exception()

def raise_empty():
raise

def raise_with_parent():
raise Exception() from A()

0 comments on commit c9fa0e5

Please sign in to comment.