Skip to content

Commit

Permalink
[C#] Asts for method, class and member annotations (#4385)
Browse files Browse the repository at this point in the history
Includes ast creation for member, class and method annotations.
Resolves #4386
  • Loading branch information
karan-batavia authored Mar 23, 2024
1 parent 7fe68a2 commit 1e81ceb
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
csharpsrc2cpg {
dotnetastgen_version: "0.27.0"
dotnetastgen_version: "0.28.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
.map(_.name)
.orElse(BuiltinTypes.DotNetTypeMap.get(typeString))
.getOrElse(typeString)
case Attribute =>
val typeString = s"${nameFromNode(node)}Attribute"
scope
.tryResolveTypeReference(typeString)
.map(_.name)
.orElse(BuiltinTypes.DotNetTypeMap.get(typeString))
.getOrElse(typeString)
case _ =>
Try(node.json(ParserKeys.Type)).map(createDotNetNodeInfo) match
case Success(typeNode) =>
Expand Down Expand Up @@ -183,7 +190,7 @@ object AstCreatorHelper {
case IdentifierName | Parameter | _: DeclarationExpr | GenericName =>
nameFromIdentifier(node)
case QualifiedName => nameFromQualifiedName(node)
case SimpleMemberAccessExpression | MemberBindingExpression | SuppressNullableWarningExpression =>
case SimpleMemberAccessExpression | MemberBindingExpression | SuppressNullableWarningExpression | Attribute =>
nameFromIdentifier(createDotNetNodeInfo(node.json(ParserKeys.Name)))
case ObjectCreationExpression | CastExpression => nameFromNode(createDotNetNodeInfo(node.json(ParserKeys.Type)))
case ThisExpression => "this"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.joern.csharpsrc2cpg.astcreation
import io.joern.csharpsrc2cpg.CSharpModifiers
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.{composeMethodFullName, composeMethodLikeSignature}
import io.joern.x2cpg.utils.NodeBuilders.{newMethodReturnNode, newModifierNode}
Expand All @@ -12,6 +13,7 @@ import io.shiftleft.codepropertygraph.generated.nodes.*
import io.shiftleft.proto.cpg.Cpg.EvaluationStrategies

import scala.annotation.tailrec
import scala.collection.mutable.ArrayBuffer
import scala.util.Try

trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) { this: AstCreator =>
Expand Down Expand Up @@ -59,6 +61,15 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {

inheritsFromTypeFullName.foreach(scope.pushTypeToScope)

val annotationAsts =
classDecl
.json(ParserKeys.AttributeLists)
.arrOpt
.getOrElse(ArrayBuffer.empty[ujson.Value])
.map(createDotNetNodeInfo)
.flatMap(astForAttributeLists)
.toSeq

val typeDecl =
typeDeclNode(classDecl, name, fullName, relativeFileName, code(classDecl), inherits = inheritsFromTypeFullName)
scope.pushNewScope(TypeScope(fullName))
Expand All @@ -73,6 +84,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
val typeDeclAst = Ast(typeDecl)
.withChildren(modifiers)
.withChildren(members)
.withChildren(annotationAsts)
Seq(typeDeclAst)
}

Expand All @@ -99,12 +111,22 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
Ast(memberNode(paramNode, name, paramNode.code, typeFullName))
}

val annotationAsts =
recordDecl
.json(ParserKeys.AttributeLists)
.arrOpt
.getOrElse(ArrayBuffer.empty[ujson.Value])
.map(createDotNetNodeInfo)
.flatMap(astForAttributeLists)
.toSeq

val members =
astForMembers(recordDecl.json(ParserKeys.Members).arr.map(createDotNetNodeInfo).toSeq) ++ membersFromParams
scope.popScope()
val typeDeclAst = Ast(typeDecl)
.withChildren(modifiers)
.withChildren(members)
.withChildren(annotationAsts)
Seq(typeDeclAst)
}

Expand All @@ -119,11 +141,21 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
scope.pushNewScope(EnumScope(fullName, aliasFor))
val modifiers = astForModifiers(enumDecl)

val annotationAsts =
enumDecl
.json(ParserKeys.AttributeLists)
.arrOpt
.getOrElse(ArrayBuffer.empty[ujson.Value])
.map(createDotNetNodeInfo)
.flatMap(astForAttributeLists)
.toSeq

val members = astForMembers(enumDecl.json(ParserKeys.Members).arr.map(createDotNetNodeInfo).toSeq)
scope.popScope()
val typeDeclAst = Ast(typeDecl)
.withChildren(modifiers)
.withChildren(members)
.withChildren(annotationAsts)
Seq(typeDeclAst)
}

Expand Down Expand Up @@ -157,10 +189,19 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
val declarationNode = createDotNetNodeInfo(fieldDecl.json(ParserKeys.Declaration))
val declAsts = astForVariableDeclaration(declarationNode, isStatic)

val annotationAsts =
fieldDecl
.json(ParserKeys.AttributeLists)
.arrOpt
.getOrElse(ArrayBuffer.empty[ujson.Value])
.map(createDotNetNodeInfo)
.flatMap(astForAttributeLists)
.toSeq

val memberNodes = declAsts
.flatMap(_.nodes.collectFirst { case x: NewIdentifier => x })
.map(x => memberNode(declarationNode, x.name, code(declarationNode), x.typeFullName))
memberNodes.map(Ast(_).withChildren(astForModifiers(fieldDecl)))
memberNodes.map(Ast(_).withChildren(annotationAsts).withChildren(astForModifiers(fieldDecl)))
}

protected def astForLocalDeclarationStatement(localDecl: DotNetNodeInfo): Seq[Ast] = {
Expand Down Expand Up @@ -317,6 +358,15 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
.map(astForParameter(_, _, None))
.toSeq

val annotationAsts =
methodDecl
.json(ParserKeys.AttributeLists)
.arrOpt
.getOrElse(ArrayBuffer.empty[ujson.Value])
.map(createDotNetNodeInfo)
.flatMap(astForAttributeLists)
.toSeq

val methodReturnAstNode = createDotNetNodeInfo(methodDecl.json(ParserKeys.ReturnType))
val methodReturn = methodReturnNode(methodReturnAstNode, nodeTypeFullName(methodReturnAstNode))
val signature =
Expand All @@ -335,7 +385,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
val thisNode =
if (!modifiers.exists(_.modifierType == ModifierTypes.STATIC)) astForThisParameter(methodDecl)
else Ast()
Seq(methodAst(methodNode_, thisNode +: params, body, methodReturn, modifiers))
Seq(methodAstWithAnnotations(methodNode_, thisNode +: params, body, methodReturn, modifiers, annotationAsts))
}

private def methodSignature(methodReturn: NewMethodReturn, params: Seq[NewMethodParameterIn]): String = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,4 +550,18 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {

Seq(callAst(fieldAccess, Seq(Ast(identifier)) ++ Seq(fieldIdentifierAst)))
}

protected def astForAttributeLists(attributeList: DotNetNodeInfo): Seq[Ast] = {
attributeList.json(ParserKeys.Attributes).arr.map(createDotNetNodeInfo).map(astForAttribute).toSeq
}

private def astForAttribute(attribute: DotNetNodeInfo): Ast = {
val attributeName = nameFromNode(attribute)
val fullName = nodeTypeFullName(attribute)
val argumentAsts =
Try(astForArgumentList(createDotNetNodeInfo(attribute.json(ParserKeys.ArgumentList)))).getOrElse(Seq.empty[Ast])

val _annotationNode = annotationNode(attribute, attribute.code, attributeName, fullName)
annotationAst(_annotationNode, argumentAsts)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ object DotNetJsonAst {

object SuppressNullableWarningExpression extends BaseExpr

object AttributeList extends BaseExpr

object Attribute extends BaseExpr

object Unknown extends DotNetParserNode

}
Expand All @@ -263,6 +267,8 @@ object ParserKeys {
val AstRoot = "AstRoot"
val Arguments = "Arguments"
val ArgumentList = "ArgumentList"
val AttributeLists = "AttributeLists"
val Attributes = "Attributes"
val BaseList = "BaseList"
val Body = "Body"
val Block = "Block"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.joern.csharpsrc2cpg.querying.ast

import io.joern.csharpsrc2cpg.testfixtures.CSharpCode2CpgFixture
import io.shiftleft.semanticcpg.language.*

class AnnotationTests extends CSharpCode2CpgFixture {
"annotations for methods" should {
"have correct attributes" in {
val cpg = code("""
|using System;
|
|namespace Foo {
| public class Bar {
| [Obsolete("Dep Method", false)]
| public static void Main() {}
| }
|}
|""".stripMargin)

inside(cpg.method("Main").annotation.l) { case obsolete :: Nil =>
obsolete.code shouldBe "Obsolete(\"Dep Method\", false)"
obsolete.name shouldBe "Obsolete"
obsolete.lineNumber shouldBe Some(5)
obsolete.columnNumber shouldBe Some(4)
obsolete.fullName shouldBe "System.ObsoleteAttribute"
}
}
}

"annotations for classes" should {
"have correct attributes" in {
val cpg = code("""
|using System;
|
|namespace Foo {
| [Obsolete("Dep Class", false)]
| public class Bar {
| public static void Main() {}
| }
|}
|""".stripMargin)

inside(cpg.typeDecl("Bar").annotation.l) { case obsolete :: Nil =>
obsolete.code shouldBe "Obsolete(\"Dep Class\", false)"
obsolete.name shouldBe "Obsolete"
obsolete.lineNumber shouldBe Some(4)
obsolete.columnNumber shouldBe Some(2)
obsolete.fullName shouldBe "System.ObsoleteAttribute"
}
}
}

"annotations for members" should {
"have correct attributes" in {
val cpg = code("""
|using System;
|
|namespace Foo {
| public class Bar {
| [Serializable] public string firstName;
| }
|}
|""".stripMargin)

inside(cpg.member("firstName").annotation.l) { case serializable :: Nil =>
serializable.code shouldBe "Serializable"
serializable.name shouldBe "Serializable"
serializable.lineNumber shouldBe Some(5)
serializable.columnNumber shouldBe Some(4)
serializable.fullName shouldBe "System.SerializableAttribute"
}
}
}
}

0 comments on commit 1e81ceb

Please sign in to comment.