Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added basic implementation of operator overloading #1606

Merged
merged 8 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType
import de.fraunhofer.aisec.cpg.graph.types.IncompleteType
import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.helpers.Util
import de.fraunhofer.aisec.cpg.passes.SymbolResolver
import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation
import java.util.*
import java.util.function.Predicate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ 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 de.fraunhofer.aisec.cpg.passes.SymbolResolver
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import de.fraunhofer.aisec.cpg.ScopeManager
import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.graph.HasOperatorCode
import de.fraunhofer.aisec.cpg.graph.HasOverloadedOperation
import de.fraunhofer.aisec.cpg.graph.LanguageProvider
import de.fraunhofer.aisec.cpg.graph.Name
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
Expand Down Expand Up @@ -235,6 +237,21 @@ interface HasOperatorOverloading : LanguageTrait {
* the name of the function.
*/
val overloadedOperatorNames: Map<Pair<KClass<out HasOverloadedOperation>, String>, Symbol>

/**
* Returns the matching operator code for [name] in [overloadedOperatorNames]. While
* [overloadedOperatorNames] can have multiple entries for a single operator code (e.g. to
* differentiate between unary and binary ops), we only ever allow one distinct operator code
* for a specific symbol. If non such distinct operator code is found, null is returned.
*/
fun operatorCodeFor(name: Symbol): String? {
return overloadedOperatorNames
.filterValues { it == name }
.keys
.map { it.second }
.distinct()
.singleOrNull()
}
}

/**
Expand All @@ -246,3 +263,23 @@ inline infix fun <reified T : HasOverloadedOperation> KClass<T>.of(
): Pair<KClass<T>, String> {
return Pair(T::class, operatorCode)
}

/** Checks whether the name for a function (as [CharSequence]) is a known operator name. */
maximiliankaul marked this conversation as resolved.
Show resolved Hide resolved
context(LanguageProvider)
val CharSequence.isKnownOperatorName: Boolean
get() {
val language = language
if (language !is HasOperatorOverloading) {
return false
}

// If this is a parsed name, we only are interested in the local name
konradweiss marked this conversation as resolved.
Show resolved Hide resolved
val name =
if (this is Name) {
this.localName
} else {
this
}

return language.overloadedOperatorNames.containsValue(name)
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ interface ArgumentHolder : Holder<Expression> {
return false
}

override fun replace(old: Expression, new: Expression): Boolean {
maximiliankaul marked this conversation as resolved.
Show resolved Hide resolved
return replaceArgument(old, new)
}

/**
* Replaces the existing argument specified in [old] with the one in [new]. Implementation how
* to do that might be specific to the argument holder.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ fun MetadataProvider.newOperatorDeclaration(
operatorCode: String,
recordDeclaration: RecordDeclaration? = null,
rawNode: Any? = null
): MethodDeclaration {
): OperatorDeclaration {
val node = OperatorDeclaration()
node.applyMetadata(this, name, rawNode, defaultNamespace = recordDeclaration?.name)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,30 @@ 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
if (callee != null) {
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 @@ -504,6 +504,10 @@ val Node?.nodes: List<Node>
val Node?.calls: List<CallExpression>
get() = this.allChildren()

/** Returns all [OperatorCallExpression] children in this graph, starting with this [Node]. */
val Node?.operatorCalls: List<OperatorCallExpression>
get() = this.allChildren()

/** Returns all [MemberCallExpression] children in this graph, starting with this [Node]. */
val Node?.mcalls: List<MemberCallExpression>
get() = this.allChildren()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
* [CallExpression]).
*/
interface Holder<NodeTypeToHold : Node> {

/**
* Replaces the existing node specified in [old] with the one in [new]. Implementation how to do
* that might be specific to the holder.
*
* An indication whether this operation was successful needs to be returned.
*/
fun replace(old: NodeTypeToHold, new: NodeTypeToHold): Boolean

/** Adds a [Node] to the list of "held" nodes. */
operator fun plusAssign(node: NodeTypeToHold)
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,5 @@ interface HasOverloadedOperation : HasOperatorCode {
* The base expression this operator works on. The [Type] of this is also the source where the
* [SymbolResolver] is looking for an overloaded [OperatorDeclaration].
*/
val operatorBase: HasType
val operatorBase: Expression
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ interface StatementHolder : Holder<Statement> {
*/
var statements: MutableList<Statement>

override fun replace(old: Statement, new: Statement): Boolean {
oxisto marked this conversation as resolved.
Show resolved Hide resolved
return statementEdges.replace(old, new)
}

override operator fun plusAssign(node: Statement) {
statementEdges += node
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ abstract class EdgeList<NodeType : Node, EdgeType : Edge<NodeType>>(
return ok
}

/** Replaces the first occurrence of an edge with [old] with a new edge to [new]. */
fun replace(old: NodeType, new: NodeType): Boolean {
oxisto marked this conversation as resolved.
Show resolved Hide resolved
val idx = this.indexOfFirst { it.end == old }
if (idx != -1) {
this[idx] = init(thisRef, new)
return true
}

return false
}

override fun clear() {
// Make a copy of our edges so we can pass a copy to our on-remove handler
val edges = this.toList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ open class BinaryOperator :
get() = listOf(rhs)

/** The binary operator operators on the [lhs]. [rhs] is part of the [operatorArguments]. */
override val operatorBase: HasType
override val operatorBase: Expression
get() = lhs

override fun equals(other: Any?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ open class CallExpression :
* is overloaded. In this case we want the [operatorBase] to point to [callee], so we can take
* its type to lookup the necessary [OperatorDeclaration].
*/
override val operatorBase: HasType
override val operatorBase: Expression
get() = callee

override fun equals(other: Any?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class MemberExpression : Reference(), HasOverloadedOperation, ArgumentHolder, Ha
override val operatorArguments: List<Expression>
get() = listOf()

override val operatorBase: HasType
override val operatorBase: Expression
get() = base

override fun toString(): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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.*
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(_) {
// read-only
}

/**
* 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
}
}

/**
* Creates a new [OperatorCallExpression] to a [OperatorDeclaration] and also sets the appropriate
* fields such as [CallExpression.invokes] and [Reference.refersTo].
*/
fun operatorCallFromDeclaration(
oxisto marked this conversation as resolved.
Show resolved Hide resolved
decl: OperatorDeclaration,
op: HasOverloadedOperation
): OperatorCallExpression {
return with(decl) {
val ref =
newMemberExpression(decl.name, op.operatorBase, operatorCode = ".")
.implicit(decl.name.localName, location = op.location)
ref.refersTo = decl
val call =
newOperatorCallExpression(operatorCode = op.operatorCode ?: "", ref)
.codeAndLocationFrom(ref)
call.invokes = mutableListOf(decl)
call
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class UnaryOperator : Expression(), HasOverloadedOperation, ArgumentHolder, HasT
get() = listOf()

/** The unary operator operates on [input]. */
override val operatorBase = input
override val operatorBase
get() = input

/** The operator code. */
override var operatorCode: String? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ package de.fraunhofer.aisec.cpg.helpers

import de.fraunhofer.aisec.cpg.ScopeManager
import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend
import de.fraunhofer.aisec.cpg.graph.ArgumentHolder
import de.fraunhofer.aisec.cpg.graph.ContextProvider
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.StatementHolder
import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdge
import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeCollection
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
import de.fraunhofer.aisec.cpg.passes.Pass
import de.fraunhofer.aisec.cpg.processing.strategy.Strategy
import java.lang.annotation.AnnotationFormatError
import java.lang.reflect.Field
Expand Down Expand Up @@ -340,3 +345,55 @@ object SubgraphWalker {
}
}
}

/**
* Tries to replace the [old] expression with a [new] one, given the [parent].
*
* There are two things to consider:
* - First, this only works if [parent] is either an [ArgumentHolder] or [StatementHolder].
* Otherwise, we cannot instruct the parent to exchange the node
* - Second, since exchanging the node has influence on their edges (such as EOG, DFG, etc.), we
* only support a replacement very early in the pass system. To be specific, we only allow
* replacement before any DFG edges are set. We are re-wiring EOG edges, but nothing else. If one
* tries to replace a node with existing [Node.nextDFG] or [Node.prevDFG], we fail.
*/
context(ContextProvider)
fun SubgraphWalker.ScopedWalker.replace(parent: Node?, old: Expression, new: Expression): Boolean {
// We do not allow to replace nodes where the DFG (or other dependent nodes, such as PDG have
// been set). The reason for that is that these edges contain a lot of information on the edges
// themselves and replacing this edge would be very complicated.
if (old.prevDFG.isNotEmpty() || old.nextDFG.isNotEmpty()) {
return false
}

val success =
when (parent) {
is ArgumentHolder -> parent.replace(old, new)
is StatementHolder -> parent.replace(old, new)
else -> {
Pass.log.error(
"Parent AST node is not an argument or statement holder. Cannot replace node. Further analysis might not be entirely accurate."
)
return false
}
}
if (!success) {
Pass.log.error(
"Replacing expression $old was not successful. Further analysis might not be entirely accurate."
)
} else {
// Store any eventual EOG/DFG nodes and disconnect old node
val oldPrevEOG = old.prevEOG.toMutableList()
oxisto marked this conversation as resolved.
Show resolved Hide resolved
val oldNextEOG = old.nextEOG.toMutableList()
old.disconnectFromGraph()

// Put the stored EOG nodes to the new node
new.prevEOG = oldPrevEOG
new.nextEOG = oldNextEOG

// Make sure to inform the walker about our change
this.registerReplacement(old, new)
}

return success
}
Loading
Loading