Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to EOG iteration and more applications #1135

Merged
merged 49 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
19ef696
Try to use a more formal worklist EOG worklist iteration for the CFSe…
KuechA Mar 17, 2023
50e1d48
Add new SplitsControlFlow interface
KuechA Mar 18, 2023
67086bc
New unreachable EOG edges pass
KuechA Mar 19, 2023
d2dc925
Handle AssignExpressions in ControlFlowSensitiveDFGPass
KuechA Mar 21, 2023
56c8a55
Mark statements which should be irrelevant because the same edges are…
KuechA Mar 21, 2023
f1cd62b
Remove setting unnecessary (already existing) edges from CFSensitiveD…
KuechA Mar 23, 2023
3229f54
ConditionalExpression splits control flow
KuechA Mar 27, 2023
f5e6342
Fix issue 1141
KuechA Mar 27, 2023
3f58534
Fix issue 1141
KuechA Mar 27, 2023
a2ab3aa
Merge branch 'main' into worklist-eog-iteration
KuechA Mar 27, 2023
9a57f2b
Consistency
KuechA Mar 28, 2023
68d97fe
Rename class and add fields
KuechA May 12, 2023
71fc6f7
Generate CDG
KuechA May 16, 2023
3e50dd6
Merge main into worklist-eog-iteration
KuechA May 16, 2023
cb035c8
Fix problem with very strange EOG self reference of literals
KuechA May 17, 2023
011ab5d
Test if statements and cdg
KuechA May 19, 2023
3396b0a
Add test for foreach loop
KuechA May 19, 2023
8560256
Reduce sonar warnings
KuechA May 22, 2023
1356f94
Fix failing tests
KuechA May 22, 2023
6262559
Merge branch 'main' into worklist-eog-iteration
KuechA May 22, 2023
ee8e2ed
Use HasShortcircuitOperators to generate ShortcircuitOperator objects…
KuechA May 22, 2023
26d25d5
Merge branch 'main' into worklist-eog-iteration
KuechA May 23, 2023
8f5e0c4
Little fix
KuechA May 23, 2023
0edfa6e
Integrate review feedback
KuechA May 31, 2023
b6090dd
Merge main into worklist-eog-iteration
KuechA May 31, 2023
648fa65
Merge main into worklist-eog-iteration
KuechA May 31, 2023
871530a
More review feedback
KuechA May 31, 2023
73b741b
Update cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWo…
KuechA Jun 7, 2023
cb9f2a2
Merge two iterateEOG functions
KuechA Jun 7, 2023
4c54266
Merge branch 'main' into worklist-eog-iteration
KuechA Jun 7, 2023
4ecc621
Fluent DSL for loops
oxisto Jun 11, 2023
c79d7cb
Fix redundant stuff
KuechA Jun 13, 2023
6ce7340
Revert "Fluent DSL for loops"
KuechA Jun 13, 2023
5a237eb
Get rid of "expectedUpdate"
KuechA Jun 13, 2023
2e6f440
Change method signatures
KuechA Jun 13, 2023
83971cf
Merge branch 'main' into worklist-eog-iteration
KuechA Jun 13, 2023
e67a75c
Small cleanup
KuechA Jun 14, 2023
6b1ff1b
Merge branch 'main' into worklist-eog-iteration
KuechA Jun 14, 2023
2f5ec72
Rename lattice to latticeelement
KuechA Jun 16, 2023
faa7d70
Remove nullable properties
KuechA Jun 16, 2023
9455a71
Nicer code
KuechA Jun 16, 2023
39b0bbd
Documentation
KuechA Jun 16, 2023
f2377fa
Merge branch 'main' into worklist-eog-iteration
KuechA Jun 16, 2023
e06d724
Documentation
KuechA Jun 23, 2023
ce93d2a
Merge branch 'main' into worklist-eog-iteration
KuechA Jun 23, 2023
a0bc4a0
Update cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/Contro…
KuechA Jul 17, 2023
8237d6c
Update cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt
KuechA Jul 17, 2023
7b37bd9
Fixes
KuechA Jul 17, 2023
24257cd
Merge main and resolve conflicts
KuechA Jul 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ open class ValueEvaluator(
return evaluateInternal(node as? Node, 0)
}

fun clearPath() {
path.clear()
}

/** Tries to evaluate this node. Anything can happen. */
protected open fun evaluateInternal(node: Node?, depth: Int): Any? {
// Add the expression to the current path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,71 +28,192 @@ package de.fraunhofer.aisec.cpg.passes
import de.fraunhofer.aisec.cpg.TranslationContext
import de.fraunhofer.aisec.cpg.analysis.ValueEvaluator
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
import de.fraunhofer.aisec.cpg.graph.edge.Properties
import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge
import de.fraunhofer.aisec.cpg.graph.statements.IfStatement
import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement
import de.fraunhofer.aisec.cpg.helpers.*
import de.fraunhofer.aisec.cpg.passes.order.DependsOn
import de.fraunhofer.aisec.cpg.processing.IVisitor
import de.fraunhofer.aisec.cpg.processing.strategy.Strategy

/**
* A [Pass] which uses a simple logic to determine constant values and mark unreachable code regions
* by setting the [Properties.UNREACHABLE] property of an eog-edge to true.
*/
@DependsOn(ControlFlowSensitiveDFGPass::class)
class UnreachableEOGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) {
override fun cleanup() {
// Nothing to do
}

override fun accept(tu: TranslationUnitDeclaration) {
tu.accept(
Strategy::AST_FORWARD,
object : IVisitor<Node>() {
override fun visit(t: Node) {
when (t) {
is IfStatement -> handleIfStatement(t)
is WhileStatement -> handleWhileStatement(t)
}

super.visit(t)
val walker = SubgraphWalker.IterativeGraphWalker()
walker.registerOnNodeVisit(::handle)
walker.iterate(tu)
}

/**
* We perform the actions for each [FunctionDeclaration].
*
* @param node every node in the TranslationResult
*/
protected fun handle(node: Node) {
if (node is FunctionDeclaration) {
val startState = UnreachabilityState()
for (firstEdge in node.nextEOGEdges) {
startState.push(firstEdge, ReachabilityLattice(Reachability.REACHABLE))
}
val finalState = iterateEOG(node.nextEOGEdges, startState, ::transfer) ?: return

for ((key, value) in finalState) {
if (value.elements == Reachability.UNREACHABLE) {
key.addProperty(Properties.UNREACHABLE, true)
}
}
)
}
}
}

/**
* This method is executed for each EOG edge which is in the worklist. [currentEdge] is the edge to
* process, [currentState] contains the state which was observed before arriving here.
*
* This method modifies the state for the next eog edge as follows:
* - If the next node in the eog is an [IfStatement], the condition is evaluated and if it is either
* always true or false, the else or then branch receives set to [Reachability.UNREACHABLE].
* - If the next node in the eog is a [WhileStatement], the condition is evaluated and if it's
* always true or false, either the EOG edge to the loop body or out of the loop body is set to
* [Reachability.UNREACHABLE].
* - For all other nodes, we simply propagate the state which led us here.
*
* Returns the updated state and true because we always expect an update of the state.
*/
fun transfer(
currentEdge: PropertyEdge<Node>,
currentState: State<PropertyEdge<Node>, Reachability>,
currentWorklist: Worklist<PropertyEdge<Node>, PropertyEdge<Node>, Reachability>
): State<PropertyEdge<Node>, Reachability> {
val currentNode = currentEdge.end
if (currentNode is IfStatement) {
handleIfStatement(currentEdge, currentNode, currentState)
} else if (currentNode is WhileStatement) {
handleWhileStatement(currentEdge, currentNode, currentState)
} else {
// For all other edges, we simply propagate the reachability property of the edge
// which made us come here.
currentNode.nextEOGEdges.forEach { currentState.push(it, currentState[currentEdge]) }
}

return currentState
}

/**
* Evaluates the condition of the [IfStatement] [n] (which is the end node of [enteringEdge]). If it
* is always true, then the else-branch receives the [state] [Reachability.UNREACHABLE]. If the
* condition is always false, then the then-branch receives the [state] [Reachability.UNREACHABLE].
* All other cases simply copy the state which led us here.
*/
private fun handleIfStatement(
enteringEdge: PropertyEdge<Node>,
n: IfStatement,
state: State<PropertyEdge<Node>, Reachability>
) {
val evalResult = ValueEvaluator().evaluate(n.condition)

private fun handleIfStatement(n: IfStatement) {
val evalResult = ValueEvaluator().evaluate(n.condition)
val (unreachableEdge, remainingEdges) =
if (evalResult is Boolean && evalResult == true) {
n.nextEOGEdges
.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }
?.addProperty(Properties.UNREACHABLE, true)
Pair(
n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 },
n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 }
)
} else if (evalResult is Boolean && evalResult == false) {
n.nextEOGEdges
.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }
?.addProperty(Properties.UNREACHABLE, true)
Pair(
n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 },
n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 }
)
} else {
Pair(null, n.nextEOGEdges)
}

if (unreachableEdge != null) {
// This edge is definitely unreachable
state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE))
}

private fun handleWhileStatement(n: WhileStatement) {
/*
* Note: It does not understand that code like
* x = true; while(x) {...; x = false;}
* makes the loop execute at least once.
* Apparently, the CPG does not offer the required functionality to
* differentiate between the first and subsequent evaluations of the
* condition.
*/
val evalResult = ValueEvaluator().evaluate(n.condition)
// For all other edges, we simply propagate the reachability property of the edge which
// made us come here.
remainingEdges.forEach { state.push(it, state[enteringEdge]) }
}

/**
* Evaluates the condition of the [WhileStatement] [n] (which is the end node of [enteringEdge]). If
* it is always true, then the edge to the code after the loop receives the [state]
* [Reachability.UNREACHABLE]. If the condition is always false, then the edge to the loop body
* receives the [state] [Reachability.UNREACHABLE]. All other cases simply copy the state which led
* us here.
*/
private fun handleWhileStatement(
enteringEdge: PropertyEdge<Node>,
n: WhileStatement,
state: State<PropertyEdge<Node>, Reachability>
) {
/*
* Note: It does not understand that code like
* x = true; while(x) {...; x = false;}
* makes the loop execute at least once.
* Apparently, the CPG does not offer the required functionality to
* differentiate between the first and subsequent evaluations of the
* condition.
*/
val evalResult = ValueEvaluator().evaluate(n.condition)

val (unreachableEdge, remainingEdges) =
if (evalResult is Boolean && evalResult == true) {
n.nextEOGEdges
.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }
?.addProperty(Properties.UNREACHABLE, true)
Pair(
n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 },
n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 }
)
} else if (evalResult is Boolean && evalResult == false) {
n.nextEOGEdges
.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }
?.addProperty(Properties.UNREACHABLE, true)
Pair(
n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 },
n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 }
)
} else {
Pair(null, n.nextEOGEdges)
}
}

override fun cleanup() {
// nothing to do
if (unreachableEdge != null) {
// This edge is definitely unreachable
state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE))
}

// For all other edges, we simply propagate the reachability property of the edge which
// made us come here.
remainingEdges.forEach { state.push(it, state[enteringEdge]) }
}

/** Implements the [Lattice] over reachability properties: TOP | REACHABLE | UNREACHABLE | BOTTOM */
class ReachabilityLattice(override val elements: Reachability) : Lattice<Reachability>(elements) {
oxisto marked this conversation as resolved.
Show resolved Hide resolved
override fun lub(other: Lattice<Reachability>?) =
ReachabilityLattice(maxOf(this.elements, other?.elements ?: Reachability.BOTTOM))

override fun duplicate() = ReachabilityLattice(this.elements)

override fun compareTo(other: Lattice<Reachability>?) =
this.elements.compareTo(other?.elements ?: Reachability.BOTTOM)
}

/** The ordering will be as follows: BOTTOM (no information) < UNREACHABLE < REACHABLE < TOP */
KuechA marked this conversation as resolved.
Show resolved Hide resolved
enum class Reachability {
BOTTOM,
UNREACHABLE,
REACHABLE,
TOP
}

/**
* A state which actually holds a state for all [PropertyEdge]s, one only for declarations and one
* for ReturnStatements.
*/
class UnreachabilityState : State<PropertyEdge<Node>, Reachability>()
Original file line number Diff line number Diff line change
Expand Up @@ -167,21 +167,25 @@ class MultiValueEvaluatorTest {

printB = main.bodyOrNull<CallExpression>(1)
assertNotNull(printB)
evaluator.clearPath()
value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet
oxisto marked this conversation as resolved.
Show resolved Hide resolved
assertEquals(setOf<Long>(0, 1, 2), value.values)

printB = main.bodyOrNull<CallExpression>(2)
assertNotNull(printB)
evaluator.clearPath()
value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet
assertEquals(setOf<Long>(0, 1, 2, 4), value.values)

printB = main.bodyOrNull<CallExpression>(3)
assertNotNull(printB)
evaluator.clearPath()
value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet
assertEquals(setOf<Long>(-4, -2, -1, 0, 1, 2, 4), value.values)

printB = main.bodyOrNull<CallExpression>(4)
assertNotNull(printB)
evaluator.clearPath()
value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet
assertEquals(setOf<Long>(3, 6), value.values)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties
import de.fraunhofer.aisec.cpg.graph.statements.IfStatement
import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement
import java.nio.file.Path
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.test.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.TestInstance

Expand Down Expand Up @@ -80,8 +77,46 @@ class UnreachableEOGPassTest {
val ifStatement = method.bodyOrNull<IfStatement>()
assertNotNull(ifStatement)

assertFalse(ifStatement.nextEOGEdges[0].getProperty(Properties.UNREACHABLE) as Boolean)
assertTrue(ifStatement.nextEOGEdges[1].getProperty(Properties.UNREACHABLE) as Boolean)
// Check if the then-branch is set as reachable including all the edges until reaching the
// print
val thenDecl = ifStatement.nextEOGEdges[0]
assertFalse(thenDecl.getProperty(Properties.UNREACHABLE) as Boolean)
assertEquals(1, thenDecl.end.nextEOGEdges.size)
// The "++"
val incOp = thenDecl.end.nextEOGEdges[0]
assertFalse(incOp.getProperty(Properties.UNREACHABLE) as Boolean)
assertEquals(1, incOp.end.nextEOGEdges.size)
// The compoundStmt
val thenCompound = incOp.end.nextEOGEdges[0]
assertFalse(thenCompound.getProperty(Properties.UNREACHABLE) as Boolean)
assertEquals(1, thenCompound.end.nextEOGEdges.size)
// There's the outgoing EOG edge to the statement after the branching
val thenExit = thenCompound.end.nextEOGEdges[0]
assertFalse(thenExit.getProperty(Properties.UNREACHABLE) as Boolean)

// Check if the else-branch is set as unreachable including all the edges until reaching the
// print
val elseDecl = ifStatement.nextEOGEdges[1]
assertTrue(elseDecl.getProperty(Properties.UNREACHABLE) as Boolean)
assertEquals(1, elseDecl.end.nextEOGEdges.size)
// The "--"
val decOp = elseDecl.end.nextEOGEdges[0]
assertTrue(decOp.getProperty(Properties.UNREACHABLE) as Boolean)
assertEquals(1, decOp.end.nextEOGEdges.size)
// The compoundStmt
val elseCompound = decOp.end.nextEOGEdges[0]
assertTrue(elseCompound.getProperty(Properties.UNREACHABLE) as Boolean)
assertEquals(1, elseCompound.end.nextEOGEdges.size)
// There's the outgoing EOG edge to the statement after the branching
val elseExit = elseCompound.end.nextEOGEdges[0]
assertTrue(elseExit.getProperty(Properties.UNREACHABLE) as Boolean)

// After the branching, it's reachable again. Check that we found the merge node and that we
// continue with reachable edges.
assertEquals(thenExit.end, elseExit.end)
val mergeNode = thenExit.end
assertEquals(1, mergeNode.nextEOGEdges.size)
assertFalse(mergeNode.nextEOGEdges[0].getProperty(Properties.UNREACHABLE) as Boolean)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,22 @@ interface HasUnknownType : LanguageTrait {
* evaluation if the logical result is already known: '&&', '||' in Java or 'and','or' in Python
*/
interface HasShortCircuitOperators : LanguageTrait {
// '&&', 'and', '^'
/**
* Operations which only execute the rhs of a binary operation if the lhs is `true`. Typically,
* these are `&&`, `and` or `^`
*/
val conjunctiveOperators: List<String>
// '||', 'or', 'v'

/**
* Operations which only execute the rhs of a binary operation if the lhs is `false`. Typically,
* these are `||`, `or` or `v`
*/
val disjunctiveOperators: List<String>

/**
* The union of [conjunctiveOperators] and [disjunctiveOperators], i.e., all binary operators of
* this language which result in some kind of branching behavior.
*/
val operatorCodes: Set<String>
KuechA marked this conversation as resolved.
Show resolved Hide resolved
get() = conjunctiveOperators.union(disjunctiveOperators)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2023, 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

/** A node triggering a conditional execution of other code. */
interface BranchingNode {
/** The node which affects the next EOG edge. Typically, this is a condition or similar. */
val branchedBy: Node?
}
Loading