Skip to content

Commit

Permalink
Introduce simpleAssignmentOperators in Language (#1464)
Browse files Browse the repository at this point in the history
Introduce simpleAssignmentOperators in Language
  • Loading branch information
oxisto authored Mar 15, 2024
1 parent 6489132 commit 4a40dc5
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class MultiValueEvaluator : ValueEvaluator() {
return evaluateInternal(node.initializer, depth + 1)
}
is NewArrayExpression -> return evaluateInternal(node.initializer, depth + 1)
is VariableDeclaration -> return evaluateInternal(node.initializer, depth + 1)
is VariableDeclaration -> return handleVariableDeclaration(node, depth)
// For a literal, we can just take its value, and we are finished
is Literal<*> -> return node.value
is Reference -> return handleReference(node, depth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ open class ValueEvaluator(
node?.let { this.path += it }

when (node) {
is NewArrayExpression -> return evaluateInternal(node.initializer, depth + 1)
is VariableDeclaration -> return evaluateInternal(node.initializer, depth + 1)
is NewArrayExpression -> return evaluateInternal(node.initializer, depth)
is VariableDeclaration -> return handleVariableDeclaration(node, depth)
// For a literal, we can just take its value, and we are finished
is Literal<*> -> return node.value
is Reference -> return handleReference(node, depth)
Expand All @@ -108,6 +108,12 @@ open class ValueEvaluator(
return cannotEvaluate(node, this)
}

protected fun handleVariableDeclaration(node: VariableDeclaration, depth: Int): Any? {
// If we have an initializer, we can use it. However, we actually should just use the DFG
// instead and do something similar to handleReference
return evaluateInternal(node.initializer, depth + 1)
}

/** Under certain circumstances, an assignment can also be used as an expression. */
protected open fun handleAssignExpression(node: AssignExpression, depth: Int): Any? {
// Handle compound assignments. Only possible with single values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ abstract class Language<T : LanguageFrontend<*, *>> : Node() {
/** All operators which perform and assignment and an operation using lhs and rhs. */
abstract val compoundAssignmentOperators: Set<String>

/** All operators which perform a simple assignment from the rhs to the lhs. */
open val simpleAssignmentOperators: Set<String> = setOf("=")

/**
* Creates a new [LanguageFrontend] object to parse the language. It requires the
* [TranslationContext], which holds the necessary managers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
*/
package de.fraunhofer.aisec.cpg.graph.statements.expressions

import de.fraunhofer.aisec.cpg.frontends.Language
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration
import de.fraunhofer.aisec.cpg.graph.types.HasType
import de.fraunhofer.aisec.cpg.graph.types.TupleType
import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.helpers.Util
import org.slf4j.Logger
import org.slf4j.LoggerFactory

Expand Down Expand Up @@ -66,10 +68,21 @@ class AssignExpression :
base = (base as? MemberExpression)?.base as? MemberExpression
}
}
if (operatorCode == "=") {
if (isSimpleAssignment) {
field.forEach { (it as? Reference)?.access = AccessValues.WRITE }
} else {
field.forEach { (it as? Reference)?.access = AccessValues.READWRITE }

if (!isCompoundAssignment) {
// If this is neither a simple nor a compound assignment, probably something
// went wrong, we still model this as a READWRITE, but we indicate a warning to
// the user
Util.warnWithFileLocation(
this,
log,
"Assignment is neither a simple nor a compound assignment. This is suspicious."
)
}
}
}

Expand Down Expand Up @@ -112,6 +125,16 @@ class AssignExpression :
isSingleValue
}

/**
* Returns true, if this assignment is a "simple" assignment, meaning that the value is directly
* assigned, without any additional complex data-flow, such as compound assignments. This
* compares the [operatorCode] with [Language.simpleAssignmentOperators].
*/
val isSimpleAssignment: Boolean
get() {
return operatorCode in (language?.simpleAssignmentOperators ?: setOf())
}

/**
* Some languages, such as Go explicitly allow the definition / declaration of variables in the
* assignment (known as a "short assignment"). Some languages, such as Python even implicitly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : EOGStarterPass

protected fun isSimpleAssignment(currentNode: Node): Boolean {
contract { returns(true) implies (currentNode is AssignExpression) }
return currentNode is AssignExpression && currentNode.operatorCode == "="
return currentNode is AssignExpression && currentNode.isSimpleAssignment
}

/** Checks if the node is an increment or decrement operator (e.g. i++, i--, ++i, --i) */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ class GoLanguage :
override val compoundAssignmentOperators =
setOf("+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&^=", "&=", "|=", "^=")

/**
* Go supports the normal `=` operator, as well as a short assignment operator, which also
* declares the variable under certain circumstances. But both act as a simple assignment.
*/
override val simpleAssignmentOperators = setOf("=", ":=")

/** See [Documentation](https://pkg.go.dev/builtin). */
@Transient
override val builtInTypes =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class StatementHandler(frontend: GoLanguageFrontend) :
if (assignStmt.tok == 47) {
":="
} else {
""
"="
}

return newAssignExpression(operatorCode, lhs, rhs, rawNode = assignStmt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import java.nio.file.Path
import kotlin.test.*

class ExpressionTest {

@Test
fun testCastExpression() {
val topLevel = Path.of("src", "test", "resources", "golang")
Expand Down Expand Up @@ -173,4 +174,30 @@ class ExpressionTest {
assertRefersTo(unaryOp.input, ch)
}
}

@Test
fun testShortAssign() {
val topLevel = Path.of("src", "test", "resources", "golang")
val tu =
TestUtils.analyzeAndGetFirstTU(
listOf(topLevel.resolve("short_assign.go").toFile()),
topLevel,
true
) {
it.registerLanguage<GoLanguage>()
}
assertNotNull(tu)

val lit5 = tu.literals.firstOrNull()
assertNotNull(lit5)
println(lit5.printDFG())

val x = tu.refs("x").lastOrNull()
assertNotNull(x)

val paths = x.followPrevDFGEdgesUntilHit { it == lit5 }
assertEquals(3, paths.fulfilled.firstOrNull()?.size)

assertEquals(5, x.evaluate())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@
*/
package de.fraunhofer.aisec.cpg.frontends.golang

import de.fraunhofer.aisec.cpg.BaseTest
import de.fraunhofer.aisec.cpg.*
import de.fraunhofer.aisec.cpg.TestUtils.analyze
import de.fraunhofer.aisec.cpg.TestUtils.analyzeAndGetFirstTU
import de.fraunhofer.aisec.cpg.TestUtils.assertInvokes
import de.fraunhofer.aisec.cpg.TestUtils.assertRefersTo
import de.fraunhofer.aisec.cpg.assertFullName
import de.fraunhofer.aisec.cpg.assertLiteralValue
import de.fraunhofer.aisec.cpg.assertLocalName
import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
import de.fraunhofer.aisec.cpg.graph.declarations.NamespaceDeclaration
Expand All @@ -44,6 +42,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.types.FunctionType
import de.fraunhofer.aisec.cpg.graph.types.ObjectType
import de.fraunhofer.aisec.cpg.graph.types.PointerType
import de.fraunhofer.aisec.cpg.passes.EdgeCachePass
import java.io.File
import java.nio.file.Path
import kotlin.test.*
Expand Down Expand Up @@ -149,7 +148,7 @@ class GoLanguageFrontendTest : BaseTest() {

assertTrue(make is NewArrayExpression)

val dimension = make.dimensions.first() as? Literal<*>
val dimension = make.dimensions.firstOrNull() as? Literal<*>
assertNotNull(dimension)
assertEquals(5, dimension.value)

Expand Down Expand Up @@ -382,7 +381,7 @@ class GoLanguageFrontendTest : BaseTest() {
assertFullName("fmt.Printf", callExpression)
assertLocalName("Printf", callExpression)

val literal = callExpression.arguments.first() as? Literal<*>
val literal = callExpression.arguments.firstOrNull() as? Literal<*>
assertNotNull(literal)

assertEquals("%s", literal.value)
Expand Down Expand Up @@ -1171,4 +1170,22 @@ class GoLanguageFrontendTest : BaseTest() {
}
assertNotNull(result)
}

@Test
fun testMultiValueEvaluate() {
val topLevel = Path.of("src", "test", "resources", "golang")
val tu =
analyzeAndGetFirstTU(listOf(topLevel.resolve("eval.go").toFile()), topLevel, true) {
it.registerLanguage<GoLanguage>()
it.registerPass<EdgeCachePass>()
}
assertNotNull(tu)

val f = tu.refs("f").lastOrNull()
assertNotNull(f)

val values = f.evaluate(MultiValueEvaluator())
assertEquals(setOf("GPT", "GTP"), values)
println(f.printDFG())
}
}
20 changes: 20 additions & 0 deletions cpg-language-go/src/test/resources/golang/eval.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"os"
)

func main() {
x := "G"
y := "T"
z := "P"

var f string
if len(os.Args) == 2 {
f = x + y + z
} else {
f = x + z + y
}

_ = f
}
7 changes: 7 additions & 0 deletions cpg-language-go/src/test/resources/golang/short_assign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package p

func assign() {
x := 5

_ = x
}

0 comments on commit 4a40dc5

Please sign in to comment.