From 2492236839830aea561d75ed2d3de2c393bc8422 Mon Sep 17 00:00:00 2001 From: Markus Lottmann Date: Mon, 8 Dec 2025 18:42:42 +0100 Subject: [PATCH] [pysrc2cpg] Avoid tmp variable creation in for non complex receivers. So far the python frontend emitted tmp variables in the context of call site lowering if the `` in a `.y()` call was anything other than an identifier/name. Now we also avoid emitting tmp variables if `` is a member access chain. E.g. `x.y.z()`. This leads to less clutter and better alias handling. --- .../io/joern/pysrc2cpg/PythonAstVisitor.scala | 15 ++++++- .../io/joern/pysrc2cpg/cpg/CallCpgTests.scala | 43 ++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala b/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala index 5477c1626a5a..eb910326aaab 100644 --- a/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala +++ b/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala @@ -12,6 +12,7 @@ import io.shiftleft.codepropertygraph.generated.nodes.{NewCall, NewIdentifier, N import org.slf4j.LoggerFactory import io.shiftleft.codepropertygraph.generated.DiffGraphBuilder +import scala.annotation.tailrec import scala.collection.mutable object MethodParameters { @@ -1836,6 +1837,18 @@ class PythonAstVisitor( ("and", Operators.logicalAnd) } + @tailrec + private def mayHaveSideEffects(expr: ast.iexpr): Boolean = { + expr match { + case attr: ast.Attribute => + mayHaveSideEffects(attr.value) + case attr: ast.Name => + false + case _ => + true + } + } + /** TODO For now this function compromises on the correctness of the lowering in order to get some data flow tracking * going. * 1. For constructs like x.func() we assume x to be the instance which is passed into func. This is not true since @@ -1862,7 +1875,7 @@ class PythonAstVisitor( createXDotYCall( () => convert(attribute.value), attribute.attr, - xMayHaveSideEffects = !attribute.value.isInstanceOf[ast.Name], + xMayHaveSideEffects = mayHaveSideEffects(attribute.value), lineAndColOf(call), argumentNodes, keywordArgNodes, diff --git a/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/CallCpgTests.scala b/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/CallCpgTests.scala index 7e649ebf5a89..5871be73f77d 100644 --- a/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/CallCpgTests.scala +++ b/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/CallCpgTests.scala @@ -7,7 +7,6 @@ import io.shiftleft.semanticcpg.language.* import java.io.File class CallCpgTests extends PySrc2CpgFixture(withOssDataflow = false) { - "call on identifier" should { lazy val cpg = code("""func(a, b)""".stripMargin, "test.py") @@ -173,6 +172,48 @@ class CallCpgTests extends PySrc2CpgFixture(withOssDataflow = false) { } } + "call on member chain" should { + lazy val cpg = code("""x.y.func(a, b)""".stripMargin, "test.py") + + "test call node properties" in { + val callNode = cpg.call.codeExact("x.y.func(a, b)").head + callNode.name shouldBe "func" + callNode.signature shouldBe "" + callNode.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH + callNode.lineNumber shouldBe Some(1) + } + + "test call receiver" in { + val callNode = cpg.call.codeExact("x.y.func(a, b)").head + val callReceiver = callNode.receiver.head + callReceiver.code shouldBe "x.y.func" + + callNode.astChildren.order(0).head shouldBe callReceiver + callNode.start.argument.b should not contain callReceiver + } + + "test call instance param" in { + val callNode = cpg.call.codeExact("x.y.func(a, b)").head + val instanceArg = callNode.argument(0) + instanceArg.code shouldBe "x.y" + + callNode.astChildren.order(1).head shouldBe instanceArg + } + + "test call arguments" in { + val callNode = cpg.call.codeExact("x.y.func(a, b)").head + val arg1 = callNode.argument(1) + arg1.code shouldBe "a" + + callNode.astChildren.order(2).head shouldBe arg1 + + val arg2 = callNode.argument(2) + arg2.code shouldBe "b" + + callNode.astChildren.order(3).head shouldBe arg2 + } + } + "call following a definition within the same module" should { lazy val cpg = code( """