Repository: joernio/joern (scala)

Issue:
[Bug] c2cpg: C++ fully-qualified calls of static methods not resolved correctly
take this code:

```c++
int square(int num) {
    return num * num;
}
void call_square() {
    ::square(10);
}
```

or this:

```c++
class A {
public:
  static int square(int num) {
    return num * num;
  }
};

void call_square() {
    A::square(10);
}
```

The `methodFullName` on the call to `square` and the `fullName` of that method don't line up and so the calls can't be resolved:


```
joern> cpg.method.name("square").fullName.l 
res64: List[String] = List("square")

joern> cpg.method.name("call_square").call.l 
res65: List[Call] = List(
  Call(
    id = 18L,
    argumentIndex = -1,
    argumentName = None,
    code = "::square(10)",
    columnNumber = Some(value = 5),
    dispatchType = "STATIC_DISPATCH",
    dynamicTypeHintFullName = ArraySeq(),
    lineNumber = Some(value = 6),
    methodFullName = "::square",
    name = "::square",
    order = 1,
    signature = "",
    typeFullName = "<empty>"
  ),
...
```

or

```
joern> cpg.method.name("square").fullName.l 
res62: List[String] = List("A.square")
joern> cpg.method.name("call_square").call.l 
res61: List[Call] = List(
  Call(
    id = 19L,
    argumentIndex = -1,
    argumentName = None,
    code = "A::square(10)",
    columnNumber = Some(value = 5),
    dispatchType = "STATIC_DISPATCH",
    dynamicTypeHintFullName = ArraySeq(),
    lineNumber = Some(value = 10),
    methodFullName = "A::square",
    name = "A::square",
    order = 1,
    signature = "",
    typeFullName = "<empty>"
  ),
...
```

Pull request description:
Fixes: https://github.com/joernio/joern/issues/2559

Patch:
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala
index 9cb6ca4410e3..baea502e0206 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala
@@ -232,7 +232,7 @@ trait AstCreatorHelper { this: AstCreator =>
   }
 
   protected def fixQualifiedName(name: String): String =
-    name.replace(Defines.qualifiedNameSeparator, ".")
+    name.stripPrefix(Defines.qualifiedNameSeparator).replace(Defines.qualifiedNameSeparator, ".")
 
   protected def isQualifiedName(name: String): Boolean =
     name.startsWith(Defines.qualifiedNameSeparator)
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForExpressionsCreator.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForExpressionsCreator.scala
index 9721f3664622..391eb99a5ec3 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForExpressionsCreator.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForExpressionsCreator.scala
@@ -102,9 +102,9 @@ trait AstForExpressionsCreator { this: AstCreator =>
 
     val fullName = typeFor(call.getFunctionNameExpression) match {
       case t if t != Defines.anyTypeName => s"$t.$name"
-      case _                             => name
+      case _                             => fixQualifiedName(name)
     }
-    val cpgCall = newCallNode(call, name, fullName, dd)
+    val cpgCall = newCallNode(call, fixQualifiedName(name), fullName, dd)
     val args    = call.getArguments.toList.map(a => astForNode(a))
     rec.root match {
       // Optimization: do not include the receiver if the receiver is just the function name,
@@ -112,7 +112,7 @@ trait AstForExpressionsCreator { this: AstCreator =>
       // have so many call sites in CPGs, this drastically reduces the number of nodes.
       // Moreover, the data flow tracker does not need to track `f`, which would not make
       // much sense anyway.
-      case Some(r: NewIdentifier) if r.name == name =>
+      case Some(r: NewIdentifier) if r.name == fixQualifiedName(name) =>
         callAst(cpgCall, args)
       case Some(_) =>
         callAst(cpgCall, args, Option(rec))
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForPrimitivesCreator.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForPrimitivesCreator.scala
index 7c30de67c005..1ffd497e423b 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForPrimitivesCreator.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstForPrimitivesCreator.scala
@@ -107,7 +107,11 @@ trait AstForPrimitivesCreator { this: AstCreator =>
       Ast(newLiteralNode(qualId.getLastName, "<global>", Defines.anyTypeName))
     }
 
-    val member = fieldIdentifierNode(qualId.getLastName, qualId.getLastName.toString, qualId.getLastName.toString)
+    val member = fieldIdentifierNode(
+      qualId.getLastName,
+      fixQualifiedName(qualId.getLastName.toString),
+      qualId.getLastName.toString
+    )
     callAst(ma, List(owner, Ast(member)))
   }
 


Test patch:
diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/ast/CallTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/ast/CallTests.scala
index 8ffb164b6be8..e892ef1c8e1e 100644
--- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/ast/CallTests.scala
+++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/ast/CallTests.scala
@@ -112,6 +112,46 @@ class CallTests extends CCodeToCpgSuite {
       val List(callIn) = m.callIn.l
       callIn.code shouldBe "b->GetObj()"
     }
+  }
+
+  "CallTest 3" should {
+    val cpg = code(
+      """
+        |int square(int num) {
+        |    return num * num;
+        |}
+        |void call_square() {
+        |    ::square(10);
+        |}
+        |""".stripMargin,
+      "test.cpp"
+    )
+    "have correct names for static methods / calls" in {
+      cpg.method.name("square").fullName.head shouldBe "square"
+      cpg.method.name("call_square").call.methodFullName.head shouldBe "square"
+    }
+  }
 
+  "CallTest 4" should {
+    val cpg = code(
+      """
+        |class A {
+        |  public:
+        |    static int square(int num) {
+        |      return num * num;
+        |    }
+        |};
+        |
+        |void call_square() {
+        |  A::square(10);
+        |}
+        |""".stripMargin,
+      "test.cpp"
+    )
+    "have correct names for static methods / calls from classes" in {
+      cpg.method.name("square").fullName.head shouldBe "A.square"
+      cpg.method.name("call_square").call.methodFullName.head shouldBe "A.square"
+    }
   }
+
 }
diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/NamespaceTypeTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/NamespaceTypeTests.scala
index c5dc751aebf9..722b4c6c7d7e 100644
--- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/NamespaceTypeTests.scala
+++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/NamespaceTypeTests.scala
@@ -171,10 +171,10 @@ class NamespaceTypeTests extends CCodeToCpgSuite(fileSuffix = FileDefaults.CPP_E
       }
 
       inside(cpg.call.filterNot(_.name == Operators.fieldAccess).l) { case List(f, g) =>
-        f.name shouldBe "X::f"
-        f.methodFullName shouldBe "X::f"
-        g.name shouldBe "X::g"
-        g.methodFullName shouldBe "X::g"
+        f.name shouldBe "X.f"
+        f.methodFullName shouldBe "X.f"
+        g.name shouldBe "X.g"
+        g.methodFullName shouldBe "X.g"
       }
     }