Skip to content

Commit

Permalink
Very basic static call structure is in place
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidBakerEffendi committed Jan 11, 2024
1 parent 0bbe134 commit 5ca27ca
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class AstCreator(val relativeFileName: String, val parserResult: ParserResult, v
case IdentifierName => Seq(astForIdentifier(nodeInfo))
case LocalDeclarationStatement => astForLocalDeclarationStatement(nodeInfo)
case GlobalStatement => astForGlobalStatement(nodeInfo)
case _: LiteralExpr => Seq(astForLiteralExpression(nodeInfo))
case _: LiteralExpr => astForLiteralExpression(nodeInfo)
case _ => notHandledYet(nodeInfo)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
BuiltinTypes.Float
case NumericLiteralExpression if node.code.matches("^\\d+\\.?\\d*[m|M]?$") => // e.g. 2m or 2.1M
BuiltinTypes.Decimal
case StringLiteralExpression if node.code.matches("^\"\\w+\"$") => BuiltinTypes.String
case StringLiteralExpression => BuiltinTypes.DotNetTypeMap(BuiltinTypes.String)
case IdentifierName =>
// TODO: Look at scope object for possible types
"ANY"
case _ =>
Try(createDotNetNodeInfo(node.json(ParserKeys.Type))) match
case Success(typeNode) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@ package io.joern.csharpsrc2cpg.astcreation

import io.joern.csharpsrc2cpg.parser.DotNetJsonAst.*
import io.joern.csharpsrc2cpg.parser.{DotNetNodeInfo, ParserKeys}
import io.joern.x2cpg.{Ast, ValidationMode}
import io.shiftleft.codepropertygraph.generated.nodes.NewCall
import io.joern.x2cpg.{Ast, Defines, ValidationMode}
import io.shiftleft.codepropertygraph.generated.nodes.{NewCall, NewMethodParameterIn}
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators}

trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) { this: AstCreator =>

def astForExpression(expr: DotNetNodeInfo): Seq[Ast] = {
val expressionNode = createDotNetNodeInfo(expr.json(ParserKeys.Expression))
expressionNode.node match
case _: UnaryExpr => astForUnaryExpression(expressionNode)
case _: BinaryExpr => astForBinaryExpression(expressionNode)
case _ => notHandledYet(expressionNode)
case _: UnaryExpr => astForUnaryExpression(expressionNode)
case _: BinaryExpr => astForBinaryExpression(expressionNode)
case _: LiteralExpr => astForLiteralExpression(expressionNode)
case InvocationExpression => astForInvocationExpression(expressionNode)
case _ => notHandledYet(expressionNode)
}

protected def astForLiteralExpression(_literalNode: DotNetNodeInfo): Ast = {
Ast(literalNode(_literalNode, code(_literalNode), nodeTypeFullName(_literalNode)))
protected def astForLiteralExpression(_literalNode: DotNetNodeInfo): Seq[Ast] = {
Seq(Ast(literalNode(_literalNode, code(_literalNode), nodeTypeFullName(_literalNode))))
}

private def astForUnaryExpression(unaryExpr: DotNetNodeInfo): Seq[Ast] = {
val operatorToken = unaryExpr.json(ParserKeys.OperatorToken)(ParserKeys.Value).str
val operatorName = operatorToken match
Expand Down Expand Up @@ -79,8 +82,52 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
protected def astForEqualsValueClause(clause: DotNetNodeInfo): Seq[Ast] = {
val rhsNode = createDotNetNodeInfo(clause.json(ParserKeys.Value))
rhsNode.node match
case _: LiteralExpr => Seq(astForLiteralExpression(rhsNode))
case _: LiteralExpr => astForLiteralExpression(rhsNode)
case _ => notHandledYet(rhsNode)
}

private def astForInvocationExpression(invocationExpr: DotNetNodeInfo): Seq[Ast] = {
val dispatchType = DispatchTypes.STATIC_DISPATCH // TODO
val typeFullName = None // TODO
val arguments = astForArgumentList(createDotNetNodeInfo(invocationExpr.json(ParserKeys.ArgumentList)))
val signature = Option(
s"${typeFullName
.getOrElse("ANY")}:(${arguments.flatMap(_.root).collect { case x: NewMethodParameterIn => x.typeFullName }.mkString(",")})"
)

val expression = createDotNetNodeInfo(invocationExpr.json(ParserKeys.Expression))
val name = nameFromNode(createDotNetNodeInfo(expression.json(ParserKeys.Name)))

val (receiver, baseTypeFullName) = expression.node match
case SimpleMemberAccessExpression =>
val baseNode = createDotNetNodeInfo(
createDotNetNodeInfo(invocationExpr.json(ParserKeys.Expression)).json(ParserKeys.Expression)
)
val baseIdentifier =
identifierNode(baseNode, nameFromNode(baseNode), code(baseNode), nodeTypeFullName(baseNode))
(Option(Ast(baseIdentifier)), Option(baseIdentifier.typeFullName))
case _ => (None, None)

// TODO: Handle signature
val methodFullName = baseTypeFullName match
case Some(typeFullName) => s"$typeFullName.$name"
case None => s"${Defines.UnresolvedNamespace}.$name"

val _callAst = callAst(
callNode(invocationExpr, code(invocationExpr), name, methodFullName, dispatchType, signature, typeFullName),
arguments,
receiver
)
Seq(_callAst)
}

private def astForArgumentList(argumentList: DotNetNodeInfo): Seq[Ast] = {
argumentList
.json(ParserKeys.Arguments)
.arr
.map(createDotNetNodeInfo)
.flatMap(astForExpression)
.toSeq
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@ class CSharpScope extends Scope[String, DeclarationNew, ScopeType] {
case ScopeElement(TypeScope(fullName), _) => fullName
}

/** @return
* the type of the surrounding scope.
*/
def surroundingScopeType: Option[ScopeType] = stack.collectFirst { x => x.scopeNode }

/** @return
* true if the scope is currently on the top-level, false if the scope is within some nested scope.
*/
def isTopLevel = stack
def isTopLevel: Boolean = stack
.filterNot(x => x.scopeNode.isInstanceOf[NamespaceScope])
.exists(x => x.scopeNode.isInstanceOf[MethodScope] || x.scopeNode.isInstanceOf[TypeScope])

def tryResolveTypeReference(typeName: String): Option[String] = {
// TODO: Look for a type with a matching name in scope
None
}

def tryResolveMethodInvocation(typeFullName: String, callName: String): Option[String] = {
// TODO: Use type map to resolve method full name
None
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ object DotNetJsonAst {

object FieldDeclaration extends DeclarationExpr

object VariableDeclaration extends DeclarationExpr
object VariableDeclaration extends DeclarationExpr

object LocalDeclarationStatement extends DeclarationExpr

object VariableDeclarator extends DeclarationExpr
Expand Down Expand Up @@ -115,42 +116,55 @@ object DotNetJsonAst {
object BitwiseAndExpression extends BinaryExpr
object BitwiseOrExpression extends BinaryExpr
object ExclusiveOrExpression extends BinaryExpr

object InvocationExpression extends BaseExpr

object Argument extends BaseExpr

object ArgumentList extends BaseExpr

trait MemberAccessExpr extends BaseExpr

object SimpleMemberAccessExpression extends MemberAccessExpr
}

/** The JSON key values, in alphabetical order.
*/
object ParserKeys {

val AstRoot = "AstRoot"
val Arguments = "Arguments"
val ArgumentList = "ArgumentList"
val Body = "Body"
val Code = "Code"
val ColumnStart = "ColumnStart"
val ColumnEnd = "ColumnEnd"
val Declaration = "Declaration"
val ElementType = "ElementType"
val Expression = "Expression"
val FileName = "FileName"
val Identifier = "Identifier"
val Initializer = "Initializer"
val MetaData = "MetaData"
val Keyword = "Keyword"
val Kind = "Kind"
val Left = "Left"
val LineStart = "LineStart"
val LineEnd = "LineEnd"
val MetaData = "MetaData"
val Members = "Members"
val Modifiers = "Modifiers"
val Name = "Name"
val Operand = "Operand"
val OperatorToken = "OperatorToken"
val Parameters = "Parameters"
val ParameterList = "ParameterList"
val Statement = "Statement"
val Statements = "Statements"
val ReturnType = "ReturnType"
val Right = "Right"
val Type = "Type"
val Usings = "Usings"
val Value = "Value"
val Variables = "Variables"
val Statements = "Statements"
val Expression = "Expression"
val OperatorToken = "OperatorToken"
val Operand = "Operand"
val Statement = "Statement"

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

import io.joern.csharpsrc2cpg.testfixtures.CSharpCode2CpgFixture
import io.shiftleft.codepropertygraph.generated.nodes.{Identifier, Literal}
import io.shiftleft.semanticcpg.language.*

class CallTests extends CSharpCode2CpgFixture {
Expand All @@ -10,10 +11,26 @@ class CallTests extends CSharpCode2CpgFixture {
val cpg = code(basicBoilerplate())

"create a call node with arguments" in {
inside(cpg.call.nameExact("WriteLine").headOption) {
case Some(writeLine) =>
writeLine.name shouldBe "WriteLine"
case None => fail("Node not found!")
val writeLine = cpg.call.nameExact("WriteLine").headOption match
case Some(callNode) => callNode
case None => fail("Node not found!")

writeLine.name shouldBe "WriteLine"
// writeLine.methodFullName shouldBe "System.Console.WriteLine" TODO: Handle when call graph is being done
// writeLine.typeFullName shouldBe "void"
writeLine.code shouldBe "Console.WriteLine(\"Hello, world!\")"

inside(writeLine.argument.l) {
case (base: Identifier) :: (strArg: Literal) :: Nil =>
// base.typeFullName shouldBe "System.Console"
base.name shouldBe "Console"
base.code shouldBe "Console"
base.argumentIndex shouldBe 0

strArg.typeFullName shouldBe "System.String"
strArg.code shouldBe "\"Hello, world!\""
strArg.argumentIndex shouldBe 1
case _ => fail("Arguments malformed or not found!")
}
}

Expand Down

0 comments on commit 5ca27ca

Please sign in to comment.