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 16c584e commit 494a5a9
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,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 @@ -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 @@ -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
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
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 @@ -534,28 +566,32 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
callee.refersTo = call.invokes.firstOrNull()
}

protected fun resolveCalleeByName(localName: String, call: CallExpression): Set<Declaration> {
val (possibleContainingTypes, _) = getPossibleContainingTypes(call)
protected fun resolveCalleeByName(
localName: String,
callee: Expression,
specificBase: Expression? = null
): Set<Declaration> {
val (possibleContainingTypes, _) = getPossibleContainingTypes(callee)

var invocationCandidates = mutableSetOf<Declaration>()
val records = possibleContainingTypes.mapNotNull { it.root.recordDeclaration }.toSet()
for (record in records) {
invocationCandidates.addAll(
ctx.scopeManager.findSymbols(record.name.fqn(call.name.localName))
ctx.scopeManager.findSymbols(record.name.fqn(callee.name.localName))
)
}
if (invocationCandidates.isEmpty() && call is CallExpression) {
/*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)
invocationCandidates.addAll(result.bestViable)
}
}*/

// Find invokes by supertypes
if (invocationCandidates.isEmpty() && localName.isNotEmpty()) {
val records = possibleContainingTypes.mapNotNull { it.root.recordDeclaration }.toSet()
invocationCandidates =
getInvocationCandidatesFromParents(localName, call, records).toMutableSet()
getInvocationCandidatesFromParents(localName, callee, records).toMutableSet()
}

// Add overridden invokes
Expand Down Expand Up @@ -642,35 +678,66 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
}
}

private fun handleOverloadedOperator(operator: HasOverloadedOperation) {
val language = operator.language
val base = operator.operatorBase
if (language !is HasOperatorOverloading || language.isPrimitive(base.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 as Expression, base as Expression)
.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
* guess.
*/
protected fun getPossibleContainingTypes(call: CallExpression): Pair<Set<Type>, Type?> {
protected fun getPossibleContainingTypes(
callee: Expression,
specificBase: Expression? = null
): 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 (specificBase != null) {
bestGuess = specificBase.type
possibleTypes.add(specificBase.type)
possibleTypes.addAll(specificBase.assignedTypes)
} else if (callee is Reference && callee !is MemberExpression) {
// 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 (callee is HasBase) {
callee.base?.let { base ->
bestGuess = base.type
possibleTypes.add(base.type)
possibleTypes.addAll(base.assignedTypes)
}
}

return Pair(possibleTypes, bestGuess)
}

protected fun getInvocationCandidatesFromParents(
name: Symbol,
call: CallExpression,
callee: Expression,
possibleTypes: Set<RecordDeclaration>
): List<Declaration> {
val workingPossibleTypes = mutableSetOf(*possibleTypes.toTypedArray())
Expand All @@ -685,15 +752,15 @@ open class SymbolResolver(ctx: TranslationContext) : ComponentPass(ctx) {
// to stop the search in the parent even if the FunctionDeclaration does not match with
// the signature of the CallExpression
// TODO: move this to refineMethodResolution of CXXLanguage
if (call.language.isCPP) { // TODO: Needs a special trait?
if (callee.language.isCPP) { // TODO: Needs a special trait?
workingPossibleTypes.removeIf { recordDeclaration ->
!shouldContinueSearchInParent(recordDeclaration, name)
}
}
firstLevelCandidates.ifEmpty {
workingPossibleTypes
.map { it.superTypeDeclarations }
.map { getInvocationCandidatesFromParents(name, call, it) }
.map { getInvocationCandidatesFromParents(name, callee, it) }
.flatten()
}
}
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
}
}
Loading

0 comments on commit 494a5a9

Please sign in to comment.