diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeleteExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeleteExpression.kt index 7540cf5772..dfa39b08f2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeleteExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeleteExpression.kt @@ -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() - var operand by unwrapping(DeleteExpression::operandEdge) + @Relationship("OPERANDS") var operandEdges = astEdgesOf() + 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) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index c19706f644..ab671ba2df 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -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) } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt index 48ba736625..1647567d08 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt @@ -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 } diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt index a6855e1df2..d08f253aaf 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt @@ -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 @@ -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, @@ -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]. diff --git a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/statementHandler/StatementHandlerTest.kt b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/statementHandler/StatementHandlerTest.kt index c6f95f3bad..1023f22fc7 100644 --- a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/statementHandler/StatementHandlerTest.kt +++ b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/statementHandler/StatementHandlerTest.kt @@ -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 @@ -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() + 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) + } } } diff --git a/cpg-language-python/src/test/resources/python/delete.py b/cpg-language-python/src/test/resources/python/delete.py new file mode 100644 index 0000000000..6181c76681 --- /dev/null +++ b/cpg-language-python/src/test/resources/python/delete.py @@ -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 \ No newline at end of file