From 43394706c07b40c756737d03c17a28e5ac97d66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Leuth=C3=A4user?= <1417198+max-leuthaeuser@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:13:54 +0100 Subject: [PATCH] [c2cpg] Remaining C++20 features (#5242) --- joern-cli/frontends/c2cpg/CPP_Features.md | 8 +- .../c2cpg/astcreation/AstCreatorHelper.scala | 13 +- .../cpp/features17/Cpp17FeaturesTests.scala | 4 +- .../cpp/features20/Cpp20FeaturesTests.scala | 126 +++++++++++++----- 4 files changed, 106 insertions(+), 45 deletions(-) diff --git a/joern-cli/frontends/c2cpg/CPP_Features.md b/joern-cli/frontends/c2cpg/CPP_Features.md index d5d24c9f6d8c..af6b85bbedca 100644 --- a/joern-cli/frontends/c2cpg/CPP_Features.md +++ b/joern-cli/frontends/c2cpg/CPP_Features.md @@ -37,10 +37,10 @@ | three-way comparison | [ ] | | designated initializers | [~] | | template syntax for lambdas | [ ] | -| range-based for loop with initializer | [?] | -| \[\[likely\]\] and \[\[unlikely\]\] attributes | [?] | -| deprecate implicit capture of this | [?] | -| class types in non-type template parameters | [?] | +| range-based for loop with initializer | [ ] | +| \[\[likely\]\] and \[\[unlikely\]\] attributes | [x] | +| deprecate implicit capture of this | [~] | +| class types in non-type template parameters | [~] | | constexpr virtual functions | [~] | | explicit(bool) | [ ] | | immediate functions | [x] | 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 d79639e58323..5a3aa2045a20 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 @@ -146,17 +146,16 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As protected def cleanType(rawType: String): String = { if (rawType == Defines.Any) return rawType - val tpe = ReservedKeywordsAtTypes.foldLeft(rawType) { (cur, repl) => + val normalizedTpe = StringUtils.normalizeSpace(rawType.stripSuffix(" ()")) + val tpe = ReservedKeywordsAtTypes.foldLeft(normalizedTpe) { (cur, repl) => if (cur.startsWith(s"$repl ") || cur.contains(s" $repl ")) { cur.replace(s" $repl ", " ").stripPrefix(s"$repl ") } else cur } - val normalizedTpe = StringUtils.normalizeSpace(tpe.stripSuffix(" ()")) - replaceWhitespaceAfterKeyword(normalizedTpe) match { + replaceWhitespaceAfterKeyword(tpe) match { case "" => Defines.Any - case t if t.startsWith("[*this]") || t.startsWith("[this]") => normalizedTpe case t if t.startsWith("[") && t.endsWith("]") => Defines.Array - case t if t.contains("->") => Defines.Function + case t if isThisLambdaCapture(t) || t.contains("->") => Defines.Function case t if t.contains("?") => Defines.Any case t if t.contains("#") => Defines.Any case t if t.contains("::{") || t.contains("}::") => Defines.Any @@ -187,6 +186,10 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As } } + private def isThisLambdaCapture(tpe: String): Boolean = { + tpe.startsWith("[*this]") || tpe.startsWith("[this]") || (tpe.startsWith("[") && tpe.contains("this]")) + } + protected def safeGetEvaluation(expr: ICPPASTExpression): Option[ICPPEvaluation] = { // In case of unresolved includes etc. this may fail throwing an unrecoverable exception Try(expr.getEvaluation).toOption diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/cpp/features17/Cpp17FeaturesTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/cpp/features17/Cpp17FeaturesTests.scala index 845b18b3c7a6..6aea738ee518 100644 --- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/cpp/features17/Cpp17FeaturesTests.scala +++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/cpp/features17/Cpp17FeaturesTests.scala @@ -162,8 +162,8 @@ class Cpp17FeaturesTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) |""".stripMargin) // TODO: we can not express these lambda types in the current schema // We would need to add a new type for lambdas that capture `this` by value copy/ref. - cpg.method.nameExact("getValueCopy").methodReturn.typeFullName.l shouldBe List("[*this] { return value; }") - cpg.method.nameExact("getValueRef").methodReturn.typeFullName.l shouldBe List("[this] { return value; }") + cpg.method.nameExact("getValueCopy").methodReturn.typeFullName.l shouldBe List(Defines.Function) + cpg.method.nameExact("getValueRef").methodReturn.typeFullName.l shouldBe List(Defines.Function) } "handle inline variables" in { diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/cpp/features20/Cpp20FeaturesTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/cpp/features20/Cpp20FeaturesTests.scala index eaae004e89ef..40e4c41be913 100644 --- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/cpp/features20/Cpp20FeaturesTests.scala +++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/cpp/features20/Cpp20FeaturesTests.scala @@ -1,5 +1,6 @@ package io.joern.c2cpg.cpp.features20 +import io.joern.c2cpg.astcreation.Defines import io.joern.c2cpg.parser.FileDefaults import io.joern.c2cpg.testfixtures.AstC2CpgSuite import io.shiftleft.codepropertygraph.generated.ControlStructureTypes @@ -210,59 +211,103 @@ class Cpp20FeaturesTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) } } - "handle range-based for loop with initializer" ignore { + "handle range-based for loop with initializer" in { val cpg = code(""" - |for (auto v = std::vector{1, 2, 3}; auto& e : v) { - | std::cout << e; + |void foo() { + | for (auto v = list; auto& e : v) { + | std::cout << e; + | } |} - |// prints "123" |""".stripMargin) - ??? + pendingUntilFixed { + // range-based for loop with initializer can not be parsed at all by CDT at the moment + val List(v, e) = cpg.method.nameExact("foo").controlStructure.astChildren.isLocal.l + v.name shouldBe "v" + e.name shouldBe "e" + val List(vectorInitCall) = + cpg.method.nameExact("foo").controlStructure.astChildren.order(2).isCall.argument.isCall.l + vectorInitCall.argumentIndex shouldBe 2 + vectorInitCall.name shouldBe Defines.OperatorConstructorInitializer + vectorInitCall.argument.code.l shouldBe List("1", "2", "3") + } } - "handle likely and unlikely attributes" ignore { + "handle likely and unlikely attributes" in { val cpg = code(""" - |switch (n) { - |case 1: - | // ... - | break; - | - |[[likely]] case 2: // n == 2 is considered to be arbitrarily more - | // ... // likely than any other value of n - | break; - |} + |void foo() { + | switch (n) { + | case 1: + | case1(); + | break; + | [[likely]] case 2: // n == 2 is considered to be arbitrarily more + | case2() // likely than any other value of n + | break; + | } | - |int random = get_random_number_between_x_and_y(0, 3); - |if (random > 0) [[likely]] { - | // body of if statement - | // ... - |} + | int random = get_random_number_between_x_and_y(0, 3); + | if (random > 0) [[likely]] { + | // body of if statement + | likelyIf(); + | } | - |while (unlikely_truthy_condition) [[unlikely]] { - | // body of while statement - | // ... + | while (unlikely_truthy_condition) [[unlikely]] { + | // body of while statement + | unlikelyWhile() + | } |} |""".stripMargin) - ??? + val cases = + cpg.method + .nameExact("foo") + .controlStructure + .controlStructureTypeExact(ControlStructureTypes.SWITCH) + .astChildren + .isBlock + ._jumpTargetViaAstOut + cases.code.l shouldBe List("case 1:", "[[likely]] case 2:") + cpg.method + .nameExact("foo") + .controlStructure + .controlStructureTypeExact(ControlStructureTypes.SWITCH) + .ast + .isCall + .code + .l shouldBe List("case1()", "case2()") + + cpg.method + .nameExact("foo") + .controlStructure + .controlStructureTypeExact(ControlStructureTypes.IF) + .ast + .isCall + .code + .l shouldBe List("random > 0", "likelyIf()") + + cpg.method + .nameExact("foo") + .controlStructure + .controlStructureTypeExact(ControlStructureTypes.WHILE) + .ast + .isCall + .code + .l shouldBe List("unlikelyWhile()") } - "handle deprecate implicit capture of this" ignore { + "handle deprecate implicit capture of this" in { val cpg = code(""" |struct int_value { | int n = 0; | auto getter_fn() { - | // BAD: - | // return [=]() { return n; }; - | - | // GOOD: | return [=, *this]() { return n; }; | } |}; |""".stripMargin) - ??? + // TODO: we can not express these lambda types in the current schema + // We would need to add a new type for lambdas that capture `this` + cpg.method.nameExact("getter_fn").methodReturn.typeFullName.l shouldBe List(Defines.Function) } - "handle class types in non-type template parameters" ignore { + "handle class types in non-type template parameters" in { val cpg = code(""" |struct foo { | foo() = default; @@ -274,10 +319,23 @@ class Cpp20FeaturesTests extends AstC2CpgSuite(fileSuffix = FileDefaults.CppExt) | return f; |} | - |get_foo(); // uses implicit constructor - |get_foo(); + |void main() { + | get_foo(); // uses implicit constructor + | get_foo(); + |} |""".stripMargin) - ??? + cpg.typeDecl.nameExact("foo").size shouldBe 1 + cpg.method.nameExact("get_foo").size shouldBe 1 + cpg.method.nameExact("main").ast.isCall.typeFullName.l shouldBe List("ANY", "foo") + cpg.method.nameExact("main").ast.isCall.methodFullName.l shouldBe List( + // we can not resolve the implicit constructor call case: + ".get_foo:(0)", + "get_foo:foo()" + ) + pendingUntilFixed { + cpg.method.nameExact("main").ast.isCall.typeFullName.l shouldBe List("foo", "foo") + cpg.method.nameExact("main").ast.isCall.methodFullName.l shouldBe List("get_foo:foo()", "get_foo:foo()") + } } "handle constexpr virtual functions" in {