Skip to content

Commit

Permalink
Overhaul of type propagation (#1268)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexander Kuechler <[email protected]>
Co-authored-by: Maximilian Kaul <[email protected]>
  • Loading branch information
3 people committed Aug 9, 2023
1 parent b29b02b commit 490562d
Show file tree
Hide file tree
Showing 107 changed files with 2,231 additions and 2,086 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.analysis
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.invoke
import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement
import de.fraunhofer.aisec.cpg.graph.statements.ForStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
Expand Down Expand Up @@ -78,6 +79,7 @@ class MultiValueEvaluator : ValueEvaluator() {
is Literal<*> -> return node.value
is DeclaredReferenceExpression -> return handleDeclaredReferenceExpression(node, depth)
is UnaryOperator -> return handleUnaryOp(node, depth)
is AssignExpression -> return handleAssignExpression(node, depth)
is BinaryOperator -> return handleBinaryOperator(node, depth)
// Casts are just a wrapper in this case, we are interested in the inner expression
is CastExpression -> return this.evaluateInternal(node.expression, depth + 1)
Expand All @@ -92,6 +94,48 @@ class MultiValueEvaluator : ValueEvaluator() {
return cannotEvaluate(node, this)
}

/**
* We are handling some basic arithmetic compound assignment operations and string operations
* that are more or less language-independent.
*/
override fun handleAssignExpression(node: AssignExpression, depth: Int): Any? {
// This only works for compound assignments
if (!node.isCompoundAssignment) {
return super.handleAssignExpression(node, depth)
}

// Resolve lhs
val lhsValue = evaluateInternal(node.lhs.singleOrNull(), depth + 1)
// Resolve rhs
val rhsValue = evaluateInternal(node.rhs.singleOrNull(), depth + 1)

if (lhsValue !is Collection<*> && rhsValue !is Collection<*>) {
return computeBinaryOpEffect(lhsValue, rhsValue, node)
}

val result = mutableSetOf<Any?>()
if (lhsValue is Collection<*>) {
// lhsValue is a collection. We compute the result for all lhsValues with all the
// rhsValue(s).
for (lhs in lhsValue) {
if (rhsValue is Collection<*>) {
result.addAll(rhsValue.map { r -> computeBinaryOpEffect(lhs, r, node) })
} else {
result.add(computeBinaryOpEffect(lhs, rhsValue, node))
}
}
} else {
// lhsValue is not a collection (so rhsValues is because if both wouldn't be a
// collection, this would be covered by the if-statement some lines above). We compute
// the result for the lhsValue with all the rhsValues.
result.addAll(
(rhsValue as Collection<*>).map { r -> computeBinaryOpEffect(lhsValue, r, node) }
)
}

return result
}

/**
* We are handling some basic arithmetic binary operations and string operations that are more
* or less language-independent.
Expand Down Expand Up @@ -279,60 +323,64 @@ class MultiValueEvaluator : ValueEvaluator() {
val loopOp = loop.iterationStatement
loopVar =
when (loopOp) {
is BinaryOperator -> {
is AssignExpression -> {
if (
loopOp.operatorCode == "=" &&
(loopOp.lhs as? DeclaredReferenceExpression)?.refersTo ==
expr.refersTo &&
loopOp.rhs is BinaryOperator
(loopOp.lhs.singleOrNull() as? DeclaredReferenceExpression)
?.refersTo == expr.refersTo &&
loopOp.rhs.singleOrNull() is BinaryOperator
) {
// Assignment to the variable, take the rhs and see if it's also a
// binary operator
val opLhs =
if (
((loopOp.rhs as BinaryOperator).lhs
((loopOp.rhs<BinaryOperator>())?.lhs
as? DeclaredReferenceExpression)
?.refersTo == expr.refersTo
) {
loopVar
} else {
(loopOp.rhs as BinaryOperator).lhs
(loopOp.rhs<BinaryOperator>())?.lhs
}
val opRhs =
if (
((loopOp.rhs as BinaryOperator).rhs
((loopOp.rhs<BinaryOperator>())?.rhs
as? DeclaredReferenceExpression)
?.refersTo == expr.refersTo
) {
loopVar
} else {
evaluateInternal((loopOp.rhs as BinaryOperator).rhs, depth + 1)
evaluateInternal((loopOp.rhs<BinaryOperator>())?.rhs, depth + 1)
}
computeBinaryOpEffect(opLhs, opRhs, (loopOp.rhs as BinaryOperator))
computeBinaryOpEffect(opLhs, opRhs, (loopOp.rhs<BinaryOperator>()))
as? Number
} else {
// No idea what this is but it's a binary op...
val opLhs =
if (
(loopOp.lhs as? DeclaredReferenceExpression)?.refersTo ==
expr.refersTo
) {
loopVar
} else {
loopOp.lhs
}
val opRhs =
if (
(loopOp.rhs as? DeclaredReferenceExpression)?.refersTo ==
expr.refersTo
) {
loopVar
} else {
loopOp.rhs
}
computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number
cannotEvaluate(loopOp, this)
}
}
is BinaryOperator -> {

// No idea what this is but it's a binary op...
val opLhs =
if (
(loopOp.lhs as? DeclaredReferenceExpression)?.refersTo ==
expr.refersTo
) {
loopVar
} else {
loopOp.lhs
}
val opRhs =
if (
(loopOp.rhs as? DeclaredReferenceExpression)?.refersTo ==
expr.refersTo
) {
loopVar
} else {
loopOp.rhs
}
computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number
}
is UnaryOperator -> {
computeUnaryOpEffect(
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package de.fraunhofer.aisec.cpg.analysis

import de.fraunhofer.aisec.cpg.graph.AccessValues
import de.fraunhofer.aisec.cpg.graph.HasOperatorCode
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
Expand Down Expand Up @@ -99,7 +100,7 @@ open class ValueEvaluator(
// While we are not handling different paths of variables with If statements, we can
// easily be partly path-sensitive in a conditional expression
is ConditionalExpression -> return handleConditionalExpression(node, depth)
is AssignExpression -> return handleAssignExpression(node)
is AssignExpression -> return handleAssignExpression(node, depth)
}

// At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe
Expand All @@ -108,8 +109,19 @@ open class ValueEvaluator(
}

/** Under certain circumstances, an assignment can also be used as an expression. */
private fun handleAssignExpression(node: AssignExpression): Any? {
if (node.usedAsExpression) {
protected open fun handleAssignExpression(node: AssignExpression, depth: Int): Any? {
// Handle compound assignments. Only possible with single values
val lhs = node.lhs.singleOrNull()
val rhs = node.rhs.singleOrNull()
if (lhs != null && rhs != null && node.isCompoundAssignment) {
// Resolve rhs
val rhsValue = evaluateInternal(rhs, depth + 1)

// Resolve lhs
val lhsValue = evaluateInternal(lhs, depth + 1)

return computeBinaryOpEffect(lhsValue, rhsValue, node)
} else if (node.usedAsExpression) {
return node.expressionValue
}

Expand All @@ -130,12 +142,19 @@ open class ValueEvaluator(
return computeBinaryOpEffect(lhsValue, rhsValue, expr)
}

/**
* Computes the effect of basic "binary" operators.
*
* Note: this is both used by a [BinaryOperator] with basic arithmetic operations as well as
* [AssignExpression], if [AssignExpression.isCompoundAssignment] is true.
*/
protected fun computeBinaryOpEffect(
lhsValue: Any?,
rhsValue: Any?,
expr: BinaryOperator
has: HasOperatorCode?,
): Any? {
return when (expr.operatorCode) {
val expr = has as? Expression
return when (has?.operatorCode) {
"+",
"+=" -> handlePlus(lhsValue, rhsValue, expr)
"-",
Expand All @@ -149,11 +168,11 @@ open class ValueEvaluator(
"<" -> handleLess(lhsValue, rhsValue, expr)
"<=" -> handleLEq(lhsValue, rhsValue, expr)
"==" -> handleEq(lhsValue, rhsValue, expr)
else -> cannotEvaluate(expr, this)
else -> cannotEvaluate(expr as Node, this)
}
}

private fun handlePlus(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? {
private fun handlePlus(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? {
return when {
lhsValue is String -> lhsValue + rhsValue
lhsValue is Int && (rhsValue is Double || rhsValue is Float) ->
Expand All @@ -174,7 +193,7 @@ open class ValueEvaluator(
}
}

private fun handleMinus(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? {
private fun handleMinus(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? {
return when {
lhsValue is Int && (rhsValue is Double || rhsValue is Float) ->
lhsValue - (rhsValue as Number).toDouble()
Expand All @@ -194,7 +213,7 @@ open class ValueEvaluator(
}
}

private fun handleDiv(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? {
private fun handleDiv(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? {
return when {
rhsValue == 0 -> cannotEvaluate(expr, this)
lhsValue is Int && (rhsValue is Double || rhsValue is Float) ->
Expand All @@ -215,7 +234,7 @@ open class ValueEvaluator(
}
}

private fun handleTimes(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? {
private fun handleTimes(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? {
return when {
lhsValue is Int && (rhsValue is Double || rhsValue is Float) ->
lhsValue * (rhsValue as Number).toDouble()
Expand All @@ -235,39 +254,39 @@ open class ValueEvaluator(
}
}

private fun handleGreater(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? {
private fun handleGreater(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? {
return if (lhsValue is Number && rhsValue is Number) {
lhsValue.compareTo(rhsValue) > 0
} else {
cannotEvaluate(expr, this)
}
}

private fun handleGEq(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? {
private fun handleGEq(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? {
return if (lhsValue is Number && rhsValue is Number) {
lhsValue.compareTo(rhsValue) >= 0
} else {
cannotEvaluate(expr, this)
}
}

private fun handleLess(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? {
private fun handleLess(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? {
return if (lhsValue is Number && rhsValue is Number) {
lhsValue.compareTo(rhsValue) < 0
} else {
cannotEvaluate(expr, this)
}
}

private fun handleLEq(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? {
private fun handleLEq(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? {
return if (lhsValue is Number && rhsValue is Number) {
lhsValue.compareTo(rhsValue) <= 0
} else {
cannotEvaluate(expr, this)
}
}

private fun handleEq(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? {
private fun handleEq(lhsValue: Any?, rhsValue: Any?, expr: Expression?): Any? {
return if (lhsValue is Number && rhsValue is Number) {
lhsValue.compareTo(rhsValue) == 0
} else {
Expand Down Expand Up @@ -414,14 +433,14 @@ open class ValueEvaluator(
// Remove the self reference
list =
list.filter {
!((it is BinaryOperator && it.lhs == ref) ||
!((it is AssignExpression && it.lhs.singleOrNull() == ref) ||
(it is UnaryOperator && it.input == ref))
}
} else if (ref.access == AccessValues.READWRITE && !isCase2) {
// Consider only the self reference
list =
list.filter {
((it is BinaryOperator && it.lhs == ref) ||
((it is AssignExpression && it.lhs.singleOrNull() == ref) ||
(it is UnaryOperator && it.input == ref))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import de.fraunhofer.aisec.cpg.graph.evaluate
import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement
import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement
import de.fraunhofer.aisec.cpg.graph.statements.ForStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator
import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import de.fraunhofer.aisec.cpg.passes.EdgeCachePass
import java.nio.file.Path
Expand Down Expand Up @@ -211,7 +211,10 @@ class MultiValueEvaluatorTest {
assertNotNull(forLoop)

val evaluator = MultiValueEvaluator()
val iVar = ((forLoop.statement as CompoundStatement).statements[0] as BinaryOperator).rhs
val iVarList =
((forLoop.statement as CompoundStatement).statements[0] as AssignExpression).rhs
assertEquals(1, iVarList.size)
val iVar = iVarList.first()
val value = evaluator.evaluate(iVar) as ConcreteNumberSet
assertEquals(setOf<Long>(0, 1, 2, 3, 4, 5), value.values)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ import de.fraunhofer.aisec.cpg.graph.bodyOrNull
import de.fraunhofer.aisec.cpg.graph.byNameOrNull
import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration
import de.fraunhofer.aisec.cpg.graph.invoke
import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement
import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement
import de.fraunhofer.aisec.cpg.graph.statements.ForStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator
import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
import java.nio.file.Path
import kotlin.test.Test
Expand Down Expand Up @@ -105,7 +107,9 @@ class SizeEvaluatorTest {
assertNotNull(forLoop)

val subscriptExpr =
((forLoop.statement as CompoundStatement).statements[0] as BinaryOperator).lhs
((forLoop.statement as CompoundStatement).statements[0] as AssignExpression).lhs<
ArraySubscriptionExpression
>()

value = evaluator.evaluate(subscriptExpr) as Int
assertEquals(3, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@
*/
package de.fraunhofer.aisec.cpg.console

import de.fraunhofer.aisec.cpg.graph.HasType
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement
import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement
import de.fraunhofer.aisec.cpg.graph.statements.IfStatement
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.types.HasType
import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation
import de.fraunhofer.aisec.cpg.sarif.Region
import java.io.File
Expand Down
Loading

0 comments on commit 490562d

Please sign in to comment.