Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[c#] allow resolving fully-qualified names without importing them first #5212

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.joern.csharpsrc2cpg.datastructures

import io.joern.x2cpg.Defines
import io.joern.x2cpg.datastructures.{OverloadableScope, Scope, ScopeElement, TypedScope, TypedScopeElement}
import io.joern.x2cpg.utils.ListUtils.singleOrNone
import io.shiftleft.codepropertygraph.generated.nodes.DeclarationNew

import scala.collection.mutable
Expand Down Expand Up @@ -62,7 +63,13 @@ class CSharpScope(summary: CSharpProgramSummary)
if (typeName == "this") {
surroundingTypeDeclFullName.flatMap(summary.matchingTypes).headOption
} else {
super.tryResolveTypeReference(typeName)
super.tryResolveTypeReference(typeName) match
case Some(x) => Some(x)
case None =>
// typeName might be a fully-qualified name e.g. System.Console, in which case, even if we
// don't import System (i.e. System is not in typesInScope), we should still find it if it's
// in the type summaries and there's exactly 1 match.
Some(typeName).filter(_.contains(".")).flatMap(summary.matchingTypes.andThen(singleOrNone))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.shiftleft.semanticcpg.language.*

class FieldAccessTests extends CSharpCode2CpgFixture {

"Console.WriteLine call" should {
"Console.WriteLine call while importing System" should {
val cpg = code("""
|using System;
|Console.WriteLine("foo");
Expand All @@ -15,15 +15,32 @@ class FieldAccessTests extends CSharpCode2CpgFixture {
"have WriteLine call correctly set" in {
inside(cpg.call.nameExact("WriteLine").l) {
case writeLine :: Nil =>
writeLine.code shouldBe "Console.WriteLine(\"foo\")"
writeLine.methodFullName shouldBe "System.Console.WriteLine:System.Void(System.String)"
writeLine.argument(0).code shouldBe "Console"
writeLine.argument(1).code shouldBe "\"foo\""
case xs => fail(s"Expected single WriteLine call, but got $xs")
}
}

"have foo literal correctly set" in {
inside(cpg.call.nameExact("WriteLine").argument(1).isLiteral.l) {
case foo :: Nil =>
foo.typeFullName shouldBe "System.String"
foo.code shouldBe "\"foo\""
case xs => fail(s"Expected single literal argument to WriteLine, but got $xs")
}
}

"have Console correctly set" in {
inside(cpg.call.nameExact("WriteLine").argument(0).isIdentifier.l) {
case console :: Nil =>
console.code shouldBe "Console"
console.typeFullName shouldBe "System.Console"
case xs => fail(s"Expected single Console identifier, but got $xs")
}
}
}

"System.Console.WriteLine call" should {
"System.Console.WriteLine call while importing System" should {
val cpg = code("""
|using System;
|System.Console.WriteLine("foo");
Expand All @@ -33,22 +50,65 @@ class FieldAccessTests extends CSharpCode2CpgFixture {
inside(cpg.call.nameExact("WriteLine").l) {
case writeLine :: Nil =>
writeLine.methodFullName shouldBe "System.Console.WriteLine:System.Void(System.String)"
inside(writeLine.argument(0).fieldAccess.l) {
case sysConsole :: Nil =>
sysConsole.typeFullName shouldBe "System.Console"
sysConsole.code shouldBe "System.Console"
sysConsole.fieldIdentifier.code.l shouldBe List("Console")
case xs => fail(s"Expected single fieldAccess to the left of WriteLine, but got $xs")
}
inside(writeLine.argument(1).start.isLiteral.l) {
case foo :: Nil =>
foo.typeFullName shouldBe "System.String"
foo.code shouldBe "\"foo\""
case xs => fail(s"Expected single literal argument to WriteLine, but got $xs")
}
writeLine.code shouldBe "System.Console.WriteLine(\"foo\")"
case xs => fail(s"Expected single WriteLine call, but got $xs")
}
}

"have foo literal correctly set" in {
inside(cpg.call.nameExact("WriteLine").argument(1).isLiteral.l) {
case foo :: Nil =>
foo.typeFullName shouldBe "System.String"
foo.code shouldBe "\"foo\""
case xs => fail(s"Expected single literal argument to WriteLine, but got $xs")
}
}

"have System.Console correctly set" in {
inside(cpg.call.nameExact("WriteLine").argument(0).fieldAccess.l) {
case sysConsole :: Nil =>
sysConsole.typeFullName shouldBe "System.Console"
sysConsole.code shouldBe "System.Console"
sysConsole.fieldIdentifier.code.l shouldBe List("Console")
sysConsole.fieldIdentifier.canonicalName.l shouldBe List("Console")
case xs => fail(s"Expected single fieldAccess to the left of WriteLine, but got $xs")
}
}
}

"System.Console.WriteLine call without importing System" should {
val cpg = code("""
|System.Console.WriteLine("foo");
|""".stripMargin)

"have WriteLine call correctly set" in {
inside(cpg.call.nameExact("WriteLine").l) {
case writeLine :: Nil =>
writeLine.methodFullName shouldBe "System.Console.WriteLine:System.Void(System.String)"
writeLine.code shouldBe "System.Console.WriteLine(\"foo\")"
case xs => fail(s"Expected single WriteLine call, but got $xs")
}
}

"have foo literal correctly set" in {
inside(cpg.call.nameExact("WriteLine").argument(1).isLiteral.l) {
case foo :: Nil =>
foo.typeFullName shouldBe "System.String"
foo.code shouldBe "\"foo\""
case xs => fail(s"Expected single literal argument to WriteLine, but got $xs")
}
}

"have System.Console correctly set" in {
inside(cpg.call.nameExact("WriteLine").argument(0).fieldAccess.l) {
case sysConsole :: Nil =>
sysConsole.typeFullName shouldBe "System.Console"
sysConsole.code shouldBe "System.Console"
sysConsole.fieldIdentifier.code.l shouldBe List("Console")
sysConsole.fieldIdentifier.canonicalName.l shouldBe List("Console")
case xs => fail(s"Expected single fieldAccess to the left of WriteLine, but got $xs")
}
}
}

"field access via explicit `this.X`" should {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ object ListUtils {
case _ => Nil
}
}

/** Returns the single element, or None if the list is empty or contains more than one element. */
def singleOrNone: Option[T] = if list.size == 1 then list.headOption else None
}
}
Loading