diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt index d6228a0421..b38749c450 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/IdentitySet.kt @@ -60,9 +60,9 @@ open class IdentitySet : MutableSet { } override fun equals(other: Any?): Boolean { - if (other !is IdentitySet<*>) return false - val otherSet = other as? IdentitySet<*> - return otherSet != null && this.containsAll(otherSet) && otherSet.containsAll(this) + if (other !is Set<*>) return false + val otherSet = other + return this.containsAll(otherSet) && otherSet.containsAll(this) } override fun add(element: T): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/functional/BasicLattices.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/functional/BasicLattices.kt index ab7203c8cd..fad488c9a0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/functional/BasicLattices.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/functional/BasicLattices.kt @@ -25,7 +25,10 @@ */ package de.fraunhofer.aisec.cpg.helpers.functional +import de.fraunhofer.aisec.cpg.helpers.IdentitySet +import de.fraunhofer.aisec.cpg.helpers.identitySetOf import de.fraunhofer.aisec.cpg.helpers.toIdentitySet +import java.util.IdentityHashMap import kotlin.Pair import kotlin.collections.component1 import kotlin.collections.component2 @@ -42,33 +45,36 @@ import kotlin.collections.toMap * Implementations of this class have to implement the comparator, the least upper bound of two * lattices. */ -abstract class LatticeElement(val elements: T) : Comparable> { +interface LatticeElement : Comparable> { + val elements: T + /** * Computes the least upper bound of this lattice and [other]. It returns a new object and does * not modify either of the objects. */ - abstract fun lub(other: LatticeElement): LatticeElement + fun lub(other: LatticeElement): LatticeElement /** Duplicates the object, i.e., makes a deep copy. */ - abstract fun duplicate(): LatticeElement + fun duplicate(): LatticeElement } -typealias PowersetLatticeT = LatticeElement> +typealias PowersetLatticeT = PowersetLattice, V> -inline fun emptyPowersetLattice() = PowersetLattice(setOf()) +inline fun emptyPowersetLattice() = + PowersetLattice, V>(identitySetOf()) /** * Implements the [LatticeElement] for a lattice over a set of nodes. The lattice itself is * constructed by the powerset. */ -class PowersetLattice(elements: Set) : LatticeElement>(elements) { - override fun lub(other: LatticeElement>) = - PowersetLattice(this.elements.union(other.elements)) +open class PowersetLattice, T>(override val elements: V) : LatticeElement { + override fun lub(other: LatticeElement) = + PowersetLattice(this.elements.union(other.elements).toIdentitySet()) - override fun duplicate(): LatticeElement> = - PowersetLattice(this.elements.toIdentitySet()) + override fun duplicate(): LatticeElement = + PowersetLattice(this.elements.toIdentitySet() as V) - override fun compareTo(other: LatticeElement>): Int { + override fun compareTo(other: LatticeElement): Int { return if (this.elements == other.elements) { 0 } else if (this.elements.containsAll(other.elements)) { @@ -79,8 +85,7 @@ class PowersetLattice(elements: Set) : LatticeElement>(elements) { } override fun equals(other: Any?): Boolean { - // The call of `toSet` ensures that we don't get stuck for different types of sets. - return other is PowersetLattice && this.elements.toSet() == other.elements.toSet() + return other is PowersetLattice && this.elements == other.elements } override fun hashCode(): Int { @@ -88,40 +93,44 @@ class PowersetLattice(elements: Set) : LatticeElement>(elements) { } } -typealias MapLatticeT = LatticeElement> +typealias MapLatticeT = MapLattice -inline fun emptyMapLattice() = MapLattice(mapOf()) +inline fun , T> emptyMapLattice() = + MapLattice(IdentityHashMap()) /** Implements the [LatticeElement] for a lattice over a map of nodes to another lattice. */ -open class MapLattice(elements: Map>) : - LatticeElement>>(elements) { +open class MapLattice, T>(override val elements: IdentityHashMap) : + LatticeElement> { + override fun lub( - other: LatticeElement>> - ): LatticeElement>> { + other: LatticeElement> + ): LatticeElement> { val allKeys = other.elements.keys.union(this.elements.keys) val newMap = - allKeys.fold(mutableMapOf>()) { current, key -> - val otherValue = other.elements[key] - val thisValue = this.elements[key] + allKeys.fold(IdentityHashMap()) { current, key -> + val otherValue: V? = other.elements[key] + val thisValue: V? = this.elements[key] val newValue = if (thisValue != null && otherValue != null) { thisValue.lub(otherValue) } else if (thisValue != null) { thisValue } else otherValue - newValue?.let { current[key] = it } + (newValue as? V?)?.let { current[key] = it } current } return MapLattice(newMap) } - override fun duplicate(): LatticeElement>> { + override fun duplicate(): LatticeElement> { return MapLattice( - this.elements.map { (k, v) -> Pair>(k, v.duplicate()) }.toMap() + IdentityHashMap( + this.elements.map { (k, v) -> Pair(k, v.duplicate() as V) }.toMap() + ) ) } - override fun compareTo(other: LatticeElement>>): Int { + override fun compareTo(other: LatticeElement>): Int { if (this.elements == other.elements) return 0 if ( this.elements.keys.containsAll(other.elements.keys) && @@ -134,7 +143,7 @@ open class MapLattice(elements: Map>) : } override fun equals(other: Any?): Boolean { - return other is MapLattice && this.elements == other.elements + return other is MapLattice && this.elements == other.elements } override fun hashCode(): Int { @@ -142,24 +151,23 @@ open class MapLattice(elements: Map>) : } } -open class TupleLattice(elements: Pair, LatticeElement>) : - LatticeElement, LatticeElement>>(elements) { - override fun lub( - other: LatticeElement, LatticeElement>> - ): LatticeElement, LatticeElement>> { +open class TupleLattice, V : LatticeElement, S, T>( + override val elements: Pair +) : LatticeElement> { + override fun lub(other: LatticeElement>): LatticeElement> { return TupleLattice( Pair( - this.elements.first.lub(other.elements.first), - this.elements.second.lub(other.elements.second) + this.elements.first.lub(other.elements.first) as U, + this.elements.second.lub(other.elements.second) as V ) ) } - override fun duplicate(): LatticeElement, LatticeElement>> { - return TupleLattice(Pair(elements.first.duplicate(), elements.second.duplicate())) + override fun duplicate(): LatticeElement> { + return TupleLattice(Pair(elements.first.duplicate() as U, elements.second.duplicate() as V)) } - override fun compareTo(other: LatticeElement, LatticeElement>>): Int { + override fun compareTo(other: LatticeElement>): Int { if ( this.elements.first == other.elements.first && this.elements.second == other.elements.second @@ -174,7 +182,7 @@ open class TupleLattice(elements: Pair, LatticeElement) return false + if (other !is TupleLattice) return false return other.elements.first == this.elements.first && other.elements.second == this.elements.second } @@ -189,10 +197,10 @@ open class TupleLattice(elements: Pair, LatticeElement( - elements: Triple, LatticeElement, LatticeElement> -) : LatticeElement, LatticeElement, LatticeElement>>(elements) { + override val elements: Triple, LatticeElement, LatticeElement> +) : LatticeElement, LatticeElement, LatticeElement>> { override fun lub( - other: LatticeElement, LatticeElement, LatticeElement>> + other: LatticeElement, LatticeElement, LatticeElement>> ): LatticeElement, LatticeElement, LatticeElement>> { return TripleLattice( Triple( diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index c8177d627d..691d323af7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -37,9 +37,18 @@ import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.ReturnStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConditionalExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.ShortCircuitOperator -import de.fraunhofer.aisec.cpg.helpers.* +import de.fraunhofer.aisec.cpg.helpers.IdentitySet +import de.fraunhofer.aisec.cpg.helpers.functional.LatticeElement +import de.fraunhofer.aisec.cpg.helpers.functional.MapLattice +import de.fraunhofer.aisec.cpg.helpers.functional.MapLatticeT +import de.fraunhofer.aisec.cpg.helpers.functional.PowersetLattice +import de.fraunhofer.aisec.cpg.helpers.functional.PowersetLatticeT +import de.fraunhofer.aisec.cpg.helpers.functional.iterateEOGClean +import de.fraunhofer.aisec.cpg.helpers.identitySetOf import de.fraunhofer.aisec.cpg.passes.configuration.DependsOn import java.util.* +import kotlin.collections.component1 +import kotlin.collections.component2 /** This pass builds the Control Dependence Graph (CDG) by iterating through the EOG. */ @DependsOn(EvaluationOrderGraphPass::class) @@ -87,19 +96,21 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : EOGStarterPass( // Maps nodes to their "cdg parent" (i.e. the dominator) and also has the information // through which path it is reached. If all outgoing paths of the node's dominator result in // the node, we use the dominator's state instead (i.e., we move the node one layer upwards) - val startState = PrevEOGState() - val identityMap = IdentityHashMap>() - identityMap[startNode] = identitySetOf(startNode) - startState.push(startNode, PrevEOGLattice(identityMap)) - val finalState = iterateEOG(startNode.nextEOGEdges, startState, ::handleEdge) ?: return + var startState = PrevEOGState(mapOf()) + val identityMap = IdentityHashMap>() + identityMap[startNode] = PowersetLattice(identitySetOf(startNode)) + startState = startState.push(startNode, PrevEOGLattice(identityMap)) + log.debug("Iterating EOG of {}", startNode) + val finalState = iterateEOGClean(startNode.nextEOGEdges, startState, ::handleEdge) + log.debug("Done iterating EOG of {}", startNode) val branchingNodeConditionals = getBranchingNodeConditions(startNode) // Collect the information, identify merge points, etc. This is not really efficient yet :( - for ((node, dominatorPaths) in finalState) { + for ((node, dominatorPaths) in finalState.elements) { val dominatorsList = dominatorPaths.elements.entries - .map { (k, v) -> Pair(k, v.toMutableSet()) } + .map { (k, v) -> Pair(k, v.elements.toMutableSet()) } .toMutableList() val finalDominators = mutableListOf>>() val conditionKeys = @@ -116,13 +127,13 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : EOGStarterPass( // want this. Move it one layer up. for (k1 in conditionKeys) { dominatorsList.removeIf { k1 == it.first } - finalState[k1]?.elements?.forEach { (newK, newV) -> + finalState.elements[k1]?.elements?.forEach { (newK, newV) -> val entry = dominatorsList.firstOrNull { it.first == newK } entry?.let { dominatorsList.remove(entry) - val update = entry.second.addAll(newV) + val update = entry.second.addAll(newV.elements) if (update) dominatorsList.add(entry) else finalDominators.add(entry) - } ?: dominatorsList.add(Pair(newK, newV.toMutableSet())) + } ?: dominatorsList.add(Pair(newK, newV.elements.toMutableSet())) } } } @@ -135,19 +146,22 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : EOGStarterPass( // We are reachable from all the branches of a branching node. Add this parent // to the worklist or update an existing entry. Also consider already existing // entries in finalDominators list and update it (if necessary) - val newDominatorMap = finalState[k]?.elements + val newDominatorMap = finalState.elements[k]?.elements newDominatorMap?.forEach { (newK, newV) -> when { dominatorsList.any { it.first == newK } -> { // Entry exists => update it - dominatorsList.first { it.first == newK }.second.addAll(newV) + dominatorsList + .first { it.first == newK } + .second + .addAll(newV.elements) } finalDominators.any { it.first == newK } -> { // Entry in final dominators => Delete it and add it to the worklist // (but only if something changed) val entry = finalDominators.first { it.first == newK } finalDominators.remove(entry) - val update = entry.second.addAll(newV) + val update = entry.second.addAll(newV.elements) if ( update && alreadySeen.none { @@ -159,17 +173,17 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : EOGStarterPass( else finalDominators.add(entry) } alreadySeen.none { - it.first == newK && it.second.containsAll(newV) + it.first == newK && it.second.containsAll(newV.elements) } -> { // We don't have an entry yet => add a new one - val newEntry = Pair(newK, newV.toMutableSet()) + val newEntry = Pair(newK, newV.elements.toMutableSet()) dominatorsList.add(newEntry) } else -> { // Not sure what to do, there seems to be a cycle but this entry is // not in finalDominators for some reason. Add to finalDominators // now. - finalDominators.add(Pair(newK, newV.toMutableSet())) + finalDominators.add(Pair(newK, newV.elements.toMutableSet())) } } } @@ -262,9 +276,11 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : EOGStarterPass( * Returns the updated state and true because we always expect an update of the state. */ fun handleEdge( - currentEdge: Edge, - currentState: State>> -): State>> { + currentEdge: EvaluationOrder, + currentState: + LatticeElement>>>>> +): LatticeElement>>>>> { + var newState = currentState as? PrevEOGState ?: return currentState // Check if we start in a branching node and if this edge leads to the conditional // branch. In this case, the next node will move "one layer downwards" in the CDG. if (currentEdge.start is BranchingNode) { // && currentEdge.isConditionalBranch()) { @@ -274,15 +290,16 @@ fun handleEdge( val prevPathLattice = PrevEOGLattice( IdentityHashMap( - currentState[currentEdge.start]?.elements?.filter { (k, _) -> - k == currentEdge.start - } + newState.elements[currentEdge.start] + ?.elements + ?.filter { (k, _) -> k == currentEdge.start } + ?.mapValues { (_, v) -> PowersetLattice(v.elements) } ) ) - val map = IdentityHashMap>() - map[currentEdge.start] = identitySetOf(currentEdge.end) - val newPath = PrevEOGLattice(map).lub(prevPathLattice) as PrevEOGLattice - currentState.push(currentEdge.end, newPath) + val map = IdentityHashMap>() + map[currentEdge.start] = PowersetLattice(identitySetOf(currentEdge.end)) + val newPath = PrevEOGLattice(map).lub(prevPathLattice) + newState = newState.push(currentEdge.end, newPath) } else { // We did not start in a branching node, so for the next node, we have the same path // (last branching + first end node) as for the start node of this edge. @@ -291,14 +308,18 @@ fun handleEdge( // have "end" as the first node in the "branch". val state = PrevEOGLattice( - currentState[currentEdge.start]?.elements - ?: IdentityHashMap( - mutableMapOf(Pair(currentEdge.start, identitySetOf(currentEdge.end))) - ) + IdentityHashMap( + newState.elements[currentEdge.start]?.elements?.mapValues { (_, v) -> + PowersetLattice(v.elements) + } + ?: mutableMapOf( + Pair(currentEdge.start, PowersetLattice(identitySetOf(currentEdge.end))) + ) + ) ) - currentState.push(currentEdge.end, state) + newState = newState.push(currentEdge.end, state) } - return currentState + return newState } /** @@ -357,40 +378,73 @@ private fun IfStatement.allBranchesFromMyThenBranchGoThrough(node: Node?): Boole * Implements the [LatticeElement] over a set of nodes and their set of "nextEOG" nodes which reach * this node. */ -class PrevEOGLattice(override val elements: IdentityHashMap>) : - LatticeElement>>(elements) { +class PrevEOGLattice(elements: IdentityHashMap>) : + MapLatticeT, IdentitySet>(elements) { override fun lub( - other: LatticeElement>> - ): LatticeElement>> { - val newMap = IdentityHashMap(other.elements.mapValues { (_, v) -> v.toIdentitySet() }) - for ((key, value) in this.elements) { - newMap.computeIfAbsent(key, ::identitySetOf).addAll(value) - } + other: LatticeElement>> + ): PrevEOGLattice { + val powerset = PowersetLattice(IdentitySet()) + MapLattice, Node>, IdentitySet>( + IdentityHashMap() + ) + val allKeys = other.elements.keys.union(this.elements.keys) + val newMap = + allKeys.fold(IdentityHashMap>()) { current, key -> + val otherValue = other.elements[key] + val thisValue = this.elements[key] + val newValue = + if (thisValue != null && otherValue != null) { + thisValue.lub(otherValue) + } else if (thisValue != null) { + thisValue + } else otherValue + newValue?.let { current[key] = it } + current + } return PrevEOGLattice(newMap) } - override fun duplicate() = PrevEOGLattice(IdentityHashMap(this.elements)) - - override fun compareTo(other: LatticeElement>>): Int { - return if ( - this.elements.keys.containsAll(other.elements.keys) && - this.elements.all { (k, v) -> v.containsAll(other.elements[k] ?: identitySetOf()) } - ) { - if ( - this.elements.keys.size > (other.elements.keys.size) || - this.elements.any { (k, v) -> v.size > (other.elements[k]?.size ?: 0) } + override fun duplicate() = + PrevEOGLattice( + IdentityHashMap( + this.elements.mapValues { (_, v) -> PowersetLattice(v.elements) }.toMap() ) - 1 - else 0 - } else { - -1 - } - } + ) } /** * A state which actually holds a state for all [Edge]s. It maps the node to its * [BranchingNode]-parent and the path through which it is reached. */ -class PrevEOGState : State>>() +class PrevEOGState(elements: IdentityHashMap) : + MapLattice>(elements) { + + override fun lub(other: LatticeElement>): PrevEOGState { + val allKeys = other.elements.keys.union(this.elements.keys) + val newMap = + allKeys.fold(IdentityHashMap()) { current, key -> + val otherValue = other.elements[key] + val thisValue = this.elements[key] + val newValue = + if (thisValue != null && otherValue != null) { + thisValue.lub(otherValue) + } else if (thisValue != null) { + thisValue + } else otherValue + newValue?.let { current[key] = it } + current + } + return PrevEOGState(newMap) + } + + override fun duplicate() = PrevEOGState(this.elements) + + fun push(newNode: Node, newEOGLattice: PrevEOGLattice): PrevEOGState { + return this.lub( + PrevEOGState( + IdentityHashMap(mutableMapOf(Pair(newNode, newEOGLattice))) + ) + ) + } +}