Skip to content

Commit

Permalink
[c2cpg] C++17 and C++20 features (#5210)
Browse files Browse the repository at this point in the history
  • Loading branch information
max-leuthaeuser authored Jan 9, 2025
1 parent bf1cda8 commit 166cfab
Show file tree
Hide file tree
Showing 12 changed files with 1,168 additions and 84 deletions.
51 changes: 51 additions & 0 deletions joern-cli/frontends/c2cpg/CPP_Features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Support For New Language Features

- For an explanation for each feature you may want to look at https://github.com/AnthonyCalandra/modern-cpp-features/tree/master.
- Table legend:
- `[?]` not yet checked
- `[ ]` not supported at all / can not even be parsed
- `[~]` can be parsed but is not fully represented in the CPG
- `[x]` full support including the CPG representation

## C++17 Language Features

| Feature | Supported |
|-------------------------------------------------------------------------|-----------|
| template argument deduction for class templates | [~] |
| declaring non-type template parameters with auto | [~] |
| folding expressions | [x] |
| new rules for auto deduction from braced-init-list | [x] |
| constexpr lambda | [~] |
| lambda capture this by value | [~] |
| inline variables | [x] |
| nested namespaces | [x] |
| structured bindings | [x] |
| selection statements with initializer | [x] |
| constexpr if | [x] |
| utf-8 character literals | [ ] |
| direct-list-initialization of enums | [x] |
| \[\[fallthrough\]\], \[\[nodiscard\]\], \[\[maybe_unused\]\] attributes | [~] |
| \_\_has_include | [~] |
| class template argument deduction | [~] |

## C++20 Language Features

| Feature | Supported |
|------------------------------------------------|-----------|
| coroutines | [?] |
| concepts | [?] |
| 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 | [?] |
| constexpr virtual functions | [?] |
| explicit(bool) | [?] |
| immediate functions | [?] |
| using enum | [?] |
| lambda capture of parameter pack | [?] |
| char8_t | [?] |
| constinit | [?] |
| \_\_VA_OPT\_\_ | [?] |
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPMethod
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPEvaluation
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.EvalBinding
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.EvalMemberAccess
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTFoldExpression
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.EvalBinary
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.EvalFoldExpression
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTEqualsInitializer
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPFunction
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPVariable
import org.eclipse.cdt.internal.core.model.ASTStringUtil

import java.nio.file.Path
Expand Down Expand Up @@ -148,8 +154,9 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
} else {
rawType
}
StringUtils.normalizeSpace(tpe) match {
StringUtils.normalizeSpace(tpe.stripSuffix(" ()")) match {
case "" => Defines.Any
case t if t.startsWith("[*this]") || t.startsWith("[this]") => t
case t if t.startsWith("[") && t.endsWith("]") => Defines.Array
case t if t.contains("->") => Defines.Function
case t if t.contains("?") => Defines.Any
Expand Down Expand Up @@ -186,6 +193,13 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
Try(expr.getEvaluation).toOption
}

protected def safeGetBinding(spec: IASTNamedTypeSpecifier): Option[IBinding] = {
// In case of unresolved includes etc. this may fail throwing an unrecoverable exception
Try(spec.getName.resolveBinding()).toOption.collect {
case binding: IBinding if !binding.isInstanceOf[IProblemBinding] => binding
}
}

protected def safeGetType(tpe: IType): String = {
// In case of unresolved includes etc. this may fail throwing an unrecoverable exception
Try(ASTTypeUtil.getType(tpe)).getOrElse(Defines.Any)
Expand All @@ -203,6 +217,22 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
}
}

private def typeForCPPASTFoldExpression(f: CPPASTFoldExpression, stripKeywords: Boolean = true): String = {
safeGetEvaluation(f) match {
case Some(evaluation: EvalFoldExpression) =>
Try(evaluation.getValue.getEvaluation).toOption match {
case Some(value: EvalBinary) =>
val s = value.toString
cleanType(s.substring(0, s.indexOf(": ")), stripKeywords)
case Some(value: EvalBinding) if value.getType.isInstanceOf[ICPPParameterPackType] =>
val s = value.getType.asInstanceOf[ICPPParameterPackType].getType.toString
cleanType(s, stripKeywords)
case _ => Defines.Any
}
case _ => Defines.Any
}
}

@nowarn
private def typeForIASTArrayDeclarator(a: IASTArrayDeclarator, stripKeywords: Boolean = true): String = {
import org.eclipse.cdt.core.dom.ast.ASTSignatureUtil.getNodeSignature
Expand Down Expand Up @@ -234,8 +264,10 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
cleanType(evaluation.getOwnerType.toString + deref, stripKeywords)
case Some(evalBinding: EvalBinding) =>
evalBinding.getBinding match {
case m: CPPMethod => cleanType(fullName(m.getDefinition))
case _ => cleanType(safeGetNodeType(s), stripKeywords)
case m: CPPMethod => cleanType(safeGetNodeType(m.getPrimaryDeclaration), stripKeywords)
case f: CPPFunction => cleanType(safeGetNodeType(f.getDefinition), stripKeywords)
case v: CPPVariable => cleanType(v.getType.toString)
case _ => cleanType(safeGetNodeType(s), stripKeywords)
}
case _ => cleanType(safeGetNodeType(s), stripKeywords)
}
Expand All @@ -256,26 +288,46 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
}
}

private def typeForCPPASTEqualsInitializer(c: CPPASTEqualsInitializer, stripKeywords: Boolean = true): String = {
import org.eclipse.cdt.core.dom.ast.ASTSignatureUtil.getNodeSignature
c.getInitializerClause match {
case initializer: ICPPASTFunctionCallExpression
if initializer.getFunctionNameExpression.isInstanceOf[CPPASTIdExpression] =>
val name = initializer.getFunctionNameExpression.asInstanceOf[CPPASTIdExpression]
typeForCPPASTIdExpression(name, stripKeywords)
case _ =>
cleanType(getNodeSignature(c), stripKeywords)
}
}

@nowarn
protected def typeFor(node: IASTNode, stripKeywords: Boolean = true): String = {
import org.eclipse.cdt.core.dom.ast.ASTSignatureUtil.getNodeSignature
node match {
case f: CPPASTFieldReference => typeForCPPASTFieldReference(f)
case f: IASTFieldReference => cleanType(safeGetType(f.getFieldOwner.getExpressionType), stripKeywords)
case a: IASTArrayDeclarator => typeForIASTArrayDeclarator(a)
case s: CPPASTIdExpression => typeForCPPASTIdExpression(s)
case f: CPPASTFoldExpression => typeForCPPASTFoldExpression(f, stripKeywords)
case f: CPPASTFieldReference => typeForCPPASTFieldReference(f, stripKeywords)
case s: CPPASTIdExpression => typeForCPPASTIdExpression(s, stripKeywords)
case s: ICPPASTNamedTypeSpecifier => typeForCPPAstNamedTypeSpecifier(s, stripKeywords)
case a: IASTArrayDeclarator => typeForIASTArrayDeclarator(a, stripKeywords)
case c: ICPPASTConstructorInitializer => typeForICPPASTConstructorInitializer(c, stripKeywords)
case c: CPPASTEqualsInitializer => typeForCPPASTEqualsInitializer(c, stripKeywords)
case _: IASTIdExpression | _: IASTName | _: IASTDeclarator => cleanType(safeGetNodeType(node), stripKeywords)
case s: IASTNamedTypeSpecifier => cleanType(ASTStringUtil.getReturnTypeString(s, null), stripKeywords)
case s: IASTCompositeTypeSpecifier => cleanType(ASTStringUtil.getReturnTypeString(s, null), stripKeywords)
case s: IASTEnumerationSpecifier => cleanType(ASTStringUtil.getReturnTypeString(s, null), stripKeywords)
case s: IASTElaboratedTypeSpecifier => cleanType(ASTStringUtil.getReturnTypeString(s, null), stripKeywords)
case l: IASTLiteralExpression => cleanType(safeGetType(l.getExpressionType))
case e: IASTExpression => cleanType(safeGetNodeType(e), stripKeywords)
case c: ICPPASTConstructorInitializer => typeForICPPASTConstructorInitializer(c)
case _ => cleanType(getNodeSignature(node), stripKeywords)
case f: IASTFieldReference => cleanType(safeGetType(f.getFieldOwner.getExpressionType), stripKeywords)
case s: IASTNamedTypeSpecifier => cleanType(ASTStringUtil.getReturnTypeString(s, null), stripKeywords)
case s: IASTCompositeTypeSpecifier => cleanType(ASTStringUtil.getReturnTypeString(s, null), stripKeywords)
case s: IASTEnumerationSpecifier => cleanType(ASTStringUtil.getReturnTypeString(s, null), stripKeywords)
case s: IASTElaboratedTypeSpecifier => cleanType(ASTStringUtil.getReturnTypeString(s, null), stripKeywords)
case l: IASTLiteralExpression => cleanType(safeGetType(l.getExpressionType), stripKeywords)
case e: IASTExpression => cleanType(safeGetNodeType(e), stripKeywords)
case _ => cleanType(getNodeSignature(node), stripKeywords)
}
}

private def typeForCPPAstNamedTypeSpecifier(s: ICPPASTNamedTypeSpecifier, stripKeywords: Boolean): String = {
val tpe = safeGetBinding(s).map(_.toString.replace(" ", "")).getOrElse(ASTStringUtil.getReturnTypeString(s, null))
cleanType(tpe, stripKeywords)
}

private def notHandledText(node: IASTNode): String =
s"""Node '${node.getClass.getSimpleName}' not handled yet!
| Code: '${node.getRawSignature}'
Expand Down Expand Up @@ -305,6 +357,9 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
r
}

protected def nullSafeAst(node: IASTInitializer): Ast =
Option(node).map(astForNode).getOrElse(Ast())

protected def nullSafeAst(node: IASTExpression): Ast =
Option(node).map(astForNode).getOrElse(Ast())

Expand All @@ -322,7 +377,10 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
}

private def pointersAsString(spec: IASTDeclSpecifier, parentDecl: IASTDeclarator, stripKeywords: Boolean): String = {
val tpe = typeFor(spec, stripKeywords)
val tpe = typeFor(spec, stripKeywords) match {
case Defines.Auto => typeFor(parentDecl, stripKeywords)
case t => t
}
val pointers = parentDecl.getPointerOperators
val arr = parentDecl match {
case p: IASTArrayDeclarator => p.getArrayModifiers.toList.map(_.getRawSignature).mkString
Expand Down Expand Up @@ -425,6 +483,7 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
case l: IASTInitializerList => astForInitializerList(l)
case c: ICPPASTConstructorInitializer => astForCPPASTConstructorInitializer(c)
case d: ICASTDesignatedInitializer => astForCASTDesignatedInitializer(d)
case d: IASTEqualsInitializer => astForNode(d.getInitializerClause)
case d: ICPPASTDesignatedInitializer => astForCPPASTDesignatedInitializer(d)
case d: CASTArrayRangeDesignator => astForCASTArrayRangeDesignator(d)
case d: CPPASTArrayRangeDesignator => astForCPPASTArrayRangeDesignator(d)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTIdExpression
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTQualifiedName
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPClosureType
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.EvalFunctionCall
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTFoldExpression

import scala.util.Try

Expand Down Expand Up @@ -521,6 +522,26 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
private def astForPackExpansionExpression(packExpansionExpression: ICPPASTPackExpansionExpression): Ast =
astForExpression(packExpansionExpression.getPattern)

private def astForFoldExpression(foldExpression: CPPASTFoldExpression): Ast = {
def valueFromField[T](obj: Any, fieldName: String): Option[T] = {
// we need this hack because fields are all private at CPPASTExpression
Try {
val field = obj.getClass.getDeclaredField(fieldName)
field.setAccessible(true)
field.get(obj).asInstanceOf[T]
}.toOption
}

val op = "<operator>.fold"
val tpe = typeFor(foldExpression)
val callNode_ =
callNode(foldExpression, code(foldExpression), op, op, DispatchTypes.STATIC_DISPATCH, None, Some(tpe))

val left = valueFromField[ICPPASTExpression](foldExpression, "fLhs").map(nullSafeAst).getOrElse(Ast())
val right = valueFromField[ICPPASTExpression](foldExpression, "fRhs").map(nullSafeAst).getOrElse(Ast())
callAst(callNode_, List(left, right))
}

protected def astForExpression(expression: IASTExpression): Ast = {
val r = expression match {
case lit: IASTLiteralExpression => astForLiteral(lit)
Expand All @@ -542,6 +563,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
case lambdaExpression: ICPPASTLambdaExpression => astForMethodRefForLambda(lambdaExpression)
case cExpr: IGNUASTCompoundStatementExpression => astForCompoundStatementExpression(cExpr)
case pExpr: ICPPASTPackExpansionExpression => astForPackExpansionExpression(pExpr)
case foldExpression: CPPASTFoldExpression => astForFoldExpression(foldExpression)
case _ => notHandledYet(expression)
}
asChildOfMacroCall(expression, r)
Expand Down
Loading

0 comments on commit 166cfab

Please sign in to comment.