diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt index 10c18881a7..a1bc20c230 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/SymbolResolver.kt @@ -517,6 +517,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { source.scope, ) val language = source.language + val sourceCall = source as? CallExpression if (language == null) { result.success = CallResolutionResult.SuccessKind.PROBLEMATIC @@ -527,29 +528,43 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { val (scope, _) = ctx.scopeManager.extractScope(source, source.scope) result.actualStartScope = scope ?: source.scope - // If the function does not allow function overloading, and we have multiple candidate - // symbols, the result is "problematic" - if (source.language !is HasFunctionOverloading && result.candidateFunctions.size > 1) { - result.success = PROBLEMATIC - } - - // Filter functions that match the signature of our call, either directly or with casts; - // those functions are "viable". Take default arguments into account if the language has - // them. - result.signatureResults = - result.candidateFunctions - .map { - Pair( - it, - it.matchesSignature( - arguments.map(Expression::type), - arguments, - source.language is HasDefaultArguments, + // Resolution depends on language features + if (source.language !is HasFunctionOverloading) { + // If the function does not allow function overloading, and we have multiple candidate + // symbols, the result is "problematic" + if (result.candidateFunctions.size > 1) { + result.success = CallResolutionResult.SuccessKind.PROBLEMATIC + } else + // If we have only one candidate function and the number of arguments match, we can take + // a shortcut and stop here + if ( + result.candidateFunctions.size == 1 && + result.candidateFunctions.first().parameters.size == sourceCall?.arguments?.size + ) { + result.signatureResults = + result.candidateFunctions.associateWith { + SignatureMatches(mutableListOf(DirectMatch)) + } + } + } else { + // Filter functions that match the signature of our call, either directly or with + // casts; those functions are "viable". Take default arguments into account if the + // language has them. + result.signatureResults = + result.candidateFunctions + .map { + Pair( + it, + it.matchesSignature( + arguments.map(Expression::type), + arguments, + source.language is HasDefaultArguments, + ) ) - ) - } - .filter { it.second is SignatureMatches } - .associate { it } + } + .filter { it.second is SignatureMatches } + .associate { it } + } result.viableFunctions = result.signatureResults.keys // If we have a "problematic" result, we can stop here. In this case we cannot really diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXDeclarationTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXDeclarationTest.kt index b874b2a2ea..c20b528452 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXDeclarationTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXDeclarationTest.kt @@ -104,12 +104,12 @@ class CXXDeclarationTest { declarations.forEach { assertEquals(definition, it.definition) } - // without the "std" lib, int will not match with size_t and we will infer a new function; - // and this will actually result in a problematic resolution, since C does not allow - // function overloading. + // As C does not support function overload, we can resolve the call to foo even if we do not + // know the argument type (due to missing includes), only by the name of the function and + // the number of arguments. val inferredDefinition = result.functions[{ it.name.localName == "foo" && !it.isDefinition && it.isInferred }] - assertNotNull(inferredDefinition) + assertNull(inferredDefinition) } @Test diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index abd331f2d9..9f32a1e584 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.* +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.ConstructorDeclaration @@ -749,6 +750,26 @@ class CallResolverTest : BaseTest() { assertEquals(2, declarations.size) } + @Test + @Throws(Exception::class) + fun testCFunctionResolution() { + val file = File("src/test/resources/calls/c-function-resolution.c") + val tu = + analyzeAndGetFirstTU(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + } + + val funcFoo = tu.functions["foo"] + assertNotNull(funcFoo) + assertFalse(funcFoo.isInferred) + + val fooCalls = tu.calls("foo") + fooCalls.forEach { assertContains(it.invokes, funcFoo) } + + val barCalls = tu.calls("bar") + barCalls.forEach { assertTrue { it.invokes.first().isInferred } } + } + companion object { private val topLevel = Path.of("src", "test", "resources", "calls") } diff --git a/cpg-language-cxx/src/test/resources/calls/c-function-resolution.c b/cpg-language-cxx/src/test/resources/calls/c-function-resolution.c new file mode 100644 index 0000000000..db200b1724 --- /dev/null +++ b/cpg-language-cxx/src/test/resources/calls/c-function-resolution.c @@ -0,0 +1,14 @@ +//#define MY_CONST_INT 1 + +void foo(int i) { +} +void bar(int i) { +} + +int main() { + foo(MY_CONST_INT); + foo(1); + bar(1,2); + return 0; +} +