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 22, 2024
1 parent cdd2f29 commit f055ceb
Show file tree
Hide file tree
Showing 15 changed files with 285 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,

/**
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 @@ -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
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 @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<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,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
Expand Down Expand Up @@ -63,6 +59,8 @@ class UnaryOperator : Expression(), HasOverloadedOperation, ArgumentHolder, HasT
/** Specifies, whether this a pre fix operation. */
var isPrefix = false

override val arguments = listOf<Expression>()

private fun changeExpressionAccess() {
var access = AccessValues.READ
if (operatorCode == "++" || operatorCode == "--") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<OperatorDeclaration>()
.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
Expand All @@ -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()
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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<OperatorDeclaration>()
// TODO: replace with call
val ops = resolveCalleeByName(symbol, operator).filterIsInstance<OperatorDeclaration>()
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
Expand All @@ -653,19 +706,19 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
protected fun getPossibleContainingTypes(call: HasCallableArguments): Pair<Set<Type>, Type?> {
val possibleTypes = mutableSetOf<Type>()
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,23 @@
*/
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
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
Expand Down Expand Up @@ -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
Expand All @@ -185,6 +200,27 @@ open class CPPLanguage :
return CastNotPossible
}

override fun bestViableResolution(
result: CallResolutionResult
): Pair<Set<FunctionDeclaration>, 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 = '>'

Expand Down
Loading

0 comments on commit f055ceb

Please sign in to comment.