Skip to content

Commit

Permalink
First experiment to model multiple variable assignments
Browse files Browse the repository at this point in the history
This adds a new statement class, called `AssignStatement` as well as `TupleType`.
  • Loading branch information
oxisto committed Mar 8, 2023
1 parent 232ecc7 commit 9ba037f
Show file tree
Hide file tree
Showing 25 changed files with 706 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.AccessValues
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression
import kotlin.UnsupportedOperationException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
Expand Down Expand Up @@ -95,13 +96,23 @@ 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)
}

// At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe
// this helps
return cannotEvaluate(node, this)
}

/** Under certain circumstances, an assignment can also be used as an expression. */
private fun handleAssignExpression(node: AssignExpression): Any? {
if (node.usedAsExpression) {
return node.expressionValue
}

return cannotEvaluate(node, this)
}

/**
* We are handling some basic arithmetic binary operations and string operations that are more
* or less language-independent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,13 +357,15 @@ fun allNonLiteralsFromFlowTo(from: Node, to: Node, allPaths: List<List<Node>>):
val noAssignmentToFrom =
allPaths.none {
it.any { it2 ->
if (it2 is Assignment) {
val prevMemberFrom = (from as? MemberExpression)?.prevDFG
val nextMemberTo = (it2.target as? MemberExpression)?.nextDFG
it2.target == from ||
prevMemberFrom != null &&
nextMemberTo != null &&
prevMemberFrom.any { it3 -> nextMemberTo.contains(it3) }
if (it2 is AssignmentHolder) {
it2.assignments.any { assign ->
val prevMemberFrom = (from as? MemberExpression)?.prevDFG
val nextMemberTo = (assign.target as? MemberExpression)?.nextDFG
assign.target == from ||
prevMemberFrom != null &&
nextMemberTo != null &&
prevMemberFrom.any { it3 -> nextMemberTo.contains(it3) }
}
} else {
false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,26 +703,32 @@ class QueryTest {
val result = analyzer.analyze().get()

val queryTreeResult =
result.all<Assignment>(
{ it.target?.type?.isPrimitive == true },
result.all<AssignmentHolder>(
{ it.assignments.all { assign -> assign.target.type.isPrimitive } },
{
max(it.value) <= maxSizeOfType(it.target!!.type) &&
min(it.value) >= minSizeOfType(it.target!!.type)
it.assignments.any {
max(it.value) <= maxSizeOfType(it.target.type) &&
min(it.value) >= minSizeOfType(it.target.type)
}
}
)
assertFalse(queryTreeResult.first)

/*
TODO: This test will not work anymore because we cannot put it into a QueryTree
val queryTreeResult2 =
result.allExtended<Assignment>(
{ it.target?.type?.isPrimitive == true },
result.allExtended<AssignmentHolder>(
{ it.assignments.all { assign -> assign.target.type.isPrimitive } },
{
(max(it.value) le maxSizeOfType(it.target!!.type)) and
(min(it.value) ge minSizeOfType(it.target!!.type))
QueryTree(it.assignments.any {
(max(it.value) le maxSizeOfType(it.target!!.type)) and
(min(it.value) ge minSizeOfType(it.target!!.type))
})
}
)
println(queryTreeResult2.printNicely())
assertFalse(queryTreeResult2.value)
assertFalse(queryTreeResult2.value)*/
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,28 @@ interface ArgumentHolder : Holder<Expression> {
/** Adds the [expression] to the list of arguments. */
fun addArgument(expression: Expression)

/**
* Removes the [expression] from the list of arguments.
*
* An indication whether this operation was successful needs to be returned.
*/
fun removeArgument(expression: Expression): Boolean {
return false
}

/**
* Replaces the existing argument specified in [old] with the one in [new]. Implementation how
* to do that might be specific to the argument holder.
*
* An indication whether this operation was successful needs to be returned.
*/
fun replaceArgument(old: Expression, new: Expression): Boolean

override operator fun plusAssign(node: Expression) {
addArgument(node)
}

operator fun minusAssign(node: Expression) {
removeArgument(node)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public boolean isFirstOrderType() {
return this instanceof ObjectType
|| this instanceof UnknownType
|| this instanceof FunctionType
|| this instanceof TupleType
// TODO(oxisto): convert FunctionPointerType to second order type
|| this instanceof FunctionPointerType
|| this instanceof IncompleteType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,23 @@
*/
package de.fraunhofer.aisec.cpg.graph

import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator
import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression
import com.fasterxml.jackson.annotation.JsonIgnore
import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression

/** An assignment holder is a node that intentionally contains assignment edges. */
interface AssignmentHolder {
val assignments: List<Assignment>
}

/** An assignment assigns a certain value (usually an [Expression]) to a certain target. */
interface Assignment {
/**
* The target of this assignment. Note that this is intentionally nullable, because while
* [BinaryOperator] implements [Assignment], not all binary operations are assignments. Thus,
* the target is only non-null for operations that have a == operator.
*/
val target: AssignmentTarget?
class Assignment(
/** The value expression that is assigned to the target. */
val value: Expression,

/**
* The value expression that is assigned to the target. This is intentionally nullable for the
* same reason as [target].
*/
val value: Expression?
}
/** The target(s) of this assignment. */
val target: HasType,

/**
* The target of an assignment. The target is usually either a [VariableDeclaration] or a
* [DeclaredReferenceExpression].
*/
interface AssignmentTarget : HasType
/** The holder of this assignment */
@JsonIgnore val holder: AssignmentHolder
) : PropertyEdge<Node>(value, target as Node)
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,31 @@ fun MetadataProvider.newUnaryOperator(
return node
}

/**
* Creates a new [AssignExpression]. The [MetadataProvider] receiver will be used to fill different
* meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires
* an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended
* argument.
*/
@JvmOverloads
fun MetadataProvider.newAssignExpression(
operatorCode: String = "=",
lhs: List<Expression> = listOf(),
rhs: List<Expression> = listOf(),
code: String? = null,
rawNode: Any? = null
): AssignExpression {
val node = AssignExpression()
node.applyMetadata(this, operatorCode, rawNode, code, true)
node.operatorCode = operatorCode
node.lhs = lhs
node.rhs = rhs

log(node)

return node
}

/**
* Creates a new [NewExpression]. This is the top-most [Node] that a [LanguageFrontend] or [Handler]
* should create. The [MetadataProvider] receiver will be used to fill different meta-data using
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,10 @@ val Node?.functions: List<FunctionDeclaration>
val Node?.records: List<RecordDeclaration>
get() = this.allChildren()

/** Returns all [RecordDeclaration] children in this graph, starting with this [Node]. */
val Node?.namespaces: List<NamespaceDeclaration>
get() = this.allChildren()

/** Returns all [VariableDeclaration] children in this graph, starting with this [Node]. */
val Node?.variables: List<VariableDeclaration>
get() = this.allChildren()
Expand All @@ -538,6 +542,35 @@ val Node?.literals: List<Literal<*>>
val Node?.refs: List<DeclaredReferenceExpression>
get() = this.allChildren()

/** Returns all [Assignment] child edges in this graph, starting with this [Node]. */
val Node?.assignments: List<Assignment>
get() {
return this?.allChildren<Node>()?.filterIsInstance<AssignmentHolder>()?.flatMap {
it.assignments
}
?: listOf()
}

/**
* Returns the [Assignment.value] of the first (by EOG order beginning from) [Assignment] that this
* variable has as its [Assignment.target] in the scope of the variable.
*/
val VariableDeclaration.firstAssignment: Expression?
get() {
val start = this.scope?.astNode ?: return null
val assignments =
start.assignments.filter {
(it.target as? DeclaredReferenceExpression)?.refersTo == this
}

// We need to measure the distance between the start and each assignment value
return assignments
.map { Pair(it, start.eogDistanceTo(it.value)) }
.minByOrNull { it.second }
?.first
?.value
}

operator fun <N : Expression> Expression.invoke(): N? {
return this as? N
}
Expand Down Expand Up @@ -609,3 +642,17 @@ val ArraySubscriptionExpression.arraySize: Expression
(((this.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration)
.initializer as ArrayCreationExpression)
.dimensions[0]

/**
* This helper function calculates the "distance", i.e., number of EOG edges between this node and
* the node specified in [to].
*/
private fun Node.eogDistanceTo(to: Node): Int {
var i = 0
this.followNextEOG {
i++
it.end == to
}

return i
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,30 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
* Specifies that a certain node has an initializer. It is a special case of [ArgumentHolder], in
* which the initializer is treated as the first (and only) argument.
*/
interface HasInitializer : ArgumentHolder {
interface HasInitializer : HasType, ArgumentHolder, AssignmentHolder {

var initializer: Expression?

override fun addArgument(expression: Expression) {
this.initializer = expression
}

override fun removeArgument(expression: Expression): Boolean {
return if (this.initializer == expression) {
this.initializer = null
true
} else {
false
}
}

override fun replaceArgument(old: Expression, new: Expression): Boolean {
this.initializer = new
return true
}

override val assignments: List<Assignment>
get() {
return initializer?.let { listOf(Assignment(it, this, this)) } ?: listOf()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider
import org.slf4j.LoggerFactory

object NodeBuilder {
private val LOGGER = LoggerFactory.getLogger(NodeBuilder::class.java)
internal val LOGGER = LoggerFactory.getLogger(NodeBuilder::class.java)

fun log(node: Node?) {
LOGGER.trace("Creating {}", node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ fun LanguageFrontend.translationResult(
context(TranslationResult)

fun LanguageFrontend.translationUnit(
name: CharSequence,
name: CharSequence = Node.EMPTY_NAME,
init: TranslationUnitDeclaration.() -> Unit
): TranslationUnitDeclaration {
val node = (this@LanguageFrontend).newTranslationUnitDeclaration(name)
Expand Down Expand Up @@ -146,13 +146,19 @@ context(DeclarationHolder)
fun LanguageFrontend.function(
name: CharSequence,
returnType: Type = UnknownType.getUnknownType(),
init: FunctionDeclaration.() -> Unit
returnTypes: List<Type>? = null,
init: (FunctionDeclaration.() -> Unit)? = null
): FunctionDeclaration {
val node = newFunctionDeclaration(name)
node.returnTypes = listOf(returnType)

if (returnTypes != null) {
node.returnTypes = returnTypes
} else {
node.returnTypes = listOf(returnType)
}

scopeManager.enterScope(node)
init(node)
init?.let { it(node) }
scopeManager.leaveScope(node)

scopeManager.addDeclaration(node)
Expand Down Expand Up @@ -615,6 +621,12 @@ operator fun Expression.plus(rhs: Expression): BinaryOperator {

(this@ArgumentHolder) += node

// We need to do a little trick here. Because of the evaluation order, lhs and rhs might also
// been added to the argument holders arguments (and we do not want that). However, we cannot
// prevent it, so we need to remove them again
(this@ArgumentHolder) -= node.lhs
(this@ArgumentHolder) -= node.rhs

return node
}

Expand Down
Loading

0 comments on commit 9ba037f

Please sign in to comment.