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

[c#] lower setter declaration into a METHOD node #5271

Merged
merged 1 commit into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import io.joern.csharpsrc2cpg.astcreation.BuiltinTypes.DotNetTypeMap
import io.joern.csharpsrc2cpg.datastructures.*
import io.joern.csharpsrc2cpg.parser.DotNetJsonAst.*
import io.joern.csharpsrc2cpg.parser.{DotNetNodeInfo, ParserKeys}
import io.joern.csharpsrc2cpg.utils.Utils.{composeGetterName, composeMethodFullName, composeMethodLikeSignature}
import io.joern.csharpsrc2cpg.utils.Utils.{
composeGetterName,
composeMethodFullName,
composeMethodLikeSignature,
composeSetterName
}
import io.joern.x2cpg.utils.NodeBuilders.{newMethodReturnNode, newModifierNode}
import io.joern.x2cpg.{Ast, Defines, ValidationMode}
import io.shiftleft.codepropertygraph.generated.*
Expand Down Expand Up @@ -547,11 +552,30 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
private def astForPropertyAccessor(accessorDecl: DotNetNodeInfo, propertyDecl: DotNetNodeInfo): Seq[Ast] = {
accessorDecl.node match
case GetAccessorDeclaration => astForGetAccessorDeclaration(accessorDecl, propertyDecl)
case SetAccessorDeclaration => astForSetAccessorDeclaration(accessorDecl, propertyDecl)
case _ =>
logger.warn(s"Unhandled property accessor '${accessorDecl.node}''")
logger.warn(s"Unhandled property accessor '${accessorDecl.node}'")
Nil
}

private def astForSetAccessorDeclaration(accessorDecl: DotNetNodeInfo, propertyDecl: DotNetNodeInfo): Seq[Ast] = {
val name = composeSetterName(nameFromNode(propertyDecl))
val modifiers = modifiersForNode(propertyDecl)
val returnType = BuiltinTypes.Void
val valueType = nodeTypeFullName(propertyDecl)
val baseType = scope.surroundingTypeDeclFullName.getOrElse(Defines.UnresolvedNamespace)
val isStatic = modifiers.exists(_.modifierType == ModifierTypes.STATIC)
val valueParam = Ast(NewMethodParameterIn().typeFullName(valueType).name("value").index(1))
val parameters = Option.unless(isStatic)(astForThisParameter(propertyDecl)).toList :+ valueParam
val signature = composeMethodLikeSignature(returnType, parameters)
val fullName = composeMethodFullName(baseType, name, signature)
val body = Try(astForBlock(createDotNetNodeInfo(accessorDecl.json(ParserKeys.Body)))).getOrElse(Ast())
val methodReturn = methodReturnNode(accessorDecl, returnType)
val methodNode_ = methodNode(accessorDecl, name, fullName, signature, relativeFileName)

methodAst(methodNode_, parameters, body, methodReturn, modifiers) :: Nil
}

private def astForGetAccessorDeclaration(accessorDecl: DotNetNodeInfo, propertyDecl: DotNetNodeInfo): Seq[Ast] = {
val name = composeGetterName(nameFromNode(propertyDecl))
val modifiers = modifiersForNode(propertyDecl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ object Utils {

def composeGetterName(fieldIdentifierName: String): String = s"get_$fieldIdentifierName"

def composeSetterName(fieldIdentifierName: String): String = s"set_$fieldIdentifierName"

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package io.joern.csharpsrc2cpg.querying.ast

import io.joern.csharpsrc2cpg.testfixtures.CSharpCode2CpgFixture
import io.shiftleft.codepropertygraph.generated.ModifierTypes
import io.shiftleft.codepropertygraph.generated.nodes.Call
import io.shiftleft.semanticcpg.language.*

class PropertySetterTests extends CSharpCode2CpgFixture {

"uninitialized set-only property declaration" should {
val cpg = code("""
|using System;
|class C
|{
| public int MyProperty { set { Console.WriteLine(value); } }
|}
|""".stripMargin)

"be lowered into a set_* method" in {
inside(cpg.method.nameExact("set_MyProperty").l) {
case method :: Nil =>
method.fullName shouldBe "C.set_MyProperty:void(C,System.Int32)"
method.signature shouldBe "void(C,System.Int32)"
case xs => fail(s"Expected single set_MyProperty method, but got $xs")
}
}

"have correct modifiers" in {
cpg.method.nameExact("set_MyProperty").modifier.modifierType.sorted.l shouldBe List(ModifierTypes.PUBLIC)
}

"have correct parameters" in {
inside(cpg.method.nameExact("set_MyProperty").parameter.sortBy(_.index).l) {
case thisArg :: valueArg :: Nil =>
thisArg.index shouldBe 0
thisArg.name shouldBe "this"
thisArg.typeFullName shouldBe "C"

valueArg.index shouldBe 1
valueArg.name shouldBe "value"
valueArg.typeFullName shouldBe "System.Int32"
case xs => fail(s"Expected two arguments to set_MyProperty, but got $xs")
}
}

"have correct body" in {
inside(cpg.method.nameExact("set_MyProperty").body.flatMap(_.astChildren).l) {
case (writeLine: Call) :: Nil =>
writeLine.code shouldBe "Console.WriteLine(value)"
writeLine.methodFullName shouldBe "System.Console.WriteLine:System.Void(System.Boolean)"
case xs => fail(s"Expected single node inside set_MyProperty's body, but got $xs")
}
}
}

"uninitialized static set-only property declaration" should {
val cpg = code("""
|class C
|{
| public static int MyProperty { set { } }
|}
|""".stripMargin)

"be lowered into a set_* method" in {
inside(cpg.method.nameExact("set_MyProperty").l) {
case method :: Nil =>
method.fullName shouldBe "C.set_MyProperty:void(System.Int32)"
method.signature shouldBe "void(System.Int32)"
case xs => fail(s"Expected single set_MyProperty method, but got $xs")
}
}

"have correct modifiers" in {
cpg.method.nameExact("set_MyProperty").modifier.modifierType.sorted.l shouldBe List(
ModifierTypes.PUBLIC,
ModifierTypes.STATIC
)
}

"have correct parameters" in {
inside(cpg.method.nameExact("set_MyProperty").parameter.sortBy(_.index).l) {
case valueArg :: Nil =>
valueArg.index shouldBe 1
valueArg.name shouldBe "value"
valueArg.typeFullName shouldBe "System.Int32"
case xs => fail(s"Expected two arguments to set_MyProperty, but got $xs")
}
}

"have correct body" in {
cpg.method.nameExact("set_MyProperty").body.flatMap(_.astChildren) shouldBe empty
}
}

}
Loading