diff --git a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstCreator.scala
index 2b269bec8e5a..90291c57fcb6 100644
--- a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstCreator.scala
@@ -27,7 +27,6 @@ class AstCreator(
with AstForPrimitivesCreator
with AstForExpressionsCreator
with AstForStatementsCreator
- with AstForControlStructuresCreator
with AstSummaryVisitor
with AstGenNodeBuilder[AstCreator] {
diff --git a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForControlStructuresCreator.scala b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForControlStructuresCreator.scala
deleted file mode 100644
index 2558efea8a27..000000000000
--- a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForControlStructuresCreator.scala
+++ /dev/null
@@ -1,76 +0,0 @@
-package io.joern.csharpsrc2cpg.astcreation
-
-import io.joern.csharpsrc2cpg.CSharpOperators
-import io.joern.csharpsrc2cpg.parser.{DotNetNodeInfo, ParserKeys}
-import io.joern.x2cpg.{Ast, ValidationMode}
-import io.shiftleft.codepropertygraph.generated.ControlStructureTypes
-import io.shiftleft.codepropertygraph.generated.nodes.{NewBlock, NewControlStructure}
-
-import scala.util.{Failure, Success, Try}
-
-trait AstForControlStructuresCreator(implicit withSchemaValidation: ValidationMode) { this: AstCreator =>
-
- protected def astForThrowStatement(throwStmt: DotNetNodeInfo): Seq[Ast] = {
- val expr = createDotNetNodeInfo(throwStmt.json(ParserKeys.Expression))
- val args = astForNode(expr)
- val throwCall = createCallNodeForOperator(
- throwStmt,
- CSharpOperators.throws,
- typeFullName = Option(getTypeFullNameFromAstNode(args))
- )
- Seq(callAst(throwCall, args))
- }
-
- protected def astForTryStatement(tryStmt: DotNetNodeInfo): Seq[Ast] = {
- val tryNode = NewControlStructure()
- .controlStructureType(ControlStructureTypes.TRY)
- .code("try")
- .lineNumber(line(tryStmt))
- .columnNumber(column(tryStmt))
- val tryAst = astForBlock(createDotNetNodeInfo(tryStmt.json(ParserKeys.Block)), Option("try"))
-
- val catchAsts = Try(tryStmt.json(ParserKeys.Catches)).map(_.arr.flatMap(astForNode).toSeq).getOrElse(Seq.empty)
- val catchBlock = Option
- .when(catchAsts.nonEmpty) {
- Ast(blockNode(tryStmt).code("catch")).withChildren(catchAsts)
- }
- .toList
-
- val finallyBlock = Try(createDotNetNodeInfo(tryStmt.json(ParserKeys.Finally))).map(astForFinallyClause) match
- case Success(finallyAst :: Nil) =>
- finallyAst.root.collect { case x: NewBlock => x.code("finally") }
- finallyAst
- case _ => Ast()
-
- val controlStructureAst = Ast(tryNode)
- .withChild(tryAst)
- .withChildren(catchBlock)
- .withChild(finallyBlock)
-
- Seq(controlStructureAst)
- }
-
- protected def astForFinallyClause(finallyClause: DotNetNodeInfo): Seq[Ast] = {
- Seq(astForBlock(createDotNetNodeInfo(finallyClause.json(ParserKeys.Block)), code = Option(code(finallyClause))))
- }
-
- protected def astForCatchClause(catchClause: DotNetNodeInfo): Seq[Ast] = {
- val declAst = astForNode(catchClause.json(ParserKeys.Declaration)).toList
- val blockAst = astForBlock(
- createDotNetNodeInfo(catchClause.json(ParserKeys.Block)),
- code = Option(code(catchClause)),
- prefixAsts = declAst
- )
- Seq(blockAst)
- }
-
- protected def astForCatchDeclaration(catchDeclaration: DotNetNodeInfo): Seq[Ast] = {
- val name = nameFromNode(catchDeclaration)
- val typeFullName = nodeTypeFullName(catchDeclaration)
- val _localNode = localNode(catchDeclaration, name, name, typeFullName)
- val localNodeAst = Ast(_localNode)
- scope.addToScope(name, _localNode)
- Seq(localNodeAst)
- }
-
-}
diff --git a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForExpressionsCreator.scala b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForExpressionsCreator.scala
index 2a1819a0206a..0915fb5fb893 100644
--- a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForExpressionsCreator.scala
+++ b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForExpressionsCreator.scala
@@ -21,7 +21,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
}
def astForExpression(expr: DotNetNodeInfo): Seq[Ast] = {
- expr.node match
+ expr.node match {
case _: UnaryExpr => astForUnaryExpression(expr)
case _: BinaryExpr => astForBinaryExpression(expr)
case _: LiteralExpr => astForLiteralExpression(expr)
@@ -39,6 +39,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
case SuppressNullableWarningExpression => astForSuppressNullableWarningExpression(expr)
case _: BaseLambdaExpression => astForSimpleLambdaExpression(expr)
case _ => notHandledYet(expr)
+ }
}
private def astForAwaitExpression(awaitExpr: DotNetNodeInfo): Seq[Ast] = {
diff --git a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForStatementsCreator.scala b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForStatementsCreator.scala
index 3fd71c0f1269..a1cdd8678373 100644
--- a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForStatementsCreator.scala
+++ b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/astcreation/AstForStatementsCreator.scala
@@ -1,39 +1,14 @@
package io.joern.csharpsrc2cpg.astcreation
-import io.circe.Json
-import io.joern.csharpsrc2cpg.parser.DotNetJsonAst.{
- BinaryExpr,
- Block,
- BreakStatement,
- CasePatternSwitchLabel,
- CaseSwitchLabel,
- ContinueStatement,
- DefaultSwitchLabel,
- DoStatement,
- ExpressionStatement,
- ForEachStatement,
- ForStatement,
- GenericName,
- GlobalStatement,
- GotoStatement,
- IfStatement,
- JumpStatement,
- LiteralExpr,
- ReturnStatement,
- SwitchStatement,
- ThrowStatement,
- TryStatement,
- UnaryExpr,
- WhileStatement
-}
+import io.joern.csharpsrc2cpg.CSharpOperators
+import io.joern.csharpsrc2cpg.parser.DotNetJsonAst.*
import io.joern.csharpsrc2cpg.parser.{DotNetNodeInfo, ParserKeys}
import io.joern.x2cpg.{Ast, ValidationMode}
-import io.shiftleft.codepropertygraph.generated.ControlStructureTypes
-import io.shiftleft.codepropertygraph.generated.nodes.ControlStructure
+import io.shiftleft.codepropertygraph.generated.{ControlStructureTypes, DispatchTypes}
+import io.shiftleft.codepropertygraph.generated.nodes.{NewBlock, NewControlStructure, NewIdentifier}
import scala.::
-import scala.collection.mutable.ArrayBuffer
-import scala.util.{Failure, Success, Try}
+import scala.util.{Success, Try}
trait AstForStatementsCreator(implicit withSchemaValidation: ValidationMode) { this: AstCreator =>
@@ -57,7 +32,7 @@ trait AstForStatementsCreator(implicit withSchemaValidation: ValidationMode) { t
}
protected def astForStatement(nodeInfo: DotNetNodeInfo): Seq[Ast] = {
- nodeInfo.node match
+ nodeInfo.node match {
case ExpressionStatement => astForExpressionStatement(nodeInfo)
case GlobalStatement => astForGlobalStatement(nodeInfo)
case IfStatement => astForIfStatement(nodeInfo)
@@ -68,8 +43,10 @@ trait AstForStatementsCreator(implicit withSchemaValidation: ValidationMode) { t
case DoStatement => astForDoStatement(nodeInfo)
case WhileStatement => astForWhileStatement(nodeInfo)
case SwitchStatement => astForSwitchStatement(nodeInfo)
+ case UsingStatement => astForUsingStatement(nodeInfo)
case _: JumpStatement => astForJumpStatement(nodeInfo)
case _ => notHandledYet(nodeInfo)
+ }
}
private def astForSwitchLabel(labelNode: DotNetNodeInfo): Seq[Ast] = {
@@ -222,4 +199,95 @@ trait AstForStatementsCreator(implicit withSchemaValidation: ValidationMode) { t
val _returnNode = returnNode(returnStmt, returnStmt.code)
Seq(returnAst(_returnNode, identifierAst))
}
+
+ protected def astForThrowStatement(throwStmt: DotNetNodeInfo): Seq[Ast] = {
+ val expr = createDotNetNodeInfo(throwStmt.json(ParserKeys.Expression))
+ val args = astForNode(expr)
+ val throwCall = createCallNodeForOperator(
+ throwStmt,
+ CSharpOperators.throws,
+ typeFullName = Option(getTypeFullNameFromAstNode(args))
+ )
+ Seq(callAst(throwCall, args))
+ }
+
+ protected def astForTryStatement(tryStmt: DotNetNodeInfo): Seq[Ast] = {
+ val tryNode = NewControlStructure()
+ .controlStructureType(ControlStructureTypes.TRY)
+ .code("try")
+ .lineNumber(line(tryStmt))
+ .columnNumber(column(tryStmt))
+ val tryAst = astForBlock(createDotNetNodeInfo(tryStmt.json(ParserKeys.Block)), Option("try"))
+
+ val catchAsts = Try(tryStmt.json(ParserKeys.Catches)).map(_.arr.flatMap(astForNode).toSeq).getOrElse(Seq.empty)
+
+ val finallyBlock = Try(createDotNetNodeInfo(tryStmt.json(ParserKeys.Finally))).map(astForFinallyClause) match
+ case Success(finallyAst :: Nil) =>
+ finallyAst.root.collect { case x: NewBlock => x.code("finally") }
+ finallyAst
+ case _ => Ast()
+
+ val controlStructureAst = tryCatchAst(tryNode, tryAst, catchAsts, Option(finallyBlock))
+ Seq(controlStructureAst)
+ }
+
+ protected def astForFinallyClause(finallyClause: DotNetNodeInfo): Seq[Ast] = {
+ Seq(astForBlock(createDotNetNodeInfo(finallyClause.json(ParserKeys.Block)), code = Option(code(finallyClause))))
+ }
+
+ /** Variables using the IDisposable interface may
+ * be used in `using`, where a call to `Dispose` is guaranteed.
+ *
+ * Thus, this is lowered as a try-finally, with finally making a call to `Dispose` on the declared variable.
+ */
+ private def astForUsingStatement(usingStmt: DotNetNodeInfo): Seq[Ast] = {
+ val tryNode = NewControlStructure()
+ .controlStructureType(ControlStructureTypes.TRY)
+ .code("try")
+ .lineNumber(line(usingStmt))
+ .columnNumber(column(usingStmt))
+ val tryAst = astForBlock(createDotNetNodeInfo(usingStmt.json(ParserKeys.Statement)), Option("try"))
+ val declNode = createDotNetNodeInfo(usingStmt.json(ParserKeys.Declaration))
+ val declAst = astForNode(declNode)
+
+ val finallyAst = declAst.flatMap(_.nodes).collectFirst { case x: NewIdentifier => x.copy }.map { id =>
+ val callCode = s"${id.name}.Dispose()"
+ id.code(callCode)
+ val disposeCall = callNode(
+ usingStmt,
+ callCode,
+ "Dispose",
+ "System.Disposable.Dispose:System.Void()",
+ DispatchTypes.DYNAMIC_DISPATCH,
+ Option("System.Void()"),
+ Option("System.Void")
+ )
+ val disposeAst = callAst(disposeCall, receiver = Option(Ast(id)))
+ Ast(blockNode(usingStmt).code("finally"))
+ .withChild(disposeAst)
+ }
+
+ declAst :+ tryCatchAst(tryNode, tryAst, Seq.empty, finallyAst)
+ }
+
+ protected def astForCatchClause(catchClause: DotNetNodeInfo): Seq[Ast] = {
+ val declAst = astForNode(catchClause.json(ParserKeys.Declaration)).toList
+ val blockAst = astForBlock(
+ createDotNetNodeInfo(catchClause.json(ParserKeys.Block)),
+ code = Option(code(catchClause)),
+ prefixAsts = declAst
+ )
+ Seq(blockAst)
+ }
+
+ protected def astForCatchDeclaration(catchDeclaration: DotNetNodeInfo): Seq[Ast] = {
+ val name = nameFromNode(catchDeclaration)
+ val typeFullName = nodeTypeFullName(catchDeclaration)
+ val _localNode = localNode(catchDeclaration, name, name, typeFullName)
+ val localNodeAst = Ast(_localNode)
+ scope.addToScope(name, _localNode)
+ Seq(localNodeAst)
+ }
+
}
diff --git a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/parser/DotNetJsonAst.scala b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/parser/DotNetJsonAst.scala
index b0ad7bee9dd9..d5ece496724a 100644
--- a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/parser/DotNetJsonAst.scala
+++ b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/parser/DotNetJsonAst.scala
@@ -194,6 +194,8 @@ object DotNetJsonAst {
object SwitchSection extends BaseExpr
+ object UsingStatement extends BaseStmt
+
object RelationalPattern extends BasePattern
object ConstantPattern extends BasePattern
diff --git a/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/ControlStructureTests.scala b/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/ControlStructureTests.scala
index 5a19c4f9d374..53565f2fb15d 100644
--- a/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/ControlStructureTests.scala
+++ b/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/ControlStructureTests.scala
@@ -57,7 +57,7 @@ class ControlStructureTests extends CSharpCode2CpgFixture {
inside(tryBlocks) {
case t :: c :: f :: Nil =>
t.code shouldBe "try"
- c.code shouldBe "catch"
+ c.code shouldBe "catch (Exception e)"
f.code shouldBe "finally"
case _ => fail("Invalid number of children under the `try` control structure!")
}
@@ -77,16 +77,12 @@ class ControlStructureTests extends CSharpCode2CpgFixture {
"generate a catch-block with a child block containing the correct console call" in {
inside(Option(tryBlocks(1))) {
- case Some(ctl) =>
- inside(ctl.astChildren.l) {
- case (catchBlock: Block) :: Nil =>
- inside(catchBlock.astChildren.l) {
- case (excepDecl: Local) :: (consoleCall: Call) :: Nil =>
- excepDecl.name shouldBe "e"
- consoleCall.code shouldBe "Console.WriteLine(\"Uh, oh!\")"
- case _ => fail("Invalid `catch` block children!")
- }
- case _ => fail("No `catch` handler block found!")
+ case Some(catchBlock: Block) =>
+ inside(catchBlock.astChildren.l) {
+ case (excepDecl: Local) :: (consoleCall: Call) :: Nil =>
+ excepDecl.name shouldBe "e"
+ consoleCall.code shouldBe "Console.WriteLine(\"Uh, oh!\")"
+ case _ => fail("Invalid `catch` block children!")
}
case None => fail("No `catch` block found!")
}
@@ -183,4 +179,47 @@ class ControlStructureTests extends CSharpCode2CpgFixture {
}
+ "a using statement" should {
+ val cpg = code(basicBoilerplate("""
+ |var numbers = new List();
+ |using (StreamReader reader = File.OpenText("numbers.txt"))
+ |{
+ | string line;
+ | while ((line = reader.ReadLine()) is not null)
+ | {
+ | if (int.TryParse(line, out int number))
+ | {
+ | numbers.Add(number);
+ | }
+ | }
+ |}
+ |""".stripMargin))
+
+ val tryBlocks = cpg.controlStructure.controlStructureTypeExact(ControlStructureTypes.TRY).astChildren.isBlock.l
+
+ "generate a try control structure with two children blocks" in {
+ inside(tryBlocks) {
+ case t :: f :: Nil =>
+ t.code shouldBe "try"
+ f.code shouldBe "finally"
+ case _ => fail("Invalid number of children under the `try` control structure!")
+ }
+ }
+
+ "generate a finally-block with the correct dispose call" in {
+ inside(tryBlocks.lastOption) {
+ case Some(ctl) =>
+ inside(ctl.astChildren.isCall.l) {
+ case disposeCall :: Nil =>
+ disposeCall.code shouldBe "reader.Dispose()"
+ disposeCall.name shouldBe "Dispose"
+ disposeCall.methodFullName shouldBe "System.Disposable.Dispose:System.Void()"
+ case _ => fail("No call node found!")
+ }
+ case None => fail("No `finally` block found!")
+ }
+ }
+
+ }
+
}