Skip to content

Commit

Permalink
Fix meta class call handler generation. (#2271)
Browse files Browse the repository at this point in the history
In cases were the "self" parameter is not explicitly given in a class
__init__ function we did crash because we assumed that at least one
positional parameter is always defined. This is now fixed.
  • Loading branch information
ml86 authored Feb 15, 2023
1 parent 575eb75 commit 64ca4c7
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,25 @@ class PythonAstVisitor(
(convertedArgs, convertedKeywordArgs)
}

/** This function strips the first positional parameter from initParameters, if present.
* @return
* Parameters without first positional parameter and adjusted line and column number information.
*/
private def stripFirstPositionalParameter(initParameters: ast.Arguments): (ast.Arguments, LineAndColumn) = {
if (initParameters.posonlyargs.nonEmpty) {
(
initParameters.copy(posonlyargs = initParameters.posonlyargs.tail),
lineAndColOf(initParameters.posonlyargs.head)
)
} else if (initParameters.args.nonEmpty) {
(initParameters.copy(args = initParameters.args.tail), lineAndColOf(initParameters.args.head))
} else if (initParameters.vararg.nonEmpty) {
(initParameters, lineAndColOf(initParameters.vararg.get))
} else {
(initParameters, lineAndColOf(initParameters.kw_arg.get))
}
}

/** Creates the method which handles a call to the meta class object. This process is also known as creating a new
* instance object, e.g. obj = MyClass(p1). The purpose of the generated function is to adapt between the special
* cased instance creation call and a normal call to __new__ (for now <fakeNew>). The adaption is required to in
Expand All @@ -614,15 +633,7 @@ class PythonAstVisitor(

// We need to drop the "self" parameter either from the position only or normal parameters
// because "self" is not passed through but rather created in __new__.
val (parametersWithoutSelf, lineAndColumn) =
if (initParameters.posonlyargs.nonEmpty) {
(
initParameters.copy(posonlyargs = initParameters.posonlyargs.tail),
lineAndColOf(initParameters.posonlyargs.head)
)
} else {
(initParameters.copy(args = initParameters.args.tail), lineAndColOf(initParameters.args.head))
}
val (parametersWithoutSelf, lineAndColumn) = stripFirstPositionalParameter(initParameters)

createMethod(
methodName,
Expand Down Expand Up @@ -671,15 +682,7 @@ class PythonAstVisitor(

// We need to drop the "self" parameter either from the position only or normal parameters
// because "self" is not passed through but rather created in __new__.
val (parametersWithoutSelf, lineAndColumn) =
if (initParameters.posonlyargs.nonEmpty) {
(
initParameters.copy(posonlyargs = initParameters.posonlyargs.tail),
lineAndColOf(initParameters.posonlyargs.head)
)
} else {
(initParameters.copy(args = initParameters.args.tail), lineAndColOf(initParameters.args.head))
}
val (parametersWithoutSelf, lineAndColumn) = stripFirstPositionalParameter(initParameters)

createMethod(
newMethodName,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.joern.pysrc2cpg.cpg

import io.joern.pysrc2cpg.PySrc2CpgFixture
import io.shiftleft.semanticcpg.language._

class ClassCpgTests extends PySrc2CpgFixture(withOssDataflow = false) {
"class meta call handler" should {
"have no self parameter if self is explicit" in {
val cpg = code("""class Foo:
| def __init__(self, x):
| pass
|""".stripMargin)

val handlerMethod = cpg.method.name("<metaClassCallHandler>").head
handlerMethod.fullName shouldBe "Test0.py:<module>.Foo.<metaClassCallHandler>"
handlerMethod.lineNumber shouldBe Some(2)

handlerMethod.parameter.size shouldBe 1
val xParameter = handlerMethod.parameter.head
xParameter.name shouldBe "x"

}

"have no self parameter if self is in varargs" in {
val cpg = code("""class Foo:
| def __init__(*x):
| pass
|""".stripMargin)

val handlerMethod = cpg.method.name("<metaClassCallHandler>").head
handlerMethod.fullName shouldBe "Test0.py:<module>.Foo.<metaClassCallHandler>"
handlerMethod.lineNumber shouldBe Some(2)

handlerMethod.parameter.size shouldBe 1
val xParameter = handlerMethod.parameter.head
xParameter.name shouldBe "x"

}
}

}

0 comments on commit 64ca4c7

Please sign in to comment.