diff --git a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/utils/ImplicitUsingsCollector.scala b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/utils/ImplicitUsingsCollector.scala index 8d921cbc48d8..14c4c26ce2b2 100644 --- a/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/utils/ImplicitUsingsCollector.scala +++ b/joern-cli/frontends/csharpsrc2cpg/src/main/scala/io/joern/csharpsrc2cpg/utils/ImplicitUsingsCollector.scala @@ -76,21 +76,40 @@ object ImplicitUsingsCollector { .collect { case x if x.label == "ImplicitUsings" => x.text } .exists(x => x == "true" || x == "enable") - val exclusions = rootElem.child + val usingsFromProjectType = if (projectType.isDefined && implicitUsingsEnabled) { + projectTypeMapping.getOrElse(projectType.get, Nil) + } else { + Nil + } + + // Once we gather the initial set of implicit usings (if any) based on the project type, we + // process ItemGroup.Using tags. The order in which we process these matters, e.g. + // + // + // removes "System", whereas + // + // + // adds "System". + + rootElem.child .collect { case x if x.label == "ItemGroup" => x.child } .flatten - .collect { - case x if x.label == "Using" && x.attribute("Remove").isDefined => - x.attribute("Remove").flatMap(_.headOption.map(_.text)) - } + .collect { case x if x.label == "Using" => x } .flatten + .foldLeft(usingsFromProjectType.toSet) { case (acc, node) => + if (node.attribute("Remove").isDefined) { + node.attribute("Remove").flatMap(_.headOption.map(_.text)) match + case None => acc + case Some(toRemove) => acc.excl(toRemove) + } else if (node.attribute("Include").isDefined) { + node.attribute("Include").flatMap(_.headOption.map(_.text)) match + case None => acc + case Some(toInclude) => acc + toInclude + } else { + acc + } + } .toList - - if (projectType.isDefined && implicitUsingsEnabled) { - projectTypeMapping.getOrElse(projectType.get, Nil).diff(exclusions) - } else { - Nil - } } } diff --git a/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/ImplicitUsingsTests.scala b/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/ImplicitUsingsTests.scala index 8a0637c001f8..ac5ce4015941 100644 --- a/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/ImplicitUsingsTests.scala +++ b/joern-cli/frontends/csharpsrc2cpg/src/test/scala/io/joern/csharpsrc2cpg/querying/ast/ImplicitUsingsTests.scala @@ -147,6 +147,86 @@ class ImplicitUsingsTests extends CSharpCode2CpgFixture { ) } } + + "accompanied by a NET.Sdk csproj with ImplicitUsings disabled but including `System`" should { + val cpg = code(""" + |Console.WriteLine("Foo"); + |""".stripMargin) + .moreCode( + """ + | + | + | Exe + | false + | + | + | + | + | + |""".stripMargin, + fileName = "App.csproj" + ) + + "resolve WriteLine call" in { + cpg.call.nameExact("WriteLine").methodFullName.l shouldBe List( + "System.Console.WriteLine:System.Void(System.String)" + ) + } + } + + "accompanied by a NET.Sdk csproj with ImplicitUsings enabled but including and excluding `System`" should { + val cpg = code(""" + |Console.WriteLine("Foo"); + |""".stripMargin) + .moreCode( + """ + | + | + | Exe + | true + | + | + | + | + | + | + |""".stripMargin, + fileName = "App.csproj" + ) + + "not resolve WriteLine call" in { + cpg.call.nameExact("WriteLine").methodFullName.l shouldBe List( + ".WriteLine:" + ) + } + } + + "accompanied by a NET.Sdk csproj with ImplicitUsings enabled but excluding and including `System`" should { + val cpg = code(""" + |Console.WriteLine("Foo"); + |""".stripMargin) + .moreCode( + """ + | + | + | Exe + | true + | + | + | + | + | + | + |""".stripMargin, + fileName = "App.csproj" + ) + + "resolve WriteLine call" in { + cpg.call.nameExact("WriteLine").methodFullName.l shouldBe List( + "System.Console.WriteLine:System.Void(System.String)" + ) + } + } } }