Skip to content

Commit

Permalink
handle basic operators
Browse files Browse the repository at this point in the history
  • Loading branch information
karan-batavia committed Jan 4, 2024
1 parent a5f3198 commit 965b769
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
csharpsrc2cpg {
dotnetastgen_version: "0.11.0"
dotnetastgen_version: "0.12.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class AstCreator(val relativeFileName: String, val parserResult: ParserResult, v
case EqualsValueClause => astForEqualsValueClause(nodeInfo)
case UsingDirective => notHandledYet(nodeInfo)
case Block => notHandledYet(nodeInfo)
case ExpressionStatement => astForExpression(nodeInfo)
case _ => notHandledYet(nodeInfo)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
}

private def astForMethodBody(body: DotNetNodeInfo): Ast = {
val block = blockNode(body)
val statements = List.empty // TODO
val block = blockNode(body)
methodAstParentStack.push(block)
val statements = body.json(ParserKeys.Statements).arr.flatMap(astForNode).toList
methodAstParentStack.pop()
blockAst(block, statements)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,90 @@
package io.joern.csharpsrc2cpg.astcreation

import io.joern.csharpsrc2cpg.parser.DotNetJsonAst.LiteralExpr
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.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators}

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

def astForExpression(expr: DotNetNodeInfo): Seq[Ast] = {
// TODO: Handle identifiers in operators
val expressionNode = createDotNetNodeInfo(expr.json(ParserKeys.Expression))
expressionNode.node match
case _: UnaryExpr => astForUnaryExpression(expressionNode)
case _: BinaryExpr => astForBinaryExpression(expressionNode)
case _ => notHandledYet(expressionNode)
}

private def astForUnaryExpression(unaryExpr: DotNetNodeInfo): Seq[Ast] = {
val operatorToken = unaryExpr.json(ParserKeys.OperatorToken)(ParserKeys.Value).toString.replaceAll("\"", "")
val operatorName = operatorToken match
case "+" => Operators.plus
case "-" => Operators.minus
case "++" =>
if (unaryExpr.node.getClass == PostIncrementExpression.getClass) Operators.postIncrement
else Operators.preIncrement
case "--" =>
if (unaryExpr.node.getClass == PostDecrementExpression.getClass) Operators.postDecrement
else Operators.preDecrement
case "~" => Operators.not
case "!" => Operators.logicalNot
case "&" => Operators.addressOf

Seq(callAst(createCallNodeForOperator(unaryExpr, operatorName, typeFullName = Some("")))) // TODO: typeFullName
}
private def astForBinaryExpression(binaryExpr: DotNetNodeInfo): Seq[Ast] = {
val operatorToken = binaryExpr.json(ParserKeys.OperatorToken)(ParserKeys.Value).toString.replaceAll("\"", "")
val operatorName = operatorToken match
case "+" => Operators.addition
case "-" => Operators.subtraction
case "*" => Operators.multiplication
case "/" => Operators.division
case "%" => Operators.modulo
case "==" => Operators.equals
case "!=" => Operators.notEquals
case "&&" => Operators.logicalAnd
case "||" => Operators.logicalOr
case "+=" => Operators.assignmentPlus
case "-=" => Operators.assignmentMinus
case "*=" => Operators.assignmentMultiplication
case "/=" => Operators.assignmentDivision
case "%=" => Operators.assignmentModulo
case "&=" => Operators.assignmentAnd
case "|=" => Operators.assignmentOr
case "^=" => Operators.assignmentXor
case ">>=" => Operators.assignmentLogicalShiftRight
case "<<=" => Operators.assignmentShiftLeft
case ">" => Operators.greaterThan
case "<" => Operators.lessThan
case ">=" => Operators.greaterEqualsThan
case "<=" => Operators.lessEqualsThan
case "|" => Operators.or
case "&" => Operators.and
case "^" => Operators.xor

val args = astForNode(binaryExpr.json(ParserKeys.Left)) ++: astForNode(binaryExpr.json(ParserKeys.Right))
val cNode =
createCallNodeForOperator(binaryExpr, operatorName, typeFullName = Some("")) // TODO: Resolve typeFullName
Seq(callAst(cNode, args))
}

protected def astForEqualsValueClause(clause: DotNetNodeInfo): Seq[Ast] = {
val rhsNode = createDotNetNodeInfo(clause.json(ParserKeys.Value))
rhsNode.node match
case _: LiteralExpr => Seq(Ast(literalNode(rhsNode, code(rhsNode), nodeTypeFullName(rhsNode))))
case _ => notHandledYet(rhsNode)
}

private def createCallNodeForOperator(
node: DotNetNodeInfo,
operatorMethod: String,
DispatchType: String = DispatchTypes.STATIC_DISPATCH,
signature: Option[String] = None,
typeFullName: Option[String] = None
): NewCall = {
callNode(node, node.code, operatorMethod, operatorMethod, DispatchType, signature, typeFullName)
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.joern.csharpsrc2cpg.parser

import io.joern.csharpsrc2cpg.parser.DotNetJsonAst.BaseExpr
import org.slf4j.LoggerFactory

object DotNetJsonAst {
Expand All @@ -26,7 +27,8 @@ object DotNetJsonAst {

sealed trait BaseExpr extends DotNetParserNode

object NotHandledType extends DotNetParserNode
object ExpressionStatement extends BaseExpr
object NotHandledType extends DotNetParserNode

object CompilationUnit extends BaseExpr

Expand Down Expand Up @@ -70,6 +72,45 @@ object DotNetJsonAst {

object QualifiedName extends IdentifierNode

sealed trait UnaryExpr extends BaseExpr
object PostIncrementExpression extends UnaryExpr
object PostDecrementExpression extends UnaryExpr
object PreIncrementExpression extends UnaryExpr
object PreDecrementExpression extends UnaryExpr
object UnaryPlusExpression extends UnaryExpr
object UnaryMinusExpression extends UnaryExpr
object BitwiseNotExpression extends UnaryExpr
object LogicalNotExpression extends UnaryExpr
object AddressOfExpression extends UnaryExpr

sealed trait BinaryExpr extends BaseExpr
object AddExpression extends BinaryExpr
object SubtractExpression extends BinaryExpr
object MultiplyExpression extends BinaryExpr
object DivideExpression extends BinaryExpr
object ModuloExpression extends BinaryExpr
object EqualsExpression extends BinaryExpr
object NotEqualsExpression extends BinaryExpr
object LogicalAndExpression extends BinaryExpr
object LogicalOrExpression extends BinaryExpr
object AddAssignmentExpression extends BinaryExpr
object SubtractAssignmentExpression extends BinaryExpr
object MultiplyAssignmentExpression extends BinaryExpr
object DivideAssignmentExpression extends BinaryExpr
object ModuloAssignmentExpression extends BinaryExpr
object AndAssignmentExpression extends BinaryExpr
object OrAssignmentExpression extends BinaryExpr
object ExclusiveOrAssignmentExpression extends BinaryExpr
object RightShiftAssignmentExpression extends BinaryExpr
object LeftShiftAssignmentExpression extends BinaryExpr

object GreaterThanExpression extends BinaryExpr
object LessThanExpression extends BinaryExpr
object GreaterThanOrEqualExpression extends BinaryExpr
object LessThanOrEqualExpression extends BinaryExpr
object BitwiseAndExpression extends BinaryExpr
object BitwiseOrExpression extends BinaryExpr
object ExclusiveOrExpression extends BinaryExpr
}

/** The JSON key values, in alphabetical order.
Expand Down Expand Up @@ -103,5 +144,9 @@ object ParserKeys {
val Usings = "Usings"
val Value = "Value"
val Variables = "Variables"
val Statements = "Statements"
val Expression = "Expression"
val OperatorToken = "OperatorToken"
val Operand = "Operand"

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

import io.joern.csharpsrc2cpg.parser.DotNetJsonAst.LiteralExpr
import io.joern.csharpsrc2cpg.testfixtures.CSharpCode2CpgFixture
import io.shiftleft.codepropertygraph.generated.Operators
import io.shiftleft.codepropertygraph.generated.nodes.Identifier
import io.shiftleft.semanticcpg.language.*

class OperatorsTests extends CSharpCode2CpgFixture {
"AST nodes for operators" should {
"be created for unary operators" in {
val cpg = code(
basicBoilerplate("""
|int i = 3;
|i++;
|i--;
|++i;
|--i;
|!i;
|~i;
|+5;
|-5;
|&i;
|""".stripMargin),
"Program.cs"
)

val operatorCalls = cpg.method("Main").ast.isCall.nameNot(Operators.assignment).l
operatorCalls.size shouldBe 9
operatorCalls.name.l shouldBe List(
"<operator>.postIncrement",
"<operator>.postDecrement",
"<operator>.preIncrement",
"<operator>.preDecrement",
"<operator>.logicalNot",
"<operator>.not",
"<operator>.plus",
"<operator>.minus",
"<operator>.addressOf"
)
operatorCalls.code.l shouldBe List("i++", "i--", "++i", "--i", "!i", "~i", "+5", "-5", "&i")
// TODO: Tests for operands
}
}

"be created for binary operators" in {
val cpg = code(
basicBoilerplate("""
|int a = 3;
|int b = 5;
|a+b;
|a-b;
|a/b;
|a%b;
|a==b;
|a!=b;
|a&&b;
|a||b;
|a&b;
|a|b;
|a^b;
|""".stripMargin),
fileName = "Program.cs"
)
val operatorCalls = cpg.method("Main").ast.isCall.nameNot(Operators.assignment).l
operatorCalls.size shouldBe 11
operatorCalls.name.l shouldBe List(
"<operator>.addition",
"<operator>.subtraction",
"<operator>.division",
"<operator>.modulo",
"<operator>.equals",
"<operator>.notEquals",
"<operator>.logicalAnd",
"<operator>.logicalOr",
"<operator>.and",
"<operator>.or",
"<operator>.xor"
)
operatorCalls.code.l shouldBe List("a+b", "a-b", "a/b", "a%b", "a==b", "a!=b", "a&&b", "a||b", "a&b", "a|b", "a^b")

// TODO: Tests for operands
}

"be created for shorthand assignment operators" in {
val cpg = code(
basicBoilerplate("""
|int a = 3;
|int b = 5;
|a+=b;
|a-=b;
|a*=b;
|a/=b;
|a%=b;
|a&=b;
|a|=b;
|a^=b;
|a>>=b;
|a<<=b;
|""".stripMargin),
fileName = "Program.cs"
)
val operatorCalls = cpg.method("Main").ast.isCall.nameNot(Operators.assignment).l
operatorCalls.size shouldBe 10
operatorCalls.name.l shouldBe List(
"<operator>.assignmentPlus",
"<operator>.assignmentMinus",
"<operator>.assignmentMultiplication",
"<operator>.assignmentDivision",
"<operators>.assignmentModulo",
"<operators>.assignmentAnd",
"<operators>.assignmentOr",
"<operators>.assignmentXor",
"<operators>.assignmentLogicalShiftRight",
"<operators>.assignmentShiftLeft"
)
operatorCalls.code.l shouldBe List("a+=b", "a-=b", "a*=b", "a/=b", "a%=b", "a&=b", "a|=b", "a^=b", "a>>=b", "a<<=b")

// TODO: Tests for operands
}

"be created for comparison operators" in {
val cpg = code(
basicBoilerplate("""
|int a = 3;
|int b = 5;
|a > b;
|a < b;
|a == b;
|a >= b;
|a <= b;
|""".stripMargin),
fileName = "Program.cs"
)
val operatorCalls = cpg.method("Main").ast.isCall.nameNot(Operators.assignment).l
operatorCalls.size shouldBe 5
operatorCalls.name.l shouldBe List(
"<operator>.greaterThan",
"<operator>.lessThan",
"<operator>.equals",
"<operator>.greaterEqualsThan",
"<operator>.lessEqualsThan"
)
operatorCalls.code.l shouldBe List("a > b", "a < b", "a == b", "a >= b", "a <= b")

// TODO: Tests for operands
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ object ExternalCommand {

private val IS_WIN: Boolean = scala.util.Properties.isWin

private val shellPrefix: Seq[String] = if (IS_WIN) "cmd" :: "/c" :: Nil else "sh" :: "-c" :: Nil
private val shellPrefix: Seq[String] = if (IS_WIN) "cmd" :: "/c" :: Nil else "bash" :: "-c" :: Nil

def run(
command: String,
Expand Down

0 comments on commit 965b769

Please sign in to comment.