Skip to content

Commit

Permalink
Initial version of python try with all things
Browse files Browse the repository at this point in the history
  • Loading branch information
KuechA committed Sep 20, 2024
1 parent 737af6e commit 4c0fbbe
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,44 @@ import org.neo4j.ogm.annotation.Relationship

/** A [Statement] which represents a try/catch block, primarily used for exception handling. */
class TryStatement : Statement() {
/**
* This represents some kind of resource which is typically opened (or similar) while entering
* the [tryBlock]. If this operation fails, we may continue with the [finallyBlock]. However,
* there is no exception raised but if an exception occurs while opening the resource, we won't
* enter the [tryBlock].
*/
@Relationship(value = "RESOURCES", direction = Relationship.Direction.OUTGOING)
var resourceEdges = astEdgesOf<Statement>()
var resources by unwrapping(TryStatement::resourceEdges)

/**
* This represents a block whose statements can throw exceptions which are handled by the
* [catchClauses].
*/
@Relationship(value = "TRY_BLOCK") var tryBlockEdge = astOptionalEdgeOf<Block>()
var tryBlock by unwrapping(TryStatement::tryBlockEdge)

/**
* This represents a block whose statements are only executed if the [tryBlock] finished without
* exceptions. Note that any exception thrown in this block is no longer caught by the
* [catchClauses].
*/
@Relationship(value = "ELSE_BLOCK") var elseBlockEdge = astOptionalEdgeOf<Block>()
var elseBlock by unwrapping(TryStatement::elseBlockEdge)

/**
* This represents a block of statements which is always executed after finishing the [tryBlock]
* or one of the [catchClauses]. Note that any exception thrown in this block is no longer
* caught by the [catchClauses].
*/
@Relationship(value = "FINALLY_BLOCK") var finallyBlockEdge = astOptionalEdgeOf<Block>()
var finallyBlock by unwrapping(TryStatement::finallyBlockEdge)

/**
* This represents a set of blocks whose statements handle the exceptions which are thrown in
* the [tryBlock]. There can be multiple catch clauses, but it is also possible that none
* exists.
*/
@Relationship(value = "CATCH_CLAUSES", direction = Relationship.Direction.OUTGOING)
var catchClauseEdges = astEdgesOf<CatchClause>()
var catchClauses by unwrapping(TryStatement::catchClauseEdges)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ interface Python {
val body: kotlin.collections.List<BaseStmt> by lazy { "body" of pyObject }
val handlers: kotlin.collections.List<excepthandler> by lazy { "handlers" of pyObject }
val orelse: kotlin.collections.List<BaseStmt> by lazy { "orelse" of pyObject }
val stmt: kotlin.collections.List<BaseStmt> by lazy { "StmtBase" of pyObject }
val finalbody: kotlin.collections.List<BaseStmt> by lazy { "finalbody" of pyObject }
}

/**
Expand Down Expand Up @@ -1321,8 +1321,8 @@ interface Python {
* TODO: excepthandler <-> ExceptHandler
*/
class excepthandler(pyObject: PyObject) : AST(pyObject), WithLocation {
val type: BaseExpr by lazy { "type" of pyObject }
val name: String by lazy { "name" of pyObject }
val type: BaseExpr? by lazy { "type" of pyObject }
val name: String? by lazy { "name" of pyObject }
val body: kotlin.collections.List<BaseStmt> by lazy { "body" of pyObject }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@
*/
package de.fraunhofer.aisec.cpg.frontends.python

import de.fraunhofer.aisec.cpg.frontends.HasFunctionStyleConstruction
import de.fraunhofer.aisec.cpg.frontends.HasOperatorOverloading
import de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators
import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.frontends.of
import de.fraunhofer.aisec.cpg.frontends.*
import de.fraunhofer.aisec.cpg.graph.HasOverloadedOperation
import de.fraunhofer.aisec.cpg.graph.autoType
import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration
Expand All @@ -45,7 +41,8 @@ class PythonLanguage :
Language<PythonLanguageFrontend>(),
HasShortCircuitOperators,
HasOperatorOverloading,
HasFunctionStyleConstruction {
HasFunctionStyleConstruction,
HasAnonymousIdentifier {
override val fileExtensions = listOf("py", "pyi")
override val namespaceDelimiter = "."
@Transient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ fun fromPython(pyObject: Any?): Python.BaseObject {
"ast.arg" -> Python.AST.arg(pyObject)
"ast.arguments" -> Python.AST.arguments(pyObject)
"ast.comprehension" -> Python.AST.comprehension(pyObject)
"ast.ExceptHandler",
"ast.excepthandler" -> Python.AST.excepthandler(pyObject)
"ast.keyword" -> Python.AST.keyword(pyObject)
"ast.match_case" -> Python.AST.match_case(pyObject)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/
package de.fraunhofer.aisec.cpg.frontends.python

import de.fraunhofer.aisec.cpg.frontends.HasAnonymousIdentifier
import de.fraunhofer.aisec.cpg.frontends.HasOperatorOverloading
import de.fraunhofer.aisec.cpg.frontends.isKnownOperatorName
import de.fraunhofer.aisec.cpg.frontends.python.Python.AST.IsAsync
Expand All @@ -33,9 +34,7 @@ import de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage.Companion.MODIFIE
import de.fraunhofer.aisec.cpg.graph.*
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.Statement
import de.fraunhofer.aisec.cpg.graph.statements.*
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression
Expand Down Expand Up @@ -67,12 +66,12 @@ class StatementHandler(frontend: PythonLanguageFrontend) :
is Python.AST.Break -> newBreakStatement(rawNode = node)
is Python.AST.Continue -> newContinueStatement(rawNode = node)
is Python.AST.Assert -> handleAssert(node)
is Python.AST.Try -> handleTryStatement(node)
is Python.AST.Delete,
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,
is Python.AST.AsyncWith ->
Expand All @@ -84,7 +83,56 @@ class StatementHandler(frontend: PythonLanguageFrontend) :
}

/**
* Translates a Python (https://docs.python.org/3/library/ast.html#ast.Assert] into a
* Translates an [`ExceptHandler`](https://docs.python.org/3/library/ast.html#ast.ExceptHandler)
* to a [CatchClause].
*
* It adds all the statements to the body and will set a parameter if it exists. For the
* catch-all clause, we do not set the [CatchClause.parameter].
*/
private fun handleExcepthandler(excepthandler: Python.AST.excepthandler): CatchClause {
val catchClause = newCatchClause(excepthandler)
catchClause.body = newBlock(excepthandler)
catchClause.body?.statements?.addAll(excepthandler.body.map(::handle))
// The parameter can have a type but if the type is None/null, it's the "catch-all" clause.
// In this case, it also cannot have a name, so we can skip the variable declaration.
if (excepthandler.type != null) {
// the parameter can have a name, or we use the anonymous identifier _
catchClause.parameter =
newVariableDeclaration(
excepthandler.name
?: (language as? HasAnonymousIdentifier)?.anonymousIdentifier,
type = frontend.typeOf(excepthandler.type),
rawNode = excepthandler
)
}
return catchClause
}

/**
* Translates a Python [`Try`](https://docs.python.org/3/library/ast.html#ast.Try) into a
* [TryStatement].
*/
private fun handleTryStatement(node: Python.AST.Try): TryStatement {
val tryStatement = newTryStatement(rawNode = node)
tryStatement.tryBlock = newBlock()
tryStatement.tryBlock?.statements?.addAll(node.body.map(::handle))

tryStatement.catchClauses.addAll(node.handlers.map { handleExcepthandler(it) })

if (node.orelse.isNotEmpty()) {
tryStatement.elseBlock = newBlock()
tryStatement.elseBlock?.statements?.addAll(node.orelse.map(::handle))
}

if (node.finalbody.isNotEmpty()) {
tryStatement.finallyBlock = newBlock()
tryStatement.finallyBlock?.statements?.addAll(node.finalbody.map(::handle))
}

return tryStatement
}
/**
* Translates a Python [`Assert`](https://docs.python.org/3/library/ast.html#ast.Assert) into a
* [AssertStatement].
*/
private fun handleAssert(node: Python.AST.Assert): AssertStatement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import de.fraunhofer.aisec.cpg.graph.statements.AssertStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal
import de.fraunhofer.aisec.cpg.test.analyze
import de.fraunhofer.aisec.cpg.test.analyzeAndGetFirstTU
import de.fraunhofer.aisec.cpg.test.assertLocalName
import de.fraunhofer.aisec.cpg.test.assertResolvedType
import java.nio.file.Path
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.TestInstance

Expand All @@ -59,6 +61,28 @@ class StatementHandlerTest {
assertNotNull(result)
}

@Test
fun testTry() {
val tu =
analyzeAndGetFirstTU(listOf(topLevel.resolve("try.py").toFile()), topLevel, true) {
it.registerLanguage<PythonLanguage>()
}
assertNotNull(tu)

val tryAll = tu.functions["tryAll"]?.trys?.singleOrNull()
assertNotNull(tryAll)

assertEquals(1, tryAll.tryBlock?.statements?.size)

assertEquals(3, tryAll.catchClauses.size)
assertLocalName("_", tryAll.catchClauses[0].parameter)
assertLocalName("e", tryAll.catchClauses[1].parameter)
assertNull(tryAll.catchClauses[2].parameter)

assertEquals(1, tryAll.elseBlock?.statements?.size)
assertEquals(1, tryAll.finallyBlock?.statements?.size)
}

@Test
fun testAsync() {
val tu =
Expand Down
13 changes: 13 additions & 0 deletions cpg-language-python/src/test/resources/python/try.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
def tryAll(a):
try:
b = a+2
except Exception1:
print("There was an occurrence of Exception1")
except OtherException as e:
print("We saw exception" + e)
except:
print("Catch all!")
else:
print("All good, got " + b)
finally:
print("It's over")

0 comments on commit 4c0fbbe

Please sign in to comment.