de.fraunhofer.aisec.cpg.helpers.Util.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) 2019, 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.helpers
import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration
import de.fraunhofer.aisec.cpg.graph.edge.Properties
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation
import java.util.*
import org.slf4j.Logger
object Util {
/**
* Filters the nodes in the AST subtree at root `node` for Nodes with the specified code.
*
* @param node root of the subtree that is searched.
* @param searchCode exact code that a node needs to have.
* @return a list of nodes with the specified String.
*/
fun subnodesOfCode(node: Node?, searchCode: String): List {
return SubgraphWalker.flattenAST(node).filter { n: Node ->
n.code != null && n.code == searchCode
}
}
/**
* Checks if the Node `n` connects to the nodes in `refs` over the CPGS EOG graph edges that
* depict the evaluation order. The parameter q defines if all edges of interest to node must
* connect to an edge in refs or one is enough, cn and cr define whether the passed AST nodes
* themselves are used to search the connections or the EOG Border nodes in the AST subnode.
* Finally, en defines whether the EOG edges go * from n to r in refs or the inverse.
*
* @param q
* - The quantifier, all or any node of n must connect to refs, defaults to ALL.
*
* @param cn
* - NODE if n itself is the node to connect or SUBTREE if the EOG borders are of interest.
* Defaults to SUBTREE
*
* @param en
* - The Edge direction and therefore the borders of n to connect to refs
*
* @param n
* - Node of interest
*
* @param cr
* - NODE if refs nodes itself are the nodes to connect or SUBTREE if the EOG borders are of
* interest
*
* @param props
* - All edges must have these properties set to the provided value
*
* @param refs
* - Multiple reference nodes that can be passed as varargs
*
* @return true if all/any of the connections from node connect to n.
*/
fun eogConnect(
q: Quantifier = Quantifier.ALL,
cn: Connect = Connect.SUBTREE,
en: Edge,
n: Node?,
cr: Connect = Connect.SUBTREE,
props: Map = mutableMapOf(),
refs: List
): Boolean {
if (n == null) {
return false
}
var nodeSide = listOf(n)
val er = if (en == Edge.ENTRIES) Edge.EXITS else Edge.ENTRIES
var refSide = refs
nodeSide =
if (cn == Connect.SUBTREE) {
val border = SubgraphWalker.getEOGPathEdges(n)
if (en == Edge.ENTRIES) {
val pe = border.entries.flatMap { it.prevEOGEdges }
if (Quantifier.ALL == q && pe.any { !it.containsProperties(props) })
return false
pe.filter { it.containsProperties(props) }.map { it.start }
} else border.exits
} else {
nodeSide.flatMap {
if (en == Edge.ENTRIES) {
val pe = it.prevEOGEdges
if (Quantifier.ALL == q && pe.any { !it.containsProperties(props) })
return false
pe.filter { it.containsProperties(props) }.map { it.start }
} else listOf(it)
}
}
refSide =
if (cr == Connect.SUBTREE) {
val borders = refs.map { SubgraphWalker.getEOGPathEdges(it) }
borders.flatMap { border ->
if (Edge.ENTRIES == er) {
val pe = border.entries.flatMap { it.prevEOGEdges }
if (Quantifier.ALL == q && pe.any { !it.containsProperties(props) })
return false
pe.filter { it.containsProperties(props) }.map { it.start }
} else border.exits
}
} else {
refSide.flatMap { node ->
if (er == Edge.ENTRIES) {
val pe = node?.prevEOGEdges ?: listOf()
if (Quantifier.ALL == q && pe.any { !it.containsProperties(props) })
return false
pe.filter { it.containsProperties(props) }.map { it.start }
} else listOf(node)
}
}
val refNodes = refSide
return if (Quantifier.ANY == q) nodeSide.any { it in refNodes }
else refNodes.containsAll(nodeSide)
}
/**
* Logs a warning with the specified file location. This is intentionally inlined, so that the
* [Logger] will use the location of the callee of this function, rather than the [Util] class.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun warnWithFileLocation(
lang: LanguageFrontend,
astNode: AstNode,
log: Logger,
format: String?,
vararg arguments: Any?
) {
log.warn(
String.format(
"%s: %s",
PhysicalLocation.locationLink(lang.locationOf(astNode)),
format
),
*arguments
)
}
/**
* Logs an error with the specified file location. This is intentionally inlined, so that the
* [Logger] will use the location of the callee of this function, rather than the [Util] class.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun errorWithFileLocation(
lang: LanguageFrontend,
astNode: AstNode,
log: Logger,
format: String?,
vararg arguments: Any?
) {
log.error(
String.format(
"%s: %s",
PhysicalLocation.locationLink(lang.locationOf(astNode)),
format
),
*arguments
)
}
/**
* Logs a warning with the specified file location. This is intentionally inlined, so that the
* [Logger] will use the location of the callee of this function, rather than the [Util] class.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun warnWithFileLocation(
node: Node,
log: Logger,
format: String?,
vararg arguments: Any?
) {
log.warn(
String.format("%s: %s", PhysicalLocation.locationLink(node.location), format),
*arguments
)
}
/**
* Logs an error with the specified file location. This is intentionally inlined, so that the
* [Logger] will use the location of the callee of this function, rather than the [Util] class.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun errorWithFileLocation(
node: Node?,
log: Logger,
format: String?,
vararg arguments: Any?
) {
log.error(
String.format("%s: %s", PhysicalLocation.locationLink(node?.location), format),
*arguments
)
}
/**
* Logs a debug message with the specified file location. This is intentionally inlined, so that
* the [Logger] will use the location of the callee of this function, rather than the [Util]
* class.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun debugWithFileLocation(
node: Node?,
log: Logger,
format: String?,
vararg arguments: Any?
) {
log.debug(
String.format("%s: %s", PhysicalLocation.locationLink(node?.location), format),
*arguments
)
}
/**
* Split a String into multiple parts by using one or more delimiter characters. Any delimiters
* that are surrounded by matching opening and closing brackets are skipped. E.g. "a,(b,c)" will
* result in a list containing "a" and "(b,c)" when splitting on commas. Empty parts are
* ignored, so when splitting "a,,,,(b,c)", the same result is returned as in the previous
* example.
*
* @param toSplit The input String
* @param delimiters A String containing all characters that should be treated as delimiters
* @return A list of all parts of the input, as divided by any delimiter
*/
fun splitLeavingParenthesisContents(toSplit: String, delimiters: String): List {
val result = mutableListOf()
var openParentheses = 0
var currPart = StringBuilder()
for (c in toSplit.toCharArray()) {
when {
c == '(' -> {
openParentheses++
currPart.append(c)
}
c == ')' -> {
if (openParentheses > 0) {
openParentheses--
}
currPart.append(c)
}
delimiters.contains("" + c) -> {
if (openParentheses == 0) {
val toAdd = currPart.toString().trim()
if (toAdd.isNotEmpty()) {
result.add(currPart.toString().trim())
}
currPart = StringBuilder()
} else {
currPart.append(c)
}
}
}
}
if (currPart.isNotEmpty()) {
result.add(currPart.toString().trim())
}
return result
}
/**
* Removes pairs of parentheses that do not provide any further separation. E.g. "(foo)" results
* in "foo" and "(((foo))((bar)))" in "(foo)(bar)", whereas "(foo)(bar)" stays the same.
*
* @param original The String to clean
* @return The modified version without excess parentheses
*/
@JvmStatic
fun removeRedundantParentheses(original: String): String {
val result = original.toCharArray()
val marker = '\uffff'
val openingParentheses: Deque = ArrayDeque()
for (i in original.indices) {
when (original[i]) {
'(' -> openingParentheses.push(i)
')' -> {
val matching = openingParentheses.pollFirst()
if (matching == 0 && i == original.length - 1) {
result[i] = marker
result[matching] = result[i]
} else if (
matching > 0 && result[matching - 1] == '(' && result[i + 1] == ')'
) {
result[i] = marker
result[matching] = result[i]
}
}
}
}
return String(result).replace("" + marker, "")
}
fun containsOnOuterLevel(input: String, marker: Char): Boolean {
var openParentheses = 0
var openTemplate = 0
for (c in input.toCharArray()) {
when (c) {
'(' -> {
openParentheses++
}
')' -> {
openParentheses--
}
'<' -> {
openTemplate++
}
'>' -> {
openTemplate--
}
marker -> {
if (openParentheses == 0 && openTemplate == 0) {
return true
}
}
}
}
return false
}
/**
* Establish data-flow from a [CallExpression] arguments to the target [FunctionDeclaration]
* parameters. Additionally, if the call is a [MemberCallExpression], it establishes a data-flow
* from the [MemberCallExpression.base] towards the [MethodDeclaration.receiver].
*
* @param target The call's target [FunctionDeclaration]
* @param call The [CallExpression]
*/
fun attachCallParameters(target: FunctionDeclaration, call: CallExpression) {
// Add an incoming DFG edge from a member call's base to the method's receiver
if (target is MethodDeclaration && call is MemberCallExpression && !call.isStatic) {
target.receiver?.let { receiver -> call.base?.addNextDFG(receiver) }
}
// Connect the arguments to parameters
val arguments = call.arguments
target.parameterEdges.sortWith(Comparator.comparing { it.end.argumentIndex })
var j = 0
while (j < arguments.size) {
val parameters = target.parameters
if (j < parameters.size) {
val param = parameters[j]
if (param.isVariadic) {
while (j < arguments.size) {
// map all the following arguments to this variadic param
param.addPrevDFG(arguments[j])
j++
}
break
} else {
param.addPrevDFG(arguments[j])
}
}
j++
}
}
/**
* Inverse operation of [attachCallParameters]
*
* @param target
* @param arguments
*/
fun detachCallParameters(target: FunctionDeclaration, arguments: List) {
for (param in target.parameters) {
// A param could be variadic, so multiple arguments could be set as incoming DFG
param.prevDFG.filter { o: Node? -> o in arguments }.forEach { param.removeNextDFG(it) }
}
}
/**
* This function returns the set of adjacent DFG nodes that is contained in the nodes subgraph.
*
* @param n Node of interest
* @param incoming whether the node connected by an incoming or, if false, outgoing DFG edge
* @return
*/
fun getAdjacentDFGNodes(n: Node?, incoming: Boolean): MutableList {
val subnodes = SubgraphWalker.getAstChildren(n)
val adjacentNodes =
if (incoming) {
subnodes.filter { it.nextDFG.contains(n) }.toMutableList()
} else {
subnodes.filter { it.prevDFG.contains(n) }.toMutableList()
}
return adjacentNodes
}
/**
* Connects the node `n` with the node `branchingExp` if present or with the node
* `branchingDecl`. The assumption is that only `branchingExp` or `branchingDecl` are present,
* e.g. C++.
*
* @param n
* @param branchingExp
* @param branchingDeclaration
*/
fun addDFGEdgesForMutuallyExclusiveBranchingExpression(
n: Node,
branchingExp: Node?,
branchingDeclaration: Node?
) {
var conditionNodes = mutableListOf()
if (branchingExp != null) {
conditionNodes = mutableListOf()
conditionNodes.add(branchingExp)
} else if (branchingDeclaration != null) {
conditionNodes = getAdjacentDFGNodes(branchingDeclaration, true)
}
conditionNodes.forEach { n.addPrevDFG(it) }
}
enum class Connect {
NODE,
SUBTREE
}
enum class Quantifier {
ANY,
ALL
}
enum class Edge {
ENTRIES,
EXITS
}
}