From 90a1534bb7154e0f875c13e630d21bc2806ca95b Mon Sep 17 00:00:00 2001 From: Tobias Specht Date: Wed, 20 Nov 2024 23:25:12 +0100 Subject: [PATCH] Fix overload operator-> for CallExpression (#1842) * Handle overloaded operator-> for CallExpression * Add test case for handle overloaded operator-> for CallExpression --- .../aisec/cpg/passes/SymbolResolver.kt | 53 ++++++++++++------- .../cpg/frontends/cxx/CXXDeclarationTest.kt | 44 +++++++++++++++ .../cxx/operators/call_expression.cpp | 27 ++++++++++ 3 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 cpg-language-cxx/src/test/resources/cxx/operators/call_expression.cpp 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..42722b74f7 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 @@ -322,6 +322,35 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } + /** + * This function resolves a possible overloaded -> (arrow) operator, for languages which support + * operator overloading. The implicit call to the overloaded operator function is inserted as + * base for the MemberExpression. This can be the case for a [MemberExpression] or + * [MemberCallExpression] + */ + private fun resolveOverloadedArrowOperator(ex: Expression): Type? { + var type: Type? = null + if ( + ex.language is HasOperatorOverloading && + ex is MemberExpression && + ex.operatorCode == "->" && + ex.base.type !is PointerType + ) { + val result = resolveOperator(ex) + val op = result?.bestViable?.singleOrNull() + if (result?.success == SUCCESSFUL && op is OperatorDeclaration) { + type = op.returnTypes.singleOrNull()?.root ?: unknownType() + + // We need to insert a new operator call expression in between + val call = operatorCallFromDeclaration(op, ex) + + // Make the call our new base + ex.base = call + } + } + return type + } + protected fun resolveMember( containingClass: ObjectType, reference: Reference @@ -334,25 +363,8 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { var member: ValueDeclaration? = null var type: Type = containingClass - // Check for a possible overloaded operator-> - if ( - reference.language is HasOperatorOverloading && - reference is MemberExpression && - reference.operatorCode == "->" && - reference.base.type !is PointerType - ) { - val result = resolveOperator(reference) - val op = result?.bestViable?.singleOrNull() - if (result?.success == SUCCESSFUL && op is OperatorDeclaration) { - type = op.returnTypes.singleOrNull()?.root ?: unknownType() - - // We need to insert a new operator call expression in between - val call = operatorCallFromDeclaration(op, reference) - - // Make the call our new base - reference.base = call - } - } + // Handle a possible overloaded operator-> + type = resolveOverloadedArrowOperator(reference) ?: type val record = type.recordDeclaration if (record != null) { @@ -398,6 +410,9 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { val callee = call.callee val language = call.language + // Handle a possible overloaded operator-> + resolveOverloadedArrowOperator(callee) + // Dynamic function invokes (such as function pointers) are handled by extra pass, so we are // not resolving them here. // 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..b0ade57095 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 @@ -306,4 +306,48 @@ class CXXDeclarationTest { assertEquals(p, opCall.base) assertInvokes(opCall, op) } + + @Test + fun testCallExpressionOperator() { + val file = File("src/test/resources/cxx/operators/call_expression.cpp") + val result = + analyze(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + } + assertNotNull(result) + + var proxy = result.records["Proxy"] + assertNotNull(proxy) + + var funcBar = proxy.functions["bar"] + assertNotNull(funcBar) + + var op = proxy.operators["operator->"] + assertNotNull(op) + + var data = result.records["Data"] + assertNotNull(data) + + var funcFoo = data.functions["foo"] + assertNotNull(funcFoo) + + val p = result.refs["p"] + assertNotNull(p) + assertEquals(proxy.toType(), p.type) + + var funcFooRef = result.memberExpressions["foo"] + assertNotNull(funcFooRef) + assertRefersTo(funcFooRef, funcFoo) + + var funcBarRef = result.memberExpressions["bar"] + assertNotNull(funcBarRef) + assertRefersTo(funcBarRef, funcBar) + + // we should now have an implicit call to our operator in-between "p" and "foo" + val opCall = funcFooRef.base + assertNotNull(opCall) + assertIs(opCall) + assertEquals(p, opCall.base) + assertInvokes(opCall, op) + } } diff --git a/cpg-language-cxx/src/test/resources/cxx/operators/call_expression.cpp b/cpg-language-cxx/src/test/resources/cxx/operators/call_expression.cpp new file mode 100644 index 0000000000..232d1df32a --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/operators/call_expression.cpp @@ -0,0 +1,27 @@ +struct Data { + int foo() { + return 1; + } +}; + +struct Proxy { + Data *data; + Proxy() { + data = new Data; + } + Data* operator->() { + return data; + } + int bar() { + return 1; + } +}; + +int main() { + Proxy p; + + int i = p->foo(); + int j = p.bar(); + return 1; +} +