Skip to content

Commit

Permalink
Resolving unary operators
Browse files Browse the repository at this point in the history
binary operator also works now
  • Loading branch information
oxisto committed Jul 21, 2024
1 parent b345cab commit a0c3a4e
Show file tree
Hide file tree
Showing 18 changed files with 389 additions and 117 deletions.
28 changes: 16 additions & 12 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -664,15 +665,15 @@ class ScopeManager : ScopeProvider {
*/
@JvmOverloads
fun resolveFunctionLegacy(
call: CallExpression,
call: HasCallableArguments,
startScope: Scope? = currentScope
): List<FunctionDeclaration> {
val (scope, name) = extractScope(call, startScope)

val func =
resolve<FunctionDeclaration>(scope) {
it.name.lastPartsMatch(name) &&
it.matchesSignature(call.signature) != IncompatibleSignature
it.matchesSignature(call.arguments.map(HasType::type)) != IncompatibleSignature
}

return func
Expand All @@ -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,
Expand All @@ -696,7 +697,7 @@ class ScopeManager : ScopeProvider {
mapOf(),
setOf(),
CallResolutionResult.SuccessKind.UNRESOLVED,
startScope,
call.scope,
)
val language = call.language

Expand All @@ -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
Expand All @@ -736,7 +737,7 @@ class ScopeManager : ScopeProvider {
it.matchesSignature(
call.signature,
call.language is HasDefaultArguments,
call
call.arguments
)
)
}
Expand Down Expand Up @@ -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<Scope?, Name> {
fun extractScope(node: HasNameAndLocation, scope: Scope? = currentScope): Pair<Scope?, Name> {
return extractScope(node.name, node.location, scope)
}

Expand Down Expand Up @@ -911,7 +912,7 @@ class ScopeManager : ScopeProvider {
}

fun resolveFunctionStopScopeTraversalOnDefinition(
call: CallExpression
call: HasCallableArguments
): List<FunctionDeclaration> {
return resolve(currentScope, true) { f -> f.name.lastPartsMatch(call.name) }
}
Expand Down Expand Up @@ -1133,7 +1134,7 @@ data class SignatureMatches(override val casts: List<CastResult>) : SignatureRes
fun FunctionDeclaration.matchesSignature(
signature: List<Type>,
useDefaultArguments: Boolean = false,
call: CallExpression? = null,
argumentHints: List<Expression>? = null,
): SignatureResult {
val casts = mutableListOf<CastResult>()

Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -318,8 +319,9 @@ abstract class Language<T : LanguageFrontend<*, *>> : 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,
Expand All @@ -333,7 +335,7 @@ abstract class Language<T : LanguageFrontend<*, *>> : 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -101,13 +102,13 @@ interface HasComplexCallResolution : LanguageTrait {
* @return a list of [FunctionDeclaration] candidates.
*/
fun refineMethodCallResolution(
curClass: RecordDeclaration?,
symbol: Symbol,
possibleContainingTypes: Set<Type>,
call: CallExpression,
call: HasCallableArguments,
ctx: TranslationContext,
currentTU: TranslationUnitDeclaration,
callResolver: SymbolResolver
): List<FunctionDeclaration>
): Set<FunctionDeclaration>
}

/** A language trait that specifies if the language supports function pointers. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,5 @@ interface HasCallableArguments : HasLanguage, HasNameAndLocation, HasScope {
val signature: List<Type>
get() = arguments.map(Expression::type)
}

interface HasOverloadedOperator : HasCallableArguments, HasLanguage, HasType, HasBase
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ open class PropertyEdge<T : Node> : Persistable {
@Transient
class PropertyEdgeDelegate<T : Node, S : Node>(
val edge: KProperty1<S, List<PropertyEdge<T>>>,
val outgoing: Boolean = true
val outgoing: Boolean = true,
) {
operator fun getValue(thisRef: S, property: KProperty<*>): List<T> {
return PropertyEdge.unwrap(edge.get(thisRef), outgoing)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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<Expression>
get() = listOf()
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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()
Expand All @@ -58,6 +56,10 @@ class UnaryOperator : Expression(), ArgumentHolder, HasType.TypeObserver {
/** Specifies, whether this a pre fix operation. */
var isPrefix = false

override val arguments = listOf<Expression>()

override val base = this

private fun changeExpressionAccess() {
var access = AccessValues.READ
if (operatorCode == "++" || operatorCode == "--") {
Expand Down
Loading

0 comments on commit a0c3a4e

Please sign in to comment.