From f055ceb5119878c29b728cd96b6150c20c0cc0e3 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sat, 29 Jun 2024 14:36:45 +0200 Subject: [PATCH] Resolving unary operators binary operator also works now --- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 5 +- .../aisec/cpg/graph/ExpressionBuilder.kt | 22 ++++++ .../fraunhofer/aisec/cpg/graph/Interfaces.kt | 2 +- .../aisec/cpg/graph/edge/PropertyEdge.kt | 2 +- .../statements/expressions/BinaryOperator.kt | 3 + .../expressions/MemberExpression.kt | 13 ++-- .../expressions/OperatorCallExpression.kt | 55 ++++++++++++++ .../statements/expressions/UnaryOperator.kt | 8 +- .../aisec/cpg/passes/SymbolResolver.kt | 73 ++++++++++++++++--- .../aisec/cpg/frontends/cxx/CLanguage.kt | 13 ---- .../aisec/cpg/frontends/cxx/CPPLanguage.kt | 36 +++++++++ .../cpg/frontends/cxx/DeclaratorHandler.kt | 6 +- .../cpg/frontends/cxx/CXXDeclarationTest.kt | 62 +++++++++++++++- .../methodresolution/overloadnoresolution.cpp | 2 +- .../resources/cxx/operators/member_access.cpp | 24 ++++++ 15 files changed, 285 insertions(+), 41 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/OperatorCallExpression.kt create mode 100644 cpg-language-cxx/src/test/resources/cxx/operators/member_access.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 7164d0fc421..7ad64e7526f 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 @@ -1203,7 +1203,10 @@ fun FunctionDeclaration.matchesSignature( * of the call resolution. */ data class CallResolutionResult( - /** The original call expression or a node that implements [HasCallableArguments]. */ + /** + * The original call expression or something that implements [HasCallableArguments] (e.g. an + * overloaded operator expression). + */ val call: HasCallableArguments, /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index 260e45f1355..316302b7a93 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -280,6 +280,28 @@ fun MetadataProvider.newCallExpression( return node } +/** + * Creates a new [MemberCallExpression]. The [MetadataProvider] receiver will be used to fill + * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin + * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional + * prepended argument. + */ +@JvmOverloads +fun MetadataProvider.newOperatorCallExpression( + operatorCode: String, + callee: Expression?, + rawNode: Any? = null +): OperatorCallExpression { + val node = OperatorCallExpression() + node.applyMetadata(this, operatorCode, rawNode) + + node.operatorCode = operatorCode + node.callee = callee + + log(node) + return node +} + /** * Creates a new [MemberCallExpression]. The [MetadataProvider] receiver will be used to fill * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Interfaces.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Interfaces.kt index 3df01459f77..999f261b599 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Interfaces.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Interfaces.kt @@ -149,4 +149,4 @@ interface HasCallableArguments : HasLanguage, HasNameAndLocation, HasScope { * Specifies that this node (e.g. a [BinaryOperator] contains an operation that can be overloaded by * an [OperatorDeclaration]. */ -interface HasOverloadedOperation : HasBase +interface HasOverloadedOperation : HasCallableArguments, HasBase, HasType diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index f9fb9408f77..be3274f3b77 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -370,7 +370,7 @@ open class PropertyEdge : Persistable { @Transient class PropertyEdgeDelegate( val edge: KProperty1>>, - val outgoing: Boolean = true + val outgoing: Boolean = true, ) { operator fun getValue(thisRef: S, property: KProperty<*>): List { return PropertyEdge.unwrap(edge.get(thisRef), outgoing) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index 7624ab0c5f6..03ce018a923 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -72,6 +72,9 @@ open class BinaryOperator : } } + override val arguments + get() = listOf(rhs) + private fun connectNewLhs(lhs: Expression) { lhs.registerTypeObserver(this) if (lhs is Reference && "=" == operatorCode) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt index 9315f056e2a..571e8fc755c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/MemberExpression.kt @@ -25,12 +25,8 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.ArgumentHolder -import de.fraunhofer.aisec.cpg.graph.HasBase -import de.fraunhofer.aisec.cpg.graph.HasOverloadedOperation +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration -import de.fraunhofer.aisec.cpg.graph.fqn import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.Objects @@ -98,4 +94,11 @@ class MemberExpression : Reference(), HasOverloadedOperation, ArgumentHolder, Ha private fun updateName() { this.name = base.type.root.name.fqn(name.localName) } + + /** + * This is actually just needed for operator overloading to member access expressions (which is + * possible in languages, such as C++). The arguments are always empty. + */ + override val arguments: List + get() = listOf() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/OperatorCallExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/OperatorCallExpression.kt new file mode 100644 index 00000000000..211f1ff0636 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/OperatorCallExpression.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.graph.statements.expressions + +import de.fraunhofer.aisec.cpg.graph.HasBase +import de.fraunhofer.aisec.cpg.graph.HasOperatorCode +import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.declarations.OperatorDeclaration + +/** + * This special call expression is used when an operator (such as a [BinaryOperator]) is overloaded. + * In this case, we replace the original [BinaryOperator] with an [OperatorCallExpression], which + * points to its respective [OperatorDeclaration]. + */ +class OperatorCallExpression : CallExpression(), HasOperatorCode, HasBase { + + override var operatorCode: String? = null + + override var name: Name + get() = Name(operatorCode ?: "") + set(_) {} + + /** + * The base object. This is basically a shortcut to accessing the base of the [callee], if it + * has one (i.e., if it implements [HasBase]). This is the case for example, if it is a + * [MemberExpression]. + */ + override val base: Expression? + get() { + return (callee as? HasBase)?.base + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt index ac0885ffdf9..72190d284ae 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/UnaryOperator.kt @@ -25,11 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.AccessValues -import de.fraunhofer.aisec.cpg.graph.ArgumentHolder -import de.fraunhofer.aisec.cpg.graph.HasOverloadedOperation -import de.fraunhofer.aisec.cpg.graph.pointer +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.Type import org.apache.commons.lang3.builder.ToStringBuilder @@ -63,6 +59,8 @@ class UnaryOperator : Expression(), HasOverloadedOperation, ArgumentHolder, HasT /** Specifies, whether this a pre fix operation. */ var isPrefix = false + override val arguments = listOf() + private fun changeExpressionAccess() { var access = AccessValues.READ if (operatorCode == "++" || operatorCode == "--") { 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 b4a5b213df7..1edbb2f7450 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 @@ -341,7 +341,38 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { return null } var member: ValueDeclaration? = null - val record = containingClass.recordDeclaration + var type = containingClass + + // Check for a possible overloaded operator-> (C++ only?!) + if ( + reference.language is HasOperatorOverloading && + reference is MemberExpression && + reference.operatorCode == "->" && + reference.base.type !is PointerType + ) { + var op = + resolveCalleeByName("operator->", reference) + .filterIsInstance() + .singleOrNull() + + if (op != null) { + type = op.returnTypes.singleOrNull()?.root ?: unknownType() + + // We need to insert a new operator call expression in between + val ref = + newMemberExpression(op.name, reference.base, operatorCode = ".") + .implicit(op.name.localName, location = reference.location) + ref.refersTo = op + var call = + newOperatorCallExpression(operatorCode = "->", ref).codeAndLocationFrom(ref) + call.invokes = listOf(op) + + // Make the call our new base + reference.base = call + } + } + + val record = type.recordDeclaration if (record != null) { member = record.fields @@ -351,8 +382,8 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } if (member == null) { member = - (containingClass.recordDeclaration?.superTypeDeclarations?.flatMap { it.fields } - ?: listOf()) + type.superTypes + .flatMap { it.recordDeclaration?.fields ?: listOf() } .filter { it.name.localName == reference.name.localName } .map { it.definition } .firstOrNull() @@ -446,6 +477,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { is Reference -> handleReference(currClass, node) is ConstructExpression -> handleConstructExpression(node) is CallExpression -> handleCallExpression(node) + is HasOverloadedOperation -> handleOverloadedOperator(node) } } @@ -645,6 +677,27 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } + private fun handleOverloadedOperator(operator: HasOverloadedOperation) { + val language = operator.language + if (language !is HasOperatorOverloading || language.isPrimitive(operator.type)) { + return + } + + val symbol = language.overloadedOperatorNames[Pair(operator::class, operator.operatorCode)] + if (symbol == null) { + log.warn( + "Could not resolve operator overloading for unknown operatorCode ${operator.operatorCode}" + ) + return + } + + // operator.invokes = + // resolveCalleeByName(symbol, operator).filterIsInstance() + // TODO: replace with call + val ops = resolveCalleeByName(symbol, operator).filterIsInstance() + println(ops) + } + /** * Returns a set of types in which the callee of our [call] could reside in. More concretely, it * returns a [Pair], where the first element is the set of types and the second is our best @@ -653,19 +706,19 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { protected fun getPossibleContainingTypes(call: HasCallableArguments): Pair, Type?> { val possibleTypes = mutableSetOf() var bestGuess: Type? = null - if (call is MemberCallExpression) { - call.base?.let { base -> - bestGuess = base.type - possibleTypes.add(base.type) - possibleTypes.addAll(base.assignedTypes) - } - } else { + if (call is CallExpression && call !is MemberCallExpression) { // This could be a C++ member call with an implicit this (which we do not create), so // let's add the current class to the possible list scopeManager.currentRecord?.toType()?.let { bestGuess = it possibleTypes.add(it) } + } else if (call is HasBase) { + call.base?.let { base -> + bestGuess = base.type + possibleTypes.add(base.type) + possibleTypes.addAll(base.assignedTypes) + } } return Pair(possibleTypes, bestGuess) diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt index b510edd3023..ad24b119052 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CLanguage.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.frontends.cxx import com.fasterxml.jackson.annotation.JsonIgnore import de.fraunhofer.aisec.cpg.frontends.* -import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration import de.fraunhofer.aisec.cpg.graph.types.* import kotlin.reflect.KClass import org.neo4j.ogm.annotation.Transient @@ -130,18 +129,6 @@ open class CLanguage : return ImplicitCast } - // Another special rule is that if we have a const reference (e.g. const T&) in a function - // call, this will match the type T because this means that the parameter is given by - // reference rather than by value. - if ( - targetType is ReferenceType && - targetType.elementType == type && - targetHint is ParameterDeclaration && - CONST in targetHint.modifiers - ) { - return DirectMatch - } - return CastNotPossible } } diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt index 9ee1f64b389..b262566d8dd 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CPPLanguage.kt @@ -25,12 +25,15 @@ */ package de.fraunhofer.aisec.cpg.frontends.cxx +import de.fraunhofer.aisec.cpg.CallResolutionResult +import de.fraunhofer.aisec.cpg.SignatureMatches import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.frontends.* import de.fraunhofer.aisec.cpg.graph.HasOverloadedOperation import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.primitiveType import de.fraunhofer.aisec.cpg.graph.scopes.Symbol import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression @@ -38,6 +41,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.graph.types.* +import de.fraunhofer.aisec.cpg.matchesSignature import de.fraunhofer.aisec.cpg.passes.* import de.fraunhofer.aisec.cpg.passes.inference.startInference import kotlin.reflect.KClass @@ -171,6 +175,17 @@ open class CPPLanguage : return match } + // Another special rule is that if we have a (const) reference (e.g. const T&) in a function + // call, this will match the type T because this means that the parameter is given by + // reference rather than by value. + if ( + targetType is ReferenceType && + targetType.elementType == type && + targetHint is ParameterDeclaration + ) { + return DirectMatch + } + // In C++, it is possible to have conversion constructors. We will not have full support for // them yet, but at least we should have some common cases here, such as const char* to // std::string @@ -185,6 +200,27 @@ open class CPPLanguage : return CastNotPossible } + override fun bestViableResolution( + result: CallResolutionResult + ): Pair, CallResolutionResult.SuccessKind> { + // There is a sort of weird workaround in C++ to select a prefix vs. postfix operator for + // increment and decrement operators. See + // https://en.cppreference.com/w/cpp/language/operator_incdec + val expr = result.call + if (expr is UnaryOperator && (expr.operatorCode == "++" || expr.operatorCode == "--")) { + // If it is a postfix, we need to match for a function with a fake "int" parameter + if (expr.isPostfix) { + result.signatureResults = + result.candidateFunctions + .map { Pair(it, it.matchesSignature(listOf(primitiveType("int")))) } + .filter { it.second is SignatureMatches } + .associate { it } + } + } + + return super.bestViableResolution(result) + } + override val startCharacter = '<' override val endCharacter = '>' diff --git a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt index 0fd21a046c6..5e885749600 100644 --- a/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt +++ b/cpg-language-cxx/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/DeclaratorHandler.kt @@ -171,7 +171,7 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : // Check if it's an operator name.isKnownOperatorName -> { // retrieve the operator code - var operatorCode = name.localName.drop("operator".length) + val operatorCode = name.localName.drop("operator".length) newOperatorDeclaration(name, operatorCode, rawNode = ctx) } // Check, if it's a constructor. This is the case if the local names of the function @@ -511,9 +511,9 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : } /** Checks whether the [Name] for a function is a known operator name. */ - val Name.isKnownOperatorName: Boolean + private val Name.isKnownOperatorName: Boolean get() { - var language = language + val language = language if (language !is HasOperatorOverloading) { return false } 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 6bfae94a613..64a4f7d8c93 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 @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block +import de.fraunhofer.aisec.cpg.graph.statements.expressions.OperatorCallExpression import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType import de.fraunhofer.aisec.cpg.test.* import java.io.File @@ -242,8 +243,67 @@ class CXXDeclarationTest { } assertNotNull(result) - var plusplus = result.operators["operator++"] + val integer = result.records["Integer"] + assertNotNull(integer) + + val plusplus = integer.operators["operator++"] assertNotNull(plusplus) assertEquals("++", plusplus.operatorCode) + + val plus = integer.operators("operator+") + assertEquals(2, plus.size) + assertEquals("+", plus.map { it.operatorCode }.distinct().singleOrNull()) + + val main = result.functions["main"] + assertNotNull(main) + + /*var unaryOp = main.allChildren().firstOrNull() + assertNotNull(unaryOp) + assertInvokes(unaryOp, plusplus) + + val binaryOp0 = main.allChildren().getOrNull(0) + assertNotNull(binaryOp0) + assertInvokes(binaryOp0, plus.getOrNull(0)) + + val binaryOp1 = main.allChildren().getOrNull(1) + assertNotNull(binaryOp1) + assertInvokes(binaryOp1, plus.getOrNull(1))*/ + } + + @Test + fun testMemberAccess() { + val file = File("src/test/resources/cxx/operators/member_access.cpp") + val result = + analyze(listOf(file), file.parentFile.toPath(), true) { + it.registerLanguage() + } + assertNotNull(result) + + var proxy = result.records["Proxy"] + assertNotNull(proxy) + + var op = proxy.operators["operator->"] + assertNotNull(op) + + var data = result.records["Data"] + assertNotNull(data) + + var size = data.fields["size"] + assertNotNull(size) + + val p = result.refs["p"] + assertNotNull(p) + assertEquals(proxy.toType(), p.type) + + var sizeRef = result.memberExpressions["size"] + assertNotNull(sizeRef) + assertRefersTo(sizeRef, size) + + // we should now have an implicit call to our operator in-between "p" and "size" + val opCall = sizeRef.base + assertNotNull(opCall) + assertIs(opCall) + assertEquals(p, opCall.base) + assertInvokes(opCall, op) } } diff --git a/cpg-language-cxx/src/test/resources/calls/cxxprioresolution/methodresolution/overloadnoresolution.cpp b/cpg-language-cxx/src/test/resources/calls/cxxprioresolution/methodresolution/overloadnoresolution.cpp index 5d1da9cea6f..9eab39df76c 100644 --- a/cpg-language-cxx/src/test/resources/calls/cxxprioresolution/methodresolution/overloadnoresolution.cpp +++ b/cpg-language-cxx/src/test/resources/calls/cxxprioresolution/methodresolution/overloadnoresolution.cpp @@ -19,6 +19,6 @@ class Overloaded : public Base int main() { Overloaded overload; - cout << overload.calc(1) << '\n'; + overload.calc(1); return 0; } \ No newline at end of file diff --git a/cpg-language-cxx/src/test/resources/cxx/operators/member_access.cpp b/cpg-language-cxx/src/test/resources/cxx/operators/member_access.cpp new file mode 100644 index 00000000000..5136efc645d --- /dev/null +++ b/cpg-language-cxx/src/test/resources/cxx/operators/member_access.cpp @@ -0,0 +1,24 @@ +class Data { +public: + int size; +}; + +class Proxy { +public: + Proxy() { + this->data = new Data(); + } + + Data* operator->() { + return data; + } + + Data* data; +}; + +int main() { + Proxy p; + int size = p->size; + + // int another_size = p.operator->()->size; +} \ No newline at end of file