Skip to content

Commit

Permalink
[c#] create implicit ctor for field inits
Browse files Browse the repository at this point in the history
  • Loading branch information
xavierpinho committed Jan 6, 2025
1 parent ec2b517 commit a8b16ea
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.joern.csharpsrc2cpg.astcreation

import io.joern.csharpsrc2cpg.CSharpModifiers
import io.joern.csharpsrc2cpg.astcreation.AstParseLevel.FULL_AST
import io.joern.csharpsrc2cpg.astcreation.BuiltinTypes.DotNetTypeMap
import io.joern.csharpsrc2cpg.datastructures.*
import io.joern.csharpsrc2cpg.parser.DotNetJsonAst.*
Expand Down Expand Up @@ -70,11 +71,9 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
typeDeclNode(classDecl, name, fullName, relativeFileName, code(classDecl), inherits = inheritsFromTypeFullName)
scope.pushNewScope(TypeScope(fullName))
val modifiers = astForModifiers(classDecl)
val members = astForMembers(classDecl.json(ParserKeys.Members).arr.map(createDotNetNodeInfo).toSeq)

// TODO: Check if any explicit constructor / static constructor decls exists,
// if it doesn't, need to add in default constructor and static constructor and
// pull all field initializations into them.
val members = astForMembers(classDecl.json(ParserKeys.Members).arr.map(createDotNetNodeInfo).toSeq)
++ addConstructorWithFieldInitializationsIfNeeded(fullName)
// TODO: do the same for static fields

scope.popScope()
val typeDeclAst = Ast(typeDecl)
Expand All @@ -84,6 +83,50 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
Seq(typeDeclAst)
}

private def addConstructorWithFieldInitializationsIfNeeded(typeDeclFullName: String): Seq[Ast] = {
val dynamicFields = scope.getFieldsInScope.filter(f => !f.isStatic && f.isInitialized)
val hasExplicitCtor =
scope.tryResolveTypeReference(typeDeclFullName).exists(_.methods.exists(_.name == Defines.ConstructorMethodName))
// We should only create the constructor when we are the FULL_AST parseLevel. Otherwise, hasExplicitCtor will
// not be accurate.
val shouldBuildCtor = dynamicFields.nonEmpty && !hasExplicitCtor && parseLevel == FULL_AST

if (shouldBuildCtor) {
val methodReturn = newMethodReturnNode(BuiltinTypes.Void, None, None, None)
val signature = composeMethodLikeSignature(BuiltinTypes.Void, Seq.empty)
val modifiers = Seq(newModifierNode(ModifierTypes.CONSTRUCTOR), newModifierNode(ModifierTypes.INTERNAL))
val name = Defines.ConstructorMethodName
val fullName = composeMethodFullName(typeDeclFullName, name, signature)

val body = {
scope.pushNewScope(MethodScope(fullName))
val fieldInitAssignmentAsts = astVariableDeclarationForInitializedFields(dynamicFields)
scope.popScope()
Ast(NewBlock().typeFullName(Defines.Any)).withChildren(fieldInitAssignmentAsts)
}

val methodNode_ = NewMethod()
.name(name)
.fullName(fullName)
.signature(signature)
.filename(relativeFileName)

val parameterNodes = Seq(
NewMethodParameterIn()
.name("this")
.code("this")
.typeFullName(typeDeclFullName)
.evaluationStrategy(EvaluationStrategies.BY_SHARING.name)
.isVariadic(false)
.index(0)
)

methodAst(methodNode_, parameterNodes.map(Ast(_)), body, methodReturn, modifiers) :: Nil
} else {
Seq.empty
}
}

protected def astForRecordDeclaration(recordDecl: DotNetNodeInfo): Seq[Ast] = {
val name = nameFromNode(recordDecl)
val fullName = astFullName(recordDecl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ class MemberTests extends CSharpCode2CpgFixture {
}
}

// TODO: Not supported yet.
"have a constructor" ignore {
"have a constructor" in {
inside(cpg.typeDecl.nameExact("Car").method.nameExact(Defines.ConstructorMethodName).l) {
case ctor :: Nil =>
ctor.fullName shouldBe s"Car.${Defines.ConstructorMethodName}:void()"
Expand All @@ -113,12 +112,11 @@ class MemberTests extends CSharpCode2CpgFixture {
}
}

// TODO: Not supported yet.
"have the member initialization inside the constructor" ignore {
"have the member initialization inside the constructor" in {
inside(cpg.method.fullNameExact(s"Car.${Defines.ConstructorMethodName}:void()").body.assignment.l) {
case assignment :: Nil =>
assignment.target.code shouldBe "Car.nonInitMaxSpeed"
assignment.source.code shouldBe "200"
assignment.target.code shouldBe "color"
assignment.source.code shouldBe "\"red\""
case xs =>
fail(s"Expected single assignment inside the constructor, but got $xs")
}
Expand Down

0 comments on commit a8b16ea

Please sign in to comment.