From 613a6842302c081d8431ab1fa16c80c30fdda758 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 21 Nov 2023 09:42:08 +0100 Subject: [PATCH] Improved `guessCastExpressions` in C++ frontend (#1357) There were more ambiguities when guesssing cast expressions, so I added them. Fixes #1347 --- .../cpg/frontends/cxx/ExpressionHandler.kt | 22 +++++++++- .../frontends/cxx/CXXLanguageFrontendTest.kt | 44 ++++++++++++++----- .../src/test/resources/cxx/parenthesis.cpp | 14 ++++++ 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt index ac785bda0b..06f5b1ae2f 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/ExpressionHandler.kt @@ -345,9 +345,10 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : val typeName = (ctx.operand as IASTIdExpression).name.toString() if (frontend.typeManager.typeExists(typeName)) { val cast = newCastExpression(frontend.codeOf(ctx)) + cast.setCastOperator(0) cast.castType = frontend.typeOf((ctx.operand as IASTIdExpression).name) // The expression member can only be filled by the parent call - // (handleFunctionCallExpression) + // (handleFunctionCallExpression and handleBinaryExpression) cast.location = frontend.locationOf(ctx) return cast } @@ -429,7 +430,8 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : ) .forEach { callExpression.addTemplateParameter(it) } } - reference is CastExpression -> { + reference is CastExpression && + frontend.config.inferenceConfiguration.guessCastExpressions -> { // this really is a cast expression in disguise reference.expression = ctx.arguments.firstOrNull()?.let { handle(it) } @@ -501,6 +503,22 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : handle(ctx.initOperand2) } ?: newProblemExpression("could not parse rhs") + + if (lhs is CastExpression && frontend.config.inferenceConfiguration.guessCastExpressions) { + // this really is a combination of a cast and a unary operator + val op = + newUnaryOperator( + operatorCode, + postfix = true, + prefix = false, + code = ctx.rawSignature + ) + op.input = rhs + op.location = frontend.locationOf(ctx.operand2) + lhs.expression = op + return lhs + } + binaryOperator.lhs = lhs binaryOperator.rhs = rhs diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index ba48292e35..ca7769e965 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -1320,21 +1320,43 @@ internal class CXXLanguageFrontendTest : BaseTest() { it.inferenceConfiguration(builder().guessCastExpressions(true).build()) it.registerLanguage() } - val main = - tu.getDeclarationsByName("main", FunctionDeclaration::class.java).iterator().next() + val main = tu.functions["main"] assertNotNull(main) - val declStatement = main.getBodyStatementAs(0, DeclarationStatement::class.java) - assertNotNull(declStatement) + val count = tu.variables["count"] + assertNotNull(count) - val decl = declStatement.singleDeclaration as VariableDeclaration - assertNotNull(decl) + var cast = count.initializer + assertIs(cast) + assertLocalName("size_t", cast.castType) + assertLiteralValue(42, cast.expression) - val initializer = decl.initializer - assertNotNull(initializer) - assertTrue(initializer is CastExpression) - assertLocalName("size_t", initializer.castType) - assertLiteralValue(42, initializer.expression) + val addr = tu.variables["addr"] + assertNotNull(addr) + + cast = addr.initializer + assertIs(cast) + assertLocalName("int64_t", cast.castType) + + val unary = cast.expression + assertIs(unary) + + val refCount = unary.input + assertIs(refCount) + assertRefersTo(refCount, count) + + var paths = addr.followPrevDFGEdgesUntilHit { it == refCount } + assertTrue(paths.fulfilled.isNotEmpty()) + assertTrue(paths.failed.isEmpty()) + + val refKey = tu.refs["key"] + assertNotNull(refKey) + + val assign = tu.assignments.firstOrNull { it.value is UnaryOperator } + assertNotNull(assign) + paths = assign.value.followPrevDFGEdgesUntilHit { it == refKey } + assertTrue(paths.fulfilled.isNotEmpty()) + assertTrue(paths.failed.isEmpty()) } @Test diff --git a/cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp b/cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp index d884f25594..5c047c9da1 100644 --- a/cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp +++ b/cpg-language-cxx/src/test/resources/cxx/parenthesis.cpp @@ -1,4 +1,18 @@ +// These headers are just here so that we could compile it, if we want it, +// to check for errors with clang. We will not parse them. +#include +#include + int main() { + // this cast could be mistaken for a call expression size_t count = (size_t)(42); + + // this cast could be mistaken for a binary operation + int64_t addr = (int64_t) &count; + + // finally, a more complex example of unary operators and casts + char* outptr, key; + *(int64_t *)outptr = *(int64_t *)&key; + return 0; } \ No newline at end of file