![JAR search and dependency download from the Maven repository](/logo.png)
de.fraunhofer.aisec.cpg.helpers.Util.kt Maven / Gradle / Ivy
/*
* 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.Language
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.ParamVariableDeclaration
import de.fraunhofer.aisec.cpg.graph.edge.Properties
import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.nio.charset.StandardCharsets
import java.util.*
import java.util.function.Consumer
import java.util.function.Function
import java.util.function.Predicate
import java.util.stream.Collectors
import java.util.stream.IntStream
import java.util.stream.Stream
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)
.stream()
.filter { n: Node -> n.code != null && n.code == searchCode }
.collect(Collectors.toList())
}
/**
* 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 { r: Node -> r.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 { n -> SubgraphWalker.getEOGPathEdges(n) }
borders.flatMap { border: SubgraphWalker.Border ->
if (Edge.ENTRIES == er) {
val pe = border.entries.flatMap { r: Node -> r.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 java.util.List.of(node)
}
}
val refNodes = refSide
return if (Quantifier.ANY == q) nodeSide.any { o -> refNodes.contains(o) }
else refNodes.containsAll(nodeSide)
}
@Throws(IOException::class)
fun inputStreamToString(inputStream: InputStream): String {
ByteArrayOutputStream().use { result ->
val buffer = ByteArray(1024)
var length: Int
while (inputStream.read(buffer).also { length = it } != -1) {
result.write(buffer, 0, length)
}
return result.toString(StandardCharsets.UTF_8)
}
}
@JvmStatic
fun distinctBy(by: Function): Predicate {
val seen: MutableSet = HashSet()
return Predicate { t: T -> seen.add(by.apply(t)) }
}
fun getExtension(file: File): String {
val pos = file.name.lastIndexOf('.')
return if (pos > 0) {
file.name.substring(pos).lowercase(Locale.getDefault())
} else {
""
}
}
@JvmStatic
fun warnWithFileLocation(
lang: LanguageFrontend,
astNode: S,
log: Logger,
format: String?,
vararg arguments: Any?
) {
log.warn(
String.format(
"%s: %s",
PhysicalLocation.locationLink(lang.getLocationFromRawNode(astNode)),
format
),
*arguments
)
}
@JvmStatic
fun errorWithFileLocation(
lang: LanguageFrontend,
astNode: S,
log: Logger,
format: String?,
vararg arguments: Any?
) {
log.error(
String.format(
"%s: %s",
PhysicalLocation.locationLink(lang.getLocationFromRawNode(astNode)),
format
),
*arguments
)
}
@JvmStatic
fun warnWithFileLocation(node: Node, log: Logger, format: String?, vararg arguments: Any?) {
log.warn(
String.format("%s: %s", PhysicalLocation.locationLink(node.location), format),
*arguments
)
}
@JvmStatic
fun errorWithFileLocation(node: Node, log: Logger, format: String?, vararg arguments: Any?) {
log.error(
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: MutableList = ArrayList()
var openParentheses = 0
var currPart = StringBuilder()
for (c in toSplit.toCharArray()) {
if (c == '(') {
openParentheses++
currPart.append(c)
} else if (c == ')') {
if (openParentheses > 0) {
openParentheses--
}
currPart.append(c)
} else if (delimiters.contains("" + c)) {
if (openParentheses == 0) {
val toAdd = currPart.toString().strip()
if (!toAdd.isEmpty()) {
result.add(currPart.toString().strip())
}
currPart = StringBuilder()
} else {
currPart.append(c)
}
} else {
currPart.append(c)
}
}
if (currPart.length > 0) {
result.add(currPart.toString().strip())
}
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 0 until original.length) {
val c = original[i]
when (c) {
'(' -> 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()) {
if (c == '(') {
openParentheses++
} else if (c == ')') {
openParentheses--
} else if (c == '<') {
openTemplate++
} else if (c == '>') {
openTemplate--
} else if (c == marker && openParentheses == 0 && openTemplate == 0) {
return true
}
}
return false
}
/**
* Establish dataflow from call arguments to the target [FunctionDeclaration] parameters
*
* @param target The call's target [FunctionDeclaration]
* @param arguments The call's arguments to be connected to the target's parameters
*/
fun attachCallParameters(target: FunctionDeclaration, arguments: List) {
target.parameterEdges.sortWith(
Comparator.comparing { pe: PropertyEdge ->
pe.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++
}
}
// TODO(oxisto): Remove at some point and directly use name class
fun getSimpleName(language: Language?, name: String): String {
var name = name
if (language != null) {
val delimiter = language.namespaceDelimiter
if (name.contains(delimiter)) {
name = name.substring(name.lastIndexOf(delimiter) + delimiter.length)
}
}
return name
}
// TODO(oxisto): Remove at some point and directly use name class
fun getParentName(language: Language?, name: String): String {
var name = name
if (language != null) {
val delimiter = language.namespaceDelimiter
if (name.contains(delimiter)) {
name = name.substring(0, name.lastIndexOf(delimiter))
}
}
return name
}
/**
* 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
.stream()
.filter { o: Node? -> arguments.contains(o) }
.forEach { next: Node? -> param.removeNextDFG(next) }
}
}
@JvmStatic
fun reverse(input: Stream): Stream {
val temp = input.toArray()
return IntStream.range(0, temp.size).mapToObj { i: Int -> temp[temp.size - i - 1] }
as Stream
}
/**
* 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: MutableList
adjacentNodes =
if (incoming) {
subnodes
.stream()
.filter { prevCandidate: Node -> prevCandidate.nextDFG.contains(n) }
.collect(Collectors.toList())
} else {
subnodes
.stream()
.filter { nextCandidate: Node -> nextCandidate.prevDFG.contains(n) }
.collect(Collectors.toList())
}
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 branchingDecl
*/
fun addDFGEdgesForMutuallyExclusiveBranchingExpression(
n: Node,
branchingExp: Node?,
branchingDecl: Node?
) {
var conditionNodes: MutableList = ArrayList()
if (branchingExp != null) {
conditionNodes = ArrayList()
conditionNodes.add(branchingExp)
} else if (branchingDecl != null) {
conditionNodes = getAdjacentDFGNodes(branchingDecl, true)
}
conditionNodes.forEach(Consumer { prev: Node? -> n.addPrevDFG(prev!!) })
}
enum class Connect {
NODE,
SUBTREE
}
enum class Quantifier {
ANY,
ALL
}
enum class Edge {
ENTRIES,
EXITS
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy