Skip to content

Commit

Permalink
Add implementation for Assert and NamedExpr nodes (#1683)
Browse files Browse the repository at this point in the history
  • Loading branch information
lshala authored Sep 16, 2024
1 parent 4e4784b commit ab6b5c8
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class ExpressionHandler(frontend: PythonLanguageFrontend) :
is Python.AST.FormattedValue -> handleFormattedValue(node)
is Python.AST.JoinedStr -> handleJoinedStr(node)
is Python.AST.Starred -> handleStarred(node)
is Python.AST.NamedExpr,
is Python.AST.NamedExpr -> handleNamedExpr(node)
is Python.AST.GeneratorExp,
is Python.AST.ListComp,
is Python.AST.SetComp,
Expand All @@ -83,6 +83,25 @@ class ExpressionHandler(frontend: PythonLanguageFrontend) :
}
}

/**
* Translates a Python (Named
* Expression)[https://docs.python.org/3/library/ast.html#ast.NamedExpr] into an
* [AssignExpression].
*
* As opposed to the Assign node, both target and value must be single nodes.
*/
private fun handleNamedExpr(node: Python.AST.NamedExpr): AssignExpression {
val assignExpression =
newAssignExpression(
operatorCode = ":=",
lhs = listOf(handle(node.target)),
rhs = listOf(handle(node.value)),
rawNode = node
)
assignExpression.usedAsExpression = true
return assignExpression
}

private fun handleFormattedValue(node: Python.AST.FormattedValue): Expression {
if (node.format_spec != null) {
return newProblemExpression(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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.expressions.Block
Expand Down Expand Up @@ -64,7 +65,7 @@ class StatementHandler(frontend: PythonLanguageFrontend) :
is Python.AST.Import -> handleImport(node)
is Python.AST.Break -> newBreakStatement(rawNode = node)
is Python.AST.Continue -> newContinueStatement(rawNode = node)
is Python.AST.Assert,
is Python.AST.Assert -> handleAssert(node)
is Python.AST.Delete,
is Python.AST.Global,
is Python.AST.Match,
Expand All @@ -81,6 +82,19 @@ class StatementHandler(frontend: PythonLanguageFrontend) :
}
}

/**
* Translates a Python (https://docs.python.org/3/library/ast.html#ast.Assert] into a
* [AssertStatement].
*/
private fun handleAssert(node: Python.AST.Assert): AssertStatement {
val assertStatement = newAssertStatement(rawNode = node)
val testExpression = frontend.expressionHandler.handle(node.test)
assertStatement.condition = testExpression
node.msg?.let { assertStatement.message = frontend.expressionHandler.handle(it) }

return assertStatement
}

private fun handleImport(node: Python.AST.Import): Statement {
val declStmt = newDeclarationStatement(rawNode = node)
for (imp in node.names) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,39 @@ class PythonFrontendTest : BaseTest() {
}
}

@Test
fun testNamedExpression() {
val topLevel = Path.of("src", "test", "resources", "python")
val result =
analyze(
listOf(
topLevel.resolve("named_expressions.py").toFile(),
),
topLevel,
true
) {
it.registerLanguage<PythonLanguage>()
}
val namedExpression = result.functions["named_expression"]
assertNotNull(namedExpression)

val assignExpression = result.statements[1] as? AssignExpression
assertNotNull(assignExpression)
assertEquals(":=", assignExpression.operatorCode)
assertEquals(true, assignExpression.usedAsExpression)

val lhs = assignExpression.lhs.firstOrNull() as? Reference
assertNotNull(lhs)

val lhsVariable = lhs.refersTo as? VariableDeclaration
assertLocalName("x", lhsVariable)

val rhs = assignExpression.rhs.firstOrNull() as? Literal<*>
assertNotNull(rhs)

assertEquals(4.toLong(), rhs.evaluate())
}

class PythonValueEvaluator : ValueEvaluator() {
override fun computeBinaryOpEffect(
lhsValue: Any?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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.get
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 java.nio.file.Path
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.TestInstance

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class StatementHandlerTest {

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

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

fun analyzeFile(fileName: String) {
result =
analyze(listOf(topLevel.resolve(fileName).toFile()), topLevel, true) {
it.registerLanguage<PythonLanguage>()
}
assertNotNull(result)
}

@Test
fun testAssert() {
analyzeFile("assert.py")

val func = result.functions["test_assert"]
assertNotNull(func, "Function 'test_assert' should be found")

val assertStatement =
func.body.statements.firstOrNull { it is AssertStatement } as? AssertStatement
assertNotNull(assertStatement, "Assert statement should be found")

val condition = assertStatement.condition
assertNotNull(condition, "Assert statement should have a condition")
assertEquals("1 == 1", condition.code, "The condition is incorrect")

val message = assertStatement.message as? Literal<*>
assertNotNull(message, "Assert statement should have a message")
assertEquals("Test message", message.value, "The assert message is incorrect")
}
}
2 changes: 2 additions & 0 deletions cpg-language-python/src/test/resources/python/assert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_assert():
assert 1 == 1, "Test message"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def named_expression():
(x := 4)

0 comments on commit ab6b5c8

Please sign in to comment.