diff --git a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForDeclarationsCreator.scala b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForDeclarationsCreator.scala index 7ab901dc26ca..2fb56f46629c 100644 --- a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForDeclarationsCreator.scala +++ b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForDeclarationsCreator.scala @@ -73,7 +73,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) { val modifiers = astForModifiers(classDecl) val members = astForMembers(classDecl.json(ParserKeys.Members).arr.map(createDotNetNodeInfo).toSeq) ++ addConstructorWithFieldInitializationsIfNeeded(fullName) - // TODO: do the same for static fields + ++ addStaticConstructorWithFieldInitializationsIfNeeded(fullName) scope.popScope() val typeDeclAst = Ast(typeDecl) @@ -93,7 +93,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) { if (shouldBuildCtor) { val methodReturn = newMethodReturnNode(BuiltinTypes.Void, None, None, None) - val signature = composeMethodLikeSignature(BuiltinTypes.Void, Seq.empty) + val signature = composeMethodLikeSignature(methodReturn.typeFullName, Seq.empty) val modifiers = Seq(newModifierNode(ModifierTypes.CONSTRUCTOR), newModifierNode(ModifierTypes.INTERNAL)) val name = Defines.ConstructorMethodName val fullName = composeMethodFullName(typeDeclFullName, name, signature) @@ -127,6 +127,42 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) { } } + private def addStaticConstructorWithFieldInitializationsIfNeeded(typeDeclFullname: String): Seq[Ast] = { + val staticFields = scope.getFieldsInScope.filter(f => f.isStatic && f.isInitialized) + val hasExplicitCtor = + scope.tryResolveTypeReference(typeDeclFullname).exists(_.methods.exists(_.name == Defines.StaticInitMethodName)) + val shouldBuildCtor = staticFields.nonEmpty && !hasExplicitCtor && parseLevel == FULL_AST + + if (shouldBuildCtor) { + val methodReturn = newMethodReturnNode(BuiltinTypes.Void, None, None, None) + val signature = composeMethodLikeSignature(methodReturn.typeFullName, Nil) + val modifiers = Seq( + newModifierNode(ModifierTypes.CONSTRUCTOR), + newModifierNode(ModifierTypes.INTERNAL), + newModifierNode(ModifierTypes.STATIC) + ) + val name = Defines.StaticInitMethodName + val fullName = composeMethodFullName(typeDeclFullname, name, signature) + + val body = { + scope.pushNewScope(MethodScope(fullName)) + val fieldInitAssignmentAsts = astVariableDeclarationForInitializedFields(staticFields) + scope.popScope() + Ast(NewBlock().typeFullName(Defines.Any)).withChildren(fieldInitAssignmentAsts) + } + + val methodNode_ = NewMethod() + .name(name) + .fullName(fullName) + .signature(signature) + .filename(relativeFileName) + + methodAst(methodNode_, Nil, body, methodReturn, modifiers) :: Nil + } else { + Nil + } + } + protected def astForRecordDeclaration(recordDecl: DotNetNodeInfo): Seq[Ast] = { val name = nameFromNode(recordDecl) val fullName = astFullName(recordDecl) diff --git a/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/MemberTests.scala b/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/MemberTests.scala index bd377a570185..31e5aa21da60 100644 --- a/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/MemberTests.scala +++ b/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/MemberTests.scala @@ -58,23 +58,25 @@ class MemberTests extends CSharpCode2CpgFixture { } } - // TODO: Not supported yet. - "have a static constructor" ignore { + "have a static constructor" in { inside(cpg.typeDecl.nameExact("Car").method.nameExact(Defines.StaticInitMethodName).l) { case cctor :: Nil => cctor.fullName shouldBe s"Car.${Defines.StaticInitMethodName}:void()" - cctor.modifier.modifierType.toSet shouldBe Set(ModifierTypes.STATIC, ModifierTypes.CONSTRUCTOR) + cctor.modifier.modifierType.toSet shouldBe Set( + ModifierTypes.STATIC, + ModifierTypes.CONSTRUCTOR, + ModifierTypes.INTERNAL + ) cctor.methodReturn.typeFullName shouldBe "void" case xs => fail(s"Expected single static constructor, but got $xs") } } - // TODO: Not supported yet. - "have the static member initialization inside the static constructor" ignore { + "have the static member initialization inside the static constructor" in { inside(cpg.method.fullNameExact(s"Car.${Defines.StaticInitMethodName}:void()").body.assignment.l) { case assignment :: Nil => - assignment.target.code shouldBe "Car.nonInitMaxSpeed" + assignment.target.code shouldBe "nonInitMaxSpeed" assignment.source.code shouldBe "200" case xs => fail(s"Expected single assignment inside the static constructor, but got $xs")