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

Introduce AssignExpression to support multiple assignments #1105

Merged
merged 1 commit into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -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? {
oxisto marked this conversation as resolved.
Show resolved Hide resolved
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 {
konradweiss marked this conversation as resolved.
Show resolved Hide resolved
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
}
oxisto marked this conversation as resolved.
Show resolved Hide resolved
}

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