From 4a40dc5bb32659f232c184d4c5a9b6498cf4ba09 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Fri, 15 Mar 2024 15:19:47 +0100 Subject: [PATCH 1/2] Introduce `simpleAssignmentOperators` in `Language` (#1464) Introduce simpleAssignmentOperators in Language --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 2 +- .../aisec/cpg/analysis/ValueEvaluator.kt | 10 +++++-- .../aisec/cpg/frontends/Language.kt | 3 ++ .../expressions/AssignExpression.kt | 25 +++++++++++++++- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 2 +- .../aisec/cpg/frontends/golang/GoLanguage.kt | 6 ++++ .../cpg/frontends/golang/StatementHandler.kt | 2 +- .../cpg/frontends/golang/ExpressionTest.kt | 27 +++++++++++++++++ .../golang/GoLanguageFrontendTest.kt | 29 +++++++++++++++---- .../src/test/resources/golang/eval.go | 20 +++++++++++++ .../src/test/resources/golang/short_assign.go | 7 +++++ 11 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 cpg-language-go/src/test/resources/golang/eval.go create mode 100644 cpg-language-go/src/test/resources/golang/short_assign.go diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 8c24411a4a3..56fb4c93483 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -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) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 84029948020..2904aa87ccd 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -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) @@ -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 diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt index 72df1e3573f..ec8e4f7c4a4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/Language.kt @@ -83,6 +83,9 @@ abstract class Language> : Node() { /** All operators which perform and assignment and an operation using lhs and rhs. */ abstract val compoundAssignmentOperators: Set + /** All operators which perform a simple assignment from the rhs to the lhs. */ + open val simpleAssignmentOperators: Set = setOf("=") + /** * Creates a new [LanguageFrontend] object to parse the language. It requires the * [TranslationContext], which holds the necessary managers. diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt index 0289ab686ce..686f7ccea4b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/AssignExpression.kt @@ -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 @@ -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." + ) + } } } @@ -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 diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 9a70abaee79..ad327dca375 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -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) */ diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt index 7039d991441..d473b735042 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguage.kt @@ -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 = diff --git a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt index 005dd6a8eec..dcc0154fac6 100644 --- a/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt +++ b/cpg-language-go/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/StatementHandler.kt @@ -71,7 +71,7 @@ class StatementHandler(frontend: GoLanguageFrontend) : if (assignStmt.tok == 47) { ":=" } else { - "" + "=" } return newAssignExpression(operatorCode, lhs, rhs, rawNode = assignStmt) diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt index a532155c0c5..ea4c94d8875 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/ExpressionTest.kt @@ -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") @@ -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() + } + 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()) + } } diff --git a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt index 8b71482647c..c2c98f54337 100644 --- a/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt +++ b/cpg-language-go/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/golang/GoLanguageFrontendTest.kt @@ -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 @@ -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.* @@ -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) @@ -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) @@ -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() + it.registerPass() + } + assertNotNull(tu) + + val f = tu.refs("f").lastOrNull() + assertNotNull(f) + + val values = f.evaluate(MultiValueEvaluator()) + assertEquals(setOf("GPT", "GTP"), values) + println(f.printDFG()) + } } diff --git a/cpg-language-go/src/test/resources/golang/eval.go b/cpg-language-go/src/test/resources/golang/eval.go new file mode 100644 index 00000000000..7cfbf0540c1 --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/eval.go @@ -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 +} diff --git a/cpg-language-go/src/test/resources/golang/short_assign.go b/cpg-language-go/src/test/resources/golang/short_assign.go new file mode 100644 index 00000000000..fcb4c5d11f9 --- /dev/null +++ b/cpg-language-go/src/test/resources/golang/short_assign.go @@ -0,0 +1,7 @@ +package p + +func assign() { + x := 5 + + _ = x +} From 6c0deda003c329cf15680b5d979817e4204197fb Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sun, 17 Mar 2024 22:12:28 +0100 Subject: [PATCH 2/2] Disabling TypeScript builds on JitPack (#1465) It seems that the version of Node we are using cannot be used in this old build environment of JitPack. --- jitpack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jitpack.yml b/jitpack.yml index 8f25a3cc8ca..194bac819bf 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -6,4 +6,4 @@ before_install: install: - export PATH="$PATH:$HOME/go/bin" - cp gradle.properties.example gradle.properties - - ./gradlew build -xtest -Pgroup=com.github.Fraunhofer-AISEC -PnodeDownload=true -PenableJavaFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=true publishToMavenLocal + - ./gradlew build -xtest -Pgroup=com.github.Fraunhofer-AISEC -PnodeDownload=true -PenableJavaFrontend=true -PenableGoFrontend=true -PenablePythonFrontend=true -PenableLLVMFrontend=true -PenableTypeScriptFrontend=false publishToMavenLocal