de.fraunhofer.aisec.cpg.passes.DynamicInvokeResolver.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cpg-core Show documentation
Show all versions of cpg-core Show documentation
A simple library to extract a code property graph out of source code. It has support for multiple passes that can extend the analysis after the graph is constructed.
/*
* Copyright (c) 2022, 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.passes
import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.graph.AccessValues
import de.fraunhofer.aisec.cpg.graph.Component
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.ParameterDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.edge.FullDataflowGranularity
import de.fraunhofer.aisec.cpg.graph.edge.Properties
import de.fraunhofer.aisec.cpg.graph.pointer
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.types.FunctionPointerType
import de.fraunhofer.aisec.cpg.graph.types.FunctionType
import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.ScopedWalker
import de.fraunhofer.aisec.cpg.helpers.identitySetOf
import de.fraunhofer.aisec.cpg.passes.order.DependsOn
import java.util.*
import java.util.function.Consumer
/**
* This [Pass] is responsible for resolving dynamic function invokes, i.e., [CallExpression] nodes
* that contain a reference/pointer to a function and are being "called". A common example includes
* C/C++ function pointers.
*
* This pass is intentionally split from the [SymbolResolver] because it depends on DFG edges. This
* split allows the [SymbolResolver] to be run before any DFG passes, which in turn allow us to also
* populate DFG passes for inferred functions.
*/
@DependsOn(SymbolResolver::class)
@DependsOn(DFGPass::class)
class DynamicInvokeResolver(ctx: TranslationContext) : ComponentPass(ctx) {
private lateinit var walker: ScopedWalker
private var inferDfgForUnresolvedCalls = false
override fun accept(component: Component) {
inferDfgForUnresolvedCalls = config.inferenceConfiguration.inferDfgForUnresolvedSymbols
walker = ScopedWalker(scopeManager)
walker.registerHandler { node, _ -> handle(node) }
for (tu in component.translationUnits) {
walker.iterate(tu)
}
}
private fun handle(node: Node?) {
when (node) {
is MemberCallExpression -> handleMemberCallExpression(node)
is CallExpression -> handleCallExpression(node)
}
}
/**
* Resolves function pointers in a [CallExpression] node. As long as the [CallExpression.callee]
* has a [FunctionPointerType], we should be able to resolve it.
*/
private fun handleCallExpression(call: CallExpression) {
val callee = call.callee
if (
callee?.type is FunctionPointerType ||
((callee as? Reference)?.refersTo is ParameterDeclaration ||
(callee as? Reference)?.refersTo is VariableDeclaration)
) {
handleCallee(call, callee)
}
}
/**
* Resolves function pointers in a [MemberCallExpression]. In this case the
* [MemberCallExpression.callee] field is a binary operator on which [BinaryOperator.rhs] needs
* to have a [FunctionPointerType].
*/
private fun handleMemberCallExpression(call: MemberCallExpression) {
val callee = call.callee
if (callee is BinaryOperator && callee.rhs.type is FunctionPointerType) {
handleCallee(call, callee.rhs)
}
}
private fun handleCallee(call: CallExpression, expr: Expression) {
// For now, we harmonize all types to the FunctionPointerType. In the future, we want to get
// rid of FunctionPointerType and only deal with FunctionTypes.
val pointerType: FunctionPointerType =
when (val type = expr.type) {
is FunctionType -> type.pointer() as FunctionPointerType
is FunctionPointerType -> type
else -> {
// some languages allow other types to derive from a function type, in this case
// we need to look for a super type
val superType = type.superTypes.singleOrNull()
if (superType is FunctionType) {
superType.pointer() as FunctionPointerType
} else {
return
}
}
}
val invocationCandidates = mutableListOf()
val work: Deque = ArrayDeque()
val seen = identitySetOf()
work.push(expr)
while (work.isNotEmpty()) {
val curr = work.pop()
if (!seen.add(curr)) {
continue
}
val isLambda = curr is VariableDeclaration && curr.initializer is LambdaExpression
val currentFunction =
if (isLambda) {
((curr as VariableDeclaration).initializer as LambdaExpression).function
} else {
curr
}
if (currentFunction is FunctionDeclaration) {
// Even if it is a function declaration, the dataflow might just come from a
// situation where the target of a fptr is passed through via a return value. Keep
// searching if return type or signature don't match
val functionPointerType = currentFunction.type.pointer()
if (
isLambda &&
currentFunction.returnTypes.isEmpty() &&
currentFunction.hasSignature(pointerType.parameters)
) {
invocationCandidates.add(currentFunction)
continue
} else if (functionPointerType == pointerType) {
invocationCandidates.add(currentFunction)
// We have found a target. Don't follow this path any further, but still
// continue the other paths that might be left, as we could have several
// potential targets at runtime
continue
}
}
// Do not consider the base for member expressions, we have to know possible values of
// the member (e.g. field).
val prevDFGToPush =
curr.prevDFGEdges
.filter { it.granularity is FullDataflowGranularity }
.map { it.start }
.toMutableList()
if (curr is MemberExpression && prevDFGToPush.isEmpty()) {
// TODO: This is only a workaround!
// If there is nothing found for MemberExpressions, we may have set the field
// somewhere else but do not yet propagate this to this location (e.g. because it
// happens in another function). In this case, we look at write-usages to the
// field and use all of those. This is only a temporary workaround until someone
// implements an interprocedural analysis (for example).
(curr.refersTo as? FieldDeclaration)
?.usages
?.filter {
it.access == AccessValues.WRITE || it.access == AccessValues.READWRITE
}
?.let { prevDFGToPush.addAll(it) }
// Also add the initializer of the field (if it exists)
(curr.refersTo as? FieldDeclaration)?.initializer?.let { prevDFGToPush.add(it) }
}
prevDFGToPush.forEach(Consumer(work::push))
}
call.invokes = invocationCandidates
call.invokeEdges.forEach { it.addProperty(Properties.DYNAMIC_INVOKE, true) }
// We have to update the dfg edges because this call could now be resolved (which was not
// the case before).
DFGPass(ctx).handleCallExpression(call, inferDfgForUnresolvedCalls)
}
override fun cleanup() {
// Nothing to do
}
}