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

Handle delete statement in python frontend #1723

Merged
merged 10 commits into from
Sep 25, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@
*/
package de.fraunhofer.aisec.cpg.graph.statements.expressions

import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf
import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
import java.util.Objects
import org.neo4j.ogm.annotation.Relationship

class DeleteExpression : Expression() {
@Relationship("OPERAND") var operandEdge = astOptionalEdgeOf<Expression>()
var operand by unwrapping(DeleteExpression::operandEdge)
@Relationship("OPERANDS") var operandEdges = astEdgesOf<Expression>()
var operands by unwrapping(DeleteExpression::operandEdges)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DeleteExpression) return false
return super.equals(other) && operand == other.operand
return super.equals(other) && operands == other.operands
}

override fun hashCode() = Objects.hash(super.hashCode(), operand)
override fun hashCode() = Objects.hash(super.hashCode(), operands)
}
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,9 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa
}

protected fun handleDeleteExpression(node: DeleteExpression) {
createEOG(node.operand)
for (operand in node.operands) {
createEOG(operand)
}
pushToEOG(node)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) :
for (name in ctx.implicitDestructorNames) {
log.debug("Implicit constructor name {}", name)
}
deleteExpression.operand = handle(ctx.operand)
handle(ctx.operand)?.let { deleteExpression.operands.add(it) }
return deleteExpression
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement
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
import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeleteExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression
Expand Down Expand Up @@ -68,7 +69,7 @@ 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.Delete,
is Python.AST.Delete -> handleDelete(node)
is Python.AST.Global,
is Python.AST.Match,
is Python.AST.Nonlocal,
Expand All @@ -84,6 +85,27 @@ class StatementHandler(frontend: PythonLanguageFrontend) :
}
}

/**
* Translates a Python [`Delete`](https://docs.python.org/3/library/ast.html#ast.Delete) into a
* [DeleteExpression].
*/
private fun handleDelete(node: Python.AST.Delete): DeleteExpression {
val delete = newDeleteExpression(rawNode = node)
node.targets.forEach { target ->
if (target is Python.AST.Subscript) {
delete.operands.add(frontend.expressionHandler.handle(target))
} else {
delete.additionalProblems +=
newProblemExpression(
problem =
"handleDelete: 'Name' and 'Attribute' deletions are not supported, as they removes them from the scope.",
rawNode = target
)
}
}
return delete
}

/**
* Translates a Python [`Assert`](https://docs.python.org/3/library/ast.html#ast.Assert) into a
* [AssertStatement].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,19 @@ package de.fraunhofer.aisec.cpg.frontends.python.statementHandler
import de.fraunhofer.aisec.cpg.TranslationResult
import de.fraunhofer.aisec.cpg.frontends.python.*
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.statements.*
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.statements.AssertStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeleteExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal
import de.fraunhofer.aisec.cpg.graph.statements.expressions.SubscriptExpression
import de.fraunhofer.aisec.cpg.test.*
import de.fraunhofer.aisec.cpg.test.analyze
import de.fraunhofer.aisec.cpg.test.analyzeAndGetFirstTU
import de.fraunhofer.aisec.cpg.test.assertResolvedType
import java.nio.file.Path
import kotlin.test.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.TestInstance

Expand Down Expand Up @@ -120,18 +128,49 @@ class StatementHandlerTest : BaseTest() {
assertEquals("Test message", message.value, "The assert message is incorrect")
}

@Test
fun testDeleteStatements() {
analyzeFile("delete.py")

val deleteExpressions = result.statements.filterIsInstance<DeleteExpression>()
assertEquals(4, deleteExpressions.size)

// Test for `del a`
val deleteStmt1 = deleteExpressions[0]
assertEquals(0, deleteStmt1.operands.size)
assertEquals(1, deleteStmt1.additionalProblems.size)

// Test for `del my_list[2]`
val deleteStmt2 = deleteExpressions[1]
assertEquals(1, deleteStmt2.operands.size)
assertTrue(deleteStmt2.operands.first() is SubscriptExpression)
assertTrue(deleteStmt2.additionalProblems.isEmpty())

// Test for `del my_dict['b']`
val deleteStmt3 = deleteExpressions[2]
assertEquals(1, deleteStmt3.operands.size)
assertTrue(deleteStmt3.operands.first() is SubscriptExpression)
assertTrue(deleteStmt3.additionalProblems.isEmpty())

// Test for `del obj.d`
val deleteStmt4 = deleteExpressions[3]
assertEquals(0, deleteStmt4.operands.size)
assertEquals(1, deleteStmt4.additionalProblems.size)
}

@Test
fun testTypeHints() {
analyzeFile("type_hints.py")

// type comments
val a = result.refs["a"]
assertNotNull(a)
assertEquals(with(result) { assertResolvedType("int") }, a.type)

// type annotation
val b = result.refs["b"]
assertNotNull(b)
assertEquals(with(result) { assertResolvedType("str") }, b.type)
with(result) {
// type comments
val a = result.refs["a"]
assertNotNull(a)
assertEquals(assertResolvedType("int"), a.type)

// type annotation
val b = result.refs["b"]
assertNotNull(b)
assertEquals(assertResolvedType("str"), b.type)
}
}
}
16 changes: 16 additions & 0 deletions cpg-language-python/src/test/resources/python/delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
a = 5
b = a
del a

my_list = [1, 2, 3]
del my_list[2]

my_dict = {'a': 1, 'b': 2}
del my_dict['b']

class MyClass:
def __init__(self):
self.d = 1

obj = MyClass()
del obj.d
Loading