From a0c3a4eca97a7129caf822e5b056ae8f4058230e 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 | 28 +-- .../aisec/cpg/frontends/Language.kt | 8 +- .../aisec/cpg/frontends/LanguageTraits.kt | 7 +- .../aisec/cpg/graph/ExpressionBuilder.kt | 22 +++ .../fraunhofer/aisec/cpg/graph/Interfaces.kt | 2 + .../aisec/cpg/graph/edge/PropertyEdge.kt | 2 +- .../statements/expressions/BinaryOperator.kt | 10 +- .../expressions/MemberExpression.kt | 14 +- .../expressions/OperatorCallExpression.kt | 55 ++++++ .../statements/expressions/UnaryOperator.kt | 14 +- .../aisec/cpg/passes/SymbolResolver.kt | 181 ++++++++++++------ .../aisec/cpg/frontends/cxx/CLanguage.kt | 13 -- .../aisec/cpg/frontends/cxx/CPPLanguage.kt | 52 ++++- .../cpg/frontends/cxx/DeclaratorHandler.kt | 6 +- .../cpg/frontends/cxx/CXXDeclarationTest.kt | 62 +++++- .../aisec/cpg/passes/CallResolverTest.kt | 4 +- .../methodresolution/overloadnoresolution.cpp | 2 +- .../resources/cxx/operators/member_access.cpp | 24 +++ 18 files changed, 389 insertions(+), 117 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 161e24788aa..8487c625b9c 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 @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.scopes.* import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType +import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.graph.types.IncompleteType import de.fraunhofer.aisec.cpg.graph.types.Type import de.fraunhofer.aisec.cpg.helpers.Util @@ -664,7 +665,7 @@ class ScopeManager : ScopeProvider { */ @JvmOverloads fun resolveFunctionLegacy( - call: CallExpression, + call: HasCallableArguments, startScope: Scope? = currentScope ): List { val (scope, name) = extractScope(call, startScope) @@ -672,7 +673,7 @@ class ScopeManager : ScopeProvider { val func = resolve(scope) { it.name.lastPartsMatch(name) && - it.matchesSignature(call.signature) != IncompatibleSignature + it.matchesSignature(call.arguments.map(HasType::type)) != IncompatibleSignature } return func @@ -687,7 +688,7 @@ class ScopeManager : ScopeProvider { * Note: The [CallExpression.callee] needs to be resolved first, otherwise the call resolution * fails. */ - fun resolveCall(call: CallExpression, startScope: Scope? = currentScope): CallResolutionResult { + fun resolveCall(call: CallExpression): CallResolutionResult { val result = CallResolutionResult( call, @@ -696,7 +697,7 @@ class ScopeManager : ScopeProvider { mapOf(), setOf(), CallResolutionResult.SuccessKind.UNRESOLVED, - startScope, + call.scope, ) val language = call.language @@ -709,7 +710,7 @@ class ScopeManager : ScopeProvider { // function val callee = call.callee as? Reference ?: return result - val (scope, _) = extractScope(callee, startScope) + val (scope, _) = extractScope(callee, call.scope) result.actualStartScope = scope // Retrieve a list of possible functions with a matching name @@ -736,7 +737,7 @@ class ScopeManager : ScopeProvider { it.matchesSignature( call.signature, call.language is HasDefaultArguments, - call + call.arguments ) ) } @@ -776,7 +777,7 @@ class ScopeManager : ScopeProvider { * @param scope the current scope relevant for the name resolution, e.g. parent of node * @return a pair with the scope of node.name and the alias-adjusted name */ - fun extractScope(node: Node, scope: Scope? = currentScope): Pair { + fun extractScope(node: HasNameAndLocation, scope: Scope? = currentScope): Pair { return extractScope(node.name, node.location, scope) } @@ -911,7 +912,7 @@ class ScopeManager : ScopeProvider { } fun resolveFunctionStopScopeTraversalOnDefinition( - call: CallExpression + call: HasCallableArguments ): List { return resolve(currentScope, true) { f -> f.name.lastPartsMatch(call.name) } } @@ -1133,7 +1134,7 @@ data class SignatureMatches(override val casts: List) : SignatureRes fun FunctionDeclaration.matchesSignature( signature: List, useDefaultArguments: Boolean = false, - call: CallExpression? = null, + argumentHints: List? = null, ): SignatureResult { val casts = mutableListOf() @@ -1155,7 +1156,7 @@ fun FunctionDeclaration.matchesSignature( // Check, if we can cast the arg into our target type; and if, yes, what is // the "distance" to the base type. We need this to narrow down the type during // resolving - val match = type.tryCast(param.type, call?.arguments?.getOrNull(i), param) + val match = type.tryCast(param.type, argumentHints?.getOrNull(i), param) if (match == CastNotPossible) { return IncompatibleSignature } @@ -1203,8 +1204,11 @@ fun FunctionDeclaration.matchesSignature( * of the call resolution. */ data class CallResolutionResult( - /** The original call expression. */ - val call: CallExpression, + /** + * The original call expression or something that implements [HasCallableArguments] (e.g. an + * overloaded operator expression). + */ + val call: HasCallableArguments, /** * A set of candidate symbols we discovered based on the [CallExpression.callee] (using diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index b09a518bff3..882559b2df7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -35,6 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.Name import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.graph.unknownType import java.io.File @@ -318,8 +319,9 @@ abstract class Language> : Node() { // We need to check, whether this language has special handling of templates. In this // case, we need to check, whether a template matches directly after we have no direct // matches - if (this is HasTemplates) { - result.call.templateParameterEdges = mutableListOf() + val call = result.call + if (this is HasTemplates && call is CallExpression) { + call.templateParameterEdges = mutableListOf() val (ok, candidates) = this.handleTemplateFunctionCalls( null, @@ -333,7 +335,7 @@ abstract class Language> : Node() { return Pair(candidates.toSet(), CallResolutionResult.SuccessKind.SUCCESSFUL) } - result.call.templateParameterEdges = null + call.templateParameterEdges = null } // If the list of viable functions is still empty at this point, the call is unresolved diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index 1abbb43c699..bcb10790843 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.frontends import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.HasCallableArguments import de.fraunhofer.aisec.cpg.graph.HasOperatorCode import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration @@ -101,13 +102,13 @@ interface HasComplexCallResolution : LanguageTrait { * @return a list of [FunctionDeclaration] candidates. */ fun refineMethodCallResolution( - curClass: RecordDeclaration?, + symbol: Symbol, possibleContainingTypes: Set, - call: CallExpression, + call: HasCallableArguments, ctx: TranslationContext, currentTU: TranslationUnitDeclaration, callResolver: SymbolResolver - ): List + ): Set } /** A language trait that specifies if the language supports function pointers. */ 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 66bfe8b468e..8023356a226 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 @@ -143,3 +143,5 @@ interface HasCallableArguments : HasLanguage, HasNameAndLocation, HasScope { val signature: List get() = arguments.map(Expression::type) } + +interface HasOverloadedOperator : HasCallableArguments, HasLanguage, HasType, HasBase 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 c9f61503449..054369867bd 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 @@ -39,7 +39,12 @@ import org.apache.commons.lang3.builder.ToStringBuilder * Note: For assignments, i.e., using an `=` or `+=`, etc. the [AssignExpression] MUST be used. */ open class BinaryOperator : - Expression(), HasBase, HasOperatorCode, ArgumentHolder, HasType.TypeObserver { + Expression(), + HasOverloadedOperator, + HasBase, + HasOperatorCode, + ArgumentHolder, + HasType.TypeObserver { /** The left-hand expression. */ @AST var lhs: Expression = ProblemExpression("could not parse lhs") @@ -72,6 +77,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 54d2c9aab81..1783251b44d 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,11 +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.* 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 @@ -40,7 +37,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * use-case is access of a member function (method) as part of the [MemberCallExpression.callee] * property of a [MemberCallExpression]. */ -class MemberExpression : Reference(), ArgumentHolder, HasBase { +class MemberExpression : Reference(), ArgumentHolder, HasBase, HasOverloadedOperator { @AST override var base: Expression = ProblemExpression("could not parse base expression") set(value) { @@ -97,4 +94,11 @@ class MemberExpression : Reference(), ArgumentHolder, HasBase { 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 51483e91f9b..57aae11e911 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,16 +25,14 @@ */ 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.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 /** A unary operator expression, involving one expression and an operator, such as `a++`. */ -class UnaryOperator : Expression(), ArgumentHolder, HasType.TypeObserver { +class UnaryOperator : + Expression(), HasOperatorCode, ArgumentHolder, HasOverloadedOperator, HasType.TypeObserver { /** The expression on which the operation is applied. */ @AST var input: Expression = ProblemExpression("could not parse input") @@ -46,7 +44,7 @@ class UnaryOperator : Expression(), ArgumentHolder, HasType.TypeObserver { } /** The operator code. */ - var operatorCode: String? = null + override var operatorCode: String? = null set(value) { field = value changeExpressionAccess() @@ -58,6 +56,10 @@ class UnaryOperator : Expression(), ArgumentHolder, HasType.TypeObserver { /** Specifies, whether this a pre fix operation. */ var isPrefix = false + override val arguments = listOf() + + override val base = this + 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 1edce6cb034..eac07a5635d 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 @@ -33,6 +33,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.scopes.NameScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.StructureDeclarationScope +import de.fraunhofer.aisec.cpg.graph.scopes.Symbol import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker @@ -368,7 +369,39 @@ 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) + .bestViable + .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 @@ -378,8 +411,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() @@ -473,6 +506,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { is Reference -> handleReference(currClass, node) is ConstructExpression -> handleConstructExpression(node) is CallExpression -> handleCallExpression(scopeManager.currentRecord, node) + is HasOverloadedOperator -> handleOverloadedOperator(node) } } @@ -677,7 +711,8 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { val result = ctx.scopeManager.resolveCall(call) result.bestViable.toList() } else { - resolveCalleeByName(callee.name.localName, curClass, call) + // TODO: directly return the result + resolveCalleeByName(callee.name.localName, call).bestViable.toList() } } @@ -703,15 +738,14 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { scopeManager, ) } - return resolveCalleeByName(callee.name.localName, curClass, call) + // TODO: directly return the result + return resolveCalleeByName(callee.name.localName, call).bestViable.toList() } protected fun resolveCalleeByName( - localName: String, - curClass: RecordDeclaration?, - call: CallExpression - ): List { - + symbol: Symbol, + call: HasCallableArguments + ): CallResolutionResult { val (possibleContainingTypes, _) = getPossibleContainingTypes(call) // Find function targets. If our languages has a complex call resolution, we need to take @@ -720,27 +754,27 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { if (call.language is HasComplexCallResolution) { (call.language as HasComplexCallResolution) .refineMethodCallResolution( - curClass, + symbol, possibleContainingTypes, call, ctx, currentTU, this ) - .toMutableList() + .toMutableSet() } else { - scopeManager.resolveFunctionLegacy(call).toMutableList() + scopeManager.resolveFunctionLegacy(call).toMutableSet() } // Find invokes by supertypes if ( invocationCandidates.isEmpty() && - localName.isNotEmpty() && + symbol.isNotEmpty() && (!call.language.isCPP || shouldSearchForInvokesInParent(call)) ) { val records = possibleContainingTypes.mapNotNull { it.root.recordDeclaration }.toSet() invocationCandidates = - getInvocationCandidatesFromParents(localName, call, records).toMutableList() + getInvocationCandidatesFromParents(symbol, call, records).toMutableSet() } // Add overridden invokes @@ -751,7 +785,40 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { .toMutableList() ) - return invocationCandidates + // The following code is unfortunately largely a copy/paste from the new resolveCall + // function; but resolveCall is not yet completely ready to resolve methods, therefore, we + // need to have this duplicate code here, to at least use the new features here. + val signatureResults = + invocationCandidates + .map { + Pair( + it, + it.matchesSignature( + call.arguments.map(HasType::type), + call.language is HasDefaultArguments, + call.arguments + ) + ) + } + .filter { it.second is SignatureMatches } + .associate { it } + val viableFunctions = signatureResults.keys + val result = + CallResolutionResult( + call, + invocationCandidates, + viableFunctions, + signatureResults, + setOf(), + UNRESOLVED, + call.scope + ) + // TODO: dangerous + val pair = call.language!!.bestViableResolution(result) + result.bestViable = pair.first + result.success = pair.second + + return result } /** @@ -791,7 +858,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { * @param call * @return true if we should stop searching parent, false otherwise */ - protected fun shouldSearchForInvokesInParent(call: CallExpression): Boolean { + protected fun shouldSearchForInvokesInParent(call: HasCallableArguments): Boolean { return scopeManager.resolveFunctionStopScopeTraversalOnDefinition(call).isEmpty() } @@ -838,27 +905,49 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { } } + private fun handleOverloadedOperator(operator: HasOverloadedOperator) { + val language = operator.language + if (language !is HasOperatorOverloading || language.isPrimitive(operator.type)) { + return + } + + val symbol = language.operatorNames[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).bestViable.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 * guess. */ - protected fun getPossibleContainingTypes(call: CallExpression): Pair, Type?> { + 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) @@ -867,10 +956,10 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { fun getInvocationCandidatesFromRecord( recordDeclaration: RecordDeclaration?, name: String, - call: CallExpression - ): List { + call: HasCallableArguments + ): Set { if (recordDeclaration == null) { - return listOf() + return setOf() } // We should not directly access the "methods" property of the record declaration, @@ -886,42 +975,12 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { ?.filterIsInstance() ?.toSet() ?: setOf() - // The following code is unfortunately largely a copy/paste from the new resolveCall - // function; but resolveCall is not yet completely ready to resolve methods, therefore, we - // need to have this duplicate code here, to at least use the new features here. - val signatureResults = - candidateFunctions - .map { - Pair( - it, - it.matchesSignature( - call.signature, - call.language is HasDefaultArguments, - call - ) - ) - } - .filter { it.second is SignatureMatches } - .associate { it } - val viableFunctions = signatureResults.keys - val result = - CallResolutionResult( - call, - candidateFunctions, - viableFunctions, - signatureResults, - setOf(), - UNRESOLVED, - call.scope - ) - val pair = call.language?.bestViableResolution(result) - - return pair?.first?.toList() ?: listOf() + return candidateFunctions } protected fun getInvocationCandidatesFromParents( name: String, - call: CallExpression, + call: HasCallableArguments, possibleTypes: Set ): List { val workingPossibleTypes = mutableSetOf(*possibleTypes.toTypedArray()) @@ -982,7 +1041,7 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) { it.matchesSignature( signature, constructExpression.language is HasDefaultArguments, - constructExpression + constructExpression.arguments ) != IncompatibleSignature } 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 864499ec62c..0b856cf1cc5 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,15 +25,21 @@ */ 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.HasCallableArguments 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.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +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 org.neo4j.ogm.annotation.Transient @@ -158,21 +164,21 @@ open class CPPLanguage : * resolution techniques */ override fun refineMethodCallResolution( - curClass: RecordDeclaration?, + symbol: Symbol, possibleContainingTypes: Set, - call: CallExpression, + call: HasCallableArguments, ctx: TranslationContext, currentTU: TranslationUnitDeclaration, callResolver: SymbolResolver - ): List { - var invocationCandidates = mutableListOf() + ): Set { + var invocationCandidates = mutableSetOf() val records = possibleContainingTypes.mapNotNull { it.root.recordDeclaration }.toSet() for (record in records) { invocationCandidates.addAll( - callResolver.getInvocationCandidatesFromRecord(record, call.name.localName, call) + callResolver.getInvocationCandidatesFromRecord(record, symbol, call) ) } - if (invocationCandidates.isEmpty()) { + if (invocationCandidates.isEmpty() && call is CallExpression) { // This could be a regular function call that somehow ends up here because of weird // complexity of the old call resolver val result = ctx.scopeManager.resolveCall(call) @@ -184,7 +190,7 @@ open class CPPLanguage : // resolver completely. if (call is MemberCallExpression) { invocationCandidates = - invocationCandidates.filterIsInstance().toMutableList() + invocationCandidates.filterIsInstance().toMutableSet() } return invocationCandidates } @@ -200,6 +206,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 @@ -214,6 +231,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 6d3eb646af8..c7869ea7787 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 @@ -225,8 +225,12 @@ class DeclaratorHandler(lang: CXXLanguageFrontend) : /* * As always, there are some special cases to consider and one of those are C++ operators. * They are regarded as functions and eclipse CDT for some reason introduces a whitespace +<<<<<<< HEAD * in the function name, which will complicate things later on. But we only want to replace * the whitespace for "standard" operators. +======= + * in the function name, which will complicate things later on +>>>>>>> 6eee641203 (Added `OperatorDeclaration`) */ if (nameDecl.name is CPPASTOperatorName && name.replace(" ", "").isKnownOperatorName) { name = name.replace(" ", "") 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/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/CallResolverTest.kt index 520b59f9e85..abd331f2d9e 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 @@ -717,6 +717,7 @@ class CallResolverTest : BaseTest() { true ) { it.registerLanguage() + it.inferenceConfiguration(InferenceConfiguration.builder().enabled(false).build()) } val calls = result.calls @@ -727,8 +728,7 @@ class CallResolverTest : BaseTest() { } false } - assertEquals(1, calcCall.invokes.size) - assertFalse(calcCall.invokes[0].isInferred) + assertEquals(0, calcCall.invokes.size) } @Test 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