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 5c22db90103e..48f3bf6d5661 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 @@ -37,11 +37,19 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) { case InterpolatedStringExpression => astForInterpolatedStringExpression(expr) case ConditionalAccessExpression => astForConditionalAccessExpression(expr) case SuppressNullableWarningExpression => astForSuppressNullableWarningExpression(expr) + case CoalesceExpression => astForCoalesceExpression(expr) case _: BaseLambdaExpression => astForSimpleLambdaExpression(expr) case _ => notHandledYet(expr) } } + private def astForCoalesceExpression(coalesceExpression: DotNetNodeInfo): Seq[Ast] = { + val leftAst = astForExpression(createDotNetNodeInfo(coalesceExpression.json(ParserKeys.Left))) + val rightAst = astForExpression(createDotNetNodeInfo(coalesceExpression.json(ParserKeys.Right))) + + leftAst ++ rightAst + } + private def astForAwaitExpression(awaitExpr: DotNetNodeInfo): Seq[Ast] = { /* fullName is the name in case of STATIC_DISPATCH */ val node = 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 67ebeadaa1a8..e3aeaec69d6c 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 @@ -270,6 +270,8 @@ object DotNetJsonAst { object Attribute extends BaseExpr + object CoalesceExpression extends BaseExpr + object Unknown extends DotNetParserNode } diff --git a/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/CallTests.scala b/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/CallTests.scala index 38764d12fd7a..ccb60bad242b 100644 --- a/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/CallTests.scala +++ b/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/CallTests.scala @@ -194,4 +194,34 @@ class CallTests extends CSharpCode2CpgFixture { } } + "null-coalescing operator" should { + val cpg = code(""" + |namespace Baz + |{ + | public class Foo + | { + | private readonly IDatabase db; + | + | public async AnyValue GetValue(string x) + | { + | var value = await db.get(x) ?? new AnyValue(); + | return value; + | } + | } + |} + |""".stripMargin).moreCode(""" + |namespace Baz; + | + |public interface IDatabase { + | public AnyValue get(string x) {} + |} + |""".stripMargin) + + "resolve methodFullName" in { + inside(cpg.call.name("get").methodFullName.l) { + case x :: Nil => x shouldBe "Baz.IDatabase.get:AnyValue(System.String)" + case _ => fail("Unexpected call node structure") + } + } + } }