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 2, 2023
1 parent bc2f253 commit c37c5ce
Show file tree
Hide file tree
Showing 25 changed files with 619 additions and 91 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 null
}

/**
* 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,22 @@ interface ArgumentHolder : Holder<Expression> {
/** Adds the [expression] to the list of arguments. */
fun addArgument(expression: Expression)

/** Removes the [expression] from the list of arguments. */
fun removeArgument(expression: Expression) {}

/**
* 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,19 @@
*/
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 de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge
import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression

/** 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?

/**
* The value expression that is assigned to the target. This is intentionally nullable for the
* same reason as [target].
*/
val value: Expression?
/** An assignment holder is a node that intentionally contains assignment edges. */
interface AssignmentHolder {
val assignments: List<Assignment>
}

/**
* The target of an assignment. The target is usually either a [VariableDeclaration] or a
* [DeclaredReferenceExpression].
*/
interface AssignmentTarget : HasType
/** An assignment assigns a certain value (usually an [Expression]) to a certain target. */
class Assignment(
/** The value expression that is assigned to the target. */
val value: Expression,

/** The target(s) of this assignment. */
val target: HasType
) : 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 @@ -538,6 +538,15 @@ 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()
}

operator fun <N : Expression> Expression.invoke(): N? {
return this as? N
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,15 @@ interface HasInitializer : ArgumentHolder {
override fun addArgument(expression: Expression) {
this.initializer = expression
}

override fun removeArgument(expression: Expression) {
if (this.initializer == expression) {
this.initializer = null
}
}

override fun replaceArgument(old: Expression, new: Expression): Boolean {
this.initializer = new
return true
}
}
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 @@ -131,6 +131,8 @@ fun LanguageFrontend.field(

scopeManager.addDeclaration(node)

this@TranslationResult.components.firstOrNull()?.translationUnits?.add(node)

return node
}

Expand All @@ -144,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 @@ -613,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
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@ import org.neo4j.ogm.annotation.Relationship

/** Represents the declaration of a variable. */
class VariableDeclaration :
ValueDeclaration(), HasType.TypeListener, HasInitializer, Assignment, AssignmentTarget {
override val value: Expression?
get() {
return initializer
}
ValueDeclaration(), HasType.TypeListener, HasInitializer, AssignmentHolder {

/**
* We need a way to store the templateParameters that a VariableDeclaration might have before
Expand Down Expand Up @@ -94,13 +90,12 @@ class VariableDeclaration :
}
val previous = type
val newType =
if (src === value && value is InitializerListExpression) {
if (src === initializer && initializer is InitializerListExpression) {
// Init list is seen as having an array type, but can be used ambiguously. It can be
// either
// used to initialize an array, or to initialize some objects. If it is used as an
// either used to initialize an array, or to initialize some objects. If it is used
// as an
// array initializer, we need to remove the array/pointer layer from the type,
// otherwise it
// can be ignored once we have a type
// otherwise it can be ignored once we have a type
if (isArray) {
src.type
} else if (!TypeManager.getInstance().isUnknown(type)) {
Expand Down Expand Up @@ -130,23 +125,25 @@ class VariableDeclaration :
return ToStringBuilder(this, TO_STRING_STYLE)
.append("name", name)
.append("location", location)
.append("initializer", value)
.append("initializer", initializer)
.toString()
}

override val assignments: List<Assignment>
get() {
return initializer?.let { listOf(Assignment(it, this)) } ?: listOf()
}

override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
return if (other !is VariableDeclaration) {
false
} else super.equals(other) && value == other.value
} else super.equals(other) && initializer == other.initializer
}

override fun hashCode(): Int {
return super.hashCode()
}

override val target: AssignmentTarget
get() = this
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ class IfStatement : Statement(), ArgumentHolder {
this.condition = expression
}

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

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is IfStatement) return false
Expand Down
Loading

0 comments on commit c37c5ce

Please sign in to comment.