From 98981559fe54a6b69bc7b05e4dcb7c38fadd00d5 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 12 Dec 2024 20:45:42 +0100 Subject: [PATCH] Better detection of typedefs in C++ (#1896) --- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 2 +- .../cpg/frontends/cxx/DeclarationHandler.kt | 20 ++++---- .../cpg/enhancements/types/TypedefTest.kt | 50 ++++++++++++++++++- .../src/test/resources/c/typedef_struct.c | 13 +++++ .../src/test/resources/cxx/typedef_struct.cpp | 15 ++++++ .../resources/typedefs/weird_typedefs.cpp | 19 +++++++ 6 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 cpg-language-cxx/src/test/resources/c/typedef_struct.c create mode 100644 cpg-language-cxx/src/test/resources/cxx/typedef_struct.cpp create mode 100644 cpg-language-cxx/src/test/resources/typedefs/weird_typedefs.cpp diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt index 01530f6db2..e20f32ed04 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt @@ -818,7 +818,7 @@ class ScopeManager : ScopeProvider { // This process has several steps: // First, do a quick local lookup, to see if we have a typedef our current scope // (only do this if the name is not qualified) - if (!alias.isQualified() && current == currentScope) { + if (!alias.isQualified() && current == scope) { val decl = current.typedefs[alias] if (decl != null) { return decl.type diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt index 210aa97d68..0656e7d21e 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclarationHandler.kt @@ -242,18 +242,20 @@ class DeclarationHandler(lang: CXXLanguageFrontend) : */ private val IASTSimpleDeclaration.isTypedef: Boolean get() { - return if (this.rawSignature.contains("typedef")) { - if (this.declSpecifier is CPPASTCompositeTypeSpecifier) { - // we need to make a difference between structs that have typedefs and structs - // that are typedefs themselves - this.declSpecifier.toString() == "struct" && - this.rawSignature.trim().startsWith("typedef") + return if (this.declSpecifier is IASTCompositeTypeSpecifier) { + if (this.declSpecifier.rawSignature.contains("typedef")) { + // This is very stupid. For composite type specifiers, we need to make sure that + // we do not match simply because our declarations contain a typedef. + // The problem is that we cannot correctly detect the case where both our "main" + // declaration and our sub declarations contain a typedef :( + (this.declSpecifier as IASTCompositeTypeSpecifier).getDeclarations(true).none { + it.rawSignature.contains("typedef") + } } else { - - true + false } } else { - false + this.declSpecifier.rawSignature.contains("typedef") } } diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt index 319cb8b077..0a85008599 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/types/TypedefTest.kt @@ -25,6 +25,8 @@ */ package de.fraunhofer.aisec.cpg.enhancements.types +import de.fraunhofer.aisec.cpg.InferenceConfiguration.Companion.builder +import de.fraunhofer.aisec.cpg.frontends.cxx.CLanguage import de.fraunhofer.aisec.cpg.frontends.cxx.CPPLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration @@ -34,6 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.types.NumericType import de.fraunhofer.aisec.cpg.graph.types.ObjectType import de.fraunhofer.aisec.cpg.graph.variables import de.fraunhofer.aisec.cpg.test.* +import java.io.File import java.nio.file.Path import kotlin.test.* @@ -187,7 +190,7 @@ internal class TypedefTest : BaseTest() { fun testArbitraryTypedefLocation() { val tu = analyzeAndGetFirstTU( - listOf(topLevel.resolve("typedefs.cpp").toFile()), + listOf(topLevel.resolve("weird_typedefs.cpp").toFile()), topLevel, true ) { @@ -195,8 +198,15 @@ internal class TypedefTest : BaseTest() { } val ullong1 = tu.variables["someUllong1"] + assertNotNull(ullong1) + val ullong2 = tu.variables["someUllong2"] - assertEquals(ullong1?.type, ullong2?.type) + assertNotNull(ullong2) + assertEquals(ullong1.type, ullong2.type) + + val records = tu.records + assertEquals(2, records.size) + assertEquals(listOf("bar", "foo"), records.map { it.name.localName }) } @Test @@ -243,4 +253,40 @@ internal class TypedefTest : BaseTest() { assertNotNull(size) assertRefersTo(size, sizeField) } + + @Test + fun testTypedefStructCPP() { + val file = File("src/test/resources/cxx/typedef_struct.cpp") + val tu = + analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + it.inferenceConfiguration(builder().enabled(false).build()) + } + with(tu) { + val me = tu.memberExpressions + me.forEach { assertNotNull(it.refersTo) } + + val test = tu.records.singleOrNull() + assertNotNull(test) + assertLocalName("test", test) + } + } + + @Test + fun testTypedefStructC() { + val file = File("src/test/resources/c/typedef_struct.c") + val tu = + analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + it.inferenceConfiguration(builder().enabled(false).build()) + } + with(tu) { + val me = tu.memberExpressions + me.forEach { assertNotNull(it.refersTo) } + + val test = tu.records.singleOrNull() + assertNotNull(test) + assertLocalName("test", test) + } + } } diff --git a/cpg-language-cxx/src/test/resources/c/typedef_struct.c b/cpg-language-cxx/src/test/resources/c/typedef_struct.c new file mode 100644 index 0000000000..788a6e8f6d --- /dev/null +++ b/cpg-language-cxx/src/test/resources/c/typedef_struct.c @@ -0,0 +1,13 @@ +typedef struct test { + int a; + int b; +} S; + +int structs() { + S s; + S t; + S* p=&s; + s.a=1; + s.b=2; + printf("%d %d\n", s.a, s.b); + } \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/cxx/typedef_struct.cpp b/cpg-language-cxx/src/test/resources/cxx/typedef_struct.cpp new file mode 100644 index 0000000000..cdfb0f4031 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/typedef_struct.cpp @@ -0,0 +1,15 @@ +typedef struct test { + int a; + int b; +} S; + +int structs() { + S s; + S t; + S* p=&s; + s.a=1; + s.b=2; + printf("%d %d\n", s.a, s.b); + } + + long typedef bla; \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/typedefs/weird_typedefs.cpp b/cpg-language-cxx/src/test/resources/typedefs/weird_typedefs.cpp new file mode 100644 index 0000000000..b5826bbb33 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/typedefs/weird_typedefs.cpp @@ -0,0 +1,19 @@ +// typedef can be used anywhere in the decl-specifier-seq +// more conventionally spelled "typedef unsigned long long int ullong;" +unsigned long typedef long int ullong; + +// usage of type that is identical to typedef +unsigned long long int someUllong1; +// usage of typedef +ullong someUllong2; + +// also possible with structs +struct bar { + int a; + int b; +} typedef baz; + +// just some type that contains a typedef for more confusion +struct foo { + typedef const int a; +}; \ No newline at end of file