From 31acd51ac4d114d01c9a4ac6cac516be3c59c2e6 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 17 Jun 2024 11:52:55 +0200 Subject: [PATCH 01/19] introduce the Length Type to check the length of an argument --- .../cpg/coko/dsl/ImplementationDsl.kt | 61 +++++++++++++++---- .../coko/core/dsl/Types.kt | 3 + 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 9de960be9..149690afb 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -24,10 +24,13 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.query.dataFlow import de.fraunhofer.aisec.cpg.query.executionPath +import de.fraunhofer.aisec.cpg.query.max +import de.fraunhofer.aisec.cpg.query.min +import de.fraunhofer.aisec.cpg.query.sizeof // // all functions/properties defined here must use CokoBackend @@ -160,21 +163,53 @@ context(CallExpression) * - If this is a [Node], we use the DFG of the CPG. */ infix fun Any.cpgFlowsTo(that: Collection): Boolean = - if (this is Wildcard) { - true - } else { - when (this) { - is String -> that.any { - val regex = Regex(this) - regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) + when (this) { + is Wildcard -> true + is String -> that.any { + val regex = Regex(this) + regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) + } + // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges + is LongRange, is IntRange -> checkRange(that) + is Iterable<*> -> this.any { it?.cpgFlowsTo(that) ?: false } + is Array<*> -> this.any { it?.cpgFlowsTo(that) ?: false } + is Node -> that.any { dataFlow(this, it).value } + is ParameterGroup -> this.parameters.all { it?.cpgFlowsTo(that) ?: false } + is Length -> checkLength(that) + else -> this in that.map { (it as Expression).evaluate() } + } + +private fun Any.checkRange(that: Collection): Boolean { + when (this) { + // I would love to combine the following two cases, but any implementation loses the benefit of + // quickly reading the last value of the range, therefore making the whole distinction useless. + is IntRange -> { + return that.all { + val minValue = min(it).value.toInt() + val maxValue = max(it).value.toInt() + minValue > this.first && maxValue < this.last + } + } + is LongRange -> { + return that.all { + val minValue = min(it).value.toInt() + val maxValue = max(it).value.toInt() + minValue > this.first && maxValue < this.last } - is Iterable<*> -> this.any { it?.cpgFlowsTo(that) ?: false } - is Array<*> -> this.any { it?.cpgFlowsTo(that) ?: false } - is Node -> that.any { dataFlow(this, it).value } - is ParameterGroup -> this.parameters.all { it?.cpgFlowsTo(that) ?: false } - else -> this in that.map { (it as Expression).evaluate() } } + else -> throw IllegalArgumentException("Unexpected type") } +} + +private fun Length.checkLength(that: Collection): Boolean { + return that.all { + val size = sizeof(it).value + if (size == -1) { + // TODO: handle case where size could not be determined -> OPEN Finding + } + size in this.value + } +} context(CokoBackend) // TODO: better description diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt index c0363f54e..28c097db4 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt @@ -29,4 +29,7 @@ data class Type(val fqn: String) data class ParamWithType(val param: Any, val type: Type) +/** Marks that we want to check the length of the argument, not the contents */ +data class Length(val value: IntRange) + infix fun Any.withType(fqn: String) = ParamWithType(this, Type(fqn)) From d81132e9d6e0e40f56c4416b2ff46d55e4fcba14 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 24 Jun 2024 10:37:29 +0200 Subject: [PATCH 02/19] Add Length related tests --- .../{ => coko/dsl}/ImplementationDslTest.kt | 85 ++++++++++++++++--- .../evaluators}/FollowsEvaluationTest.kt | 4 +- .../evaluators}/NeverEvaluationTest.kt | 4 +- .../evaluators}/OnlyEvaluationTest.kt | 4 +- .../evaluators}/OrderEvaluationTest.kt | 3 +- .../ordering}/NfaDfaConstructionTest.kt | 5 +- .../ImplementationDslTest/LengthJavaFile.java | 42 +++++++++ 7 files changed, 126 insertions(+), 21 deletions(-) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/dsl}/ImplementationDslTest.kt (50%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/evaluators}/FollowsEvaluationTest.kt (96%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/evaluators}/NeverEvaluationTest.kt (95%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/evaluators}/OnlyEvaluationTest.kt (93%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/evaluators}/OrderEvaluationTest.kt (98%) rename codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/{ => coko/ordering}/NfaDfaConstructionTest.kt (99%) create mode 100644 codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt similarity index 50% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ImplementationDslTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index 0dece26f6..a16b6d5a9 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -13,14 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetAllNodes -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Wildcard -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.op -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.signature +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import kotlin.io.path.toPath @@ -36,7 +33,7 @@ class ImplementationDslTest { signature(2) } } - with(backend) { + with(simpleBackend) { val allNodes = op.cpgGetAllNodes() assertEquals( 5, @@ -53,7 +50,7 @@ class ImplementationDslTest { signature(1..10) } } - with(backend) { + with(simpleBackend) { val nodes = op.cpgGetNodes() assertEquals( 2, @@ -73,7 +70,7 @@ class ImplementationDslTest { } } } - with(backend) { + with(simpleBackend) { val nodes = op.cpgGetNodes() assertEquals( 3, @@ -83,19 +80,79 @@ class ImplementationDslTest { } } + @Test + fun `test Array Length`() { + val opX: MutableList = mutableListOf() + for (i in 0..3) { + opX += op { + "Foo.fun" { + signature( + Length(i..i) + ) + } + } + } + + val results = arrayOf(1, 0, 1, 2) + for (i in 0..3) { + with(lengthBackend) { + val nodes = opX[i].cpgGetNodes() + assertEquals( + results[i], + nodes.size, + "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + ) + } + } + } + + @Test + fun `test List Length`() { + val opX: MutableList = mutableListOf() + for (i in 0..3) { + opX += op { + "Foo.bar" { + signature( + Length(i..i) + ) + } + } + } + + val results = arrayOf(1, 0, 1, 2) + for (i in 0..3) { + with(lengthBackend) { + val nodes = opX[i].cpgGetNodes() + assertEquals( + results[i], + nodes.size, + "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + ) + } + } + } + companion object { - lateinit var backend: CokoCpgBackend + lateinit var simpleBackend: CokoCpgBackend + lateinit var lengthBackend: CokoCpgBackend @BeforeAll @JvmStatic fun startup() { val classLoader = ImplementationDslTest::class.java.classLoader - val testFileResource = classLoader.getResource("ImplementationDslTest/SimpleJavaFile.java") - assertNotNull(testFileResource) - val testFile = testFileResource.toURI().toPath() - backend = CokoCpgBackend(config = createCpgConfiguration(testFile)) + val simpleFileResource = classLoader.getResource("ImplementationDslTest/SimpleJavaFile.java") + val lengthFileResource = classLoader.getResource("ImplementationDslTest/LengthJavaFile.java") + + assertNotNull(simpleFileResource) + assertNotNull(lengthFileResource) + + val simpleFile = simpleFileResource.toURI().toPath() + val lengthFile = lengthFileResource.toURI().toPath() + + simpleBackend = CokoCpgBackend(config = createCpgConfiguration(simpleFile)) + lengthBackend = CokoCpgBackend(config = createCpgConfiguration(lengthFile)) } } } diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/FollowsEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt similarity index 96% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/FollowsEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt index 231f0b736..a0f43063f 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/FollowsEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NeverEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt similarity index 95% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NeverEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt index 615c531df..5068a2333 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NeverEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OnlyEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt similarity index 93% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OnlyEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt index 5b99e2a31..7d4d3e394 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OnlyEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OrderEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt similarity index 98% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OrderEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt index e7f8fa616..741230829 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OrderEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NfaDfaConstructionTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt similarity index 99% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NfaDfaConstructionTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt index fabb881a0..b8baf492f 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NfaDfaConstructionTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering.toNfa import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.* import de.fraunhofer.aisec.cpg.analysis.fsm.DFA @@ -48,7 +47,7 @@ class NfaDfaConstructionTest { } private val baseName = - "de.fraunhofer.aisec.codyze.backends.cpg.NfaDfaConstructionTest\$TestClass" + "de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering.NfaDfaConstructionTest\$TestClass" private fun orderExpressionToNfa(block: Order.() -> Unit): NFA { val order = Order().apply(block) diff --git a/codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java b/codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java new file mode 100644 index 000000000..53b1ef7f4 --- /dev/null +++ b/codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java @@ -0,0 +1,42 @@ +import java.util.List; +import java.lang.String; + +public class Length { + + public void one(int[] e) { + Foo f = new Foo(); + + int[] a = {1, 2, 3}; + int[] b = { }; + String[] c = {"a", "b", "c"}; + String[] d = {"ab", "c"}; + + f.fun(a); + f.fun(b); + f.fun(c); + f.fun(d); + f.fun(e); + } + + public void two(List e) { + Foo f = new Foo(); + + List a = List.of(1, 2, 3); + List b = List.of(); + List c = List.of("a", "b", "c"); + List c = List.of("ab", "c"); + + f.bar(a); + f.bar(b); + f.bar(c); + f.bar(d); + f.bar(e); + } +} + + +public class Foo { + public void fun(int[] a) {} + + public void bar(List l) {} +} From bb82ebdfbf87035d46646e6a73fb435a74fa7104 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 24 Jun 2024 12:52:16 +0200 Subject: [PATCH 03/19] rewrite cpgGetNodes to return the associated Result with the Node --- .../cpg/coko/dsl/ImplementationDsl.kt | 122 ++++++++++++------ 1 file changed, 86 insertions(+), 36 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 149690afb..0f1186973 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -18,6 +18,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* @@ -39,6 +40,40 @@ import de.fraunhofer.aisec.cpg.query.sizeof val CokoBackend.cpg: TranslationResult get() = this.backendData as TranslationResult +enum class Result { + VALID, + INVALID, + OPEN; + + companion object { + fun convert(from: Any?): Result { + return when(from) { + is Result -> from + is Boolean -> if (from) VALID else INVALID + else -> OPEN + } + } + } +} + +inline fun Iterable.allResult(predicate: (T) -> Result?): Result { + for (element in this) { + if (predicate(element) == OPEN) return OPEN + else if (predicate(element) == INVALID) return INVALID + } + return VALID +} + +inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { + var openFlag = false + for (element in this) { + if (predicate(element) == VALID) return VALID + else if (predicate(element) == OPEN) openFlag = true + } + return if (openFlag) OPEN else INVALID +} + + /** Get all [Nodes] that are associated with this [Op]. */ context(CokoBackend) fun Op.cpgGetAllNodes(): Collection = @@ -63,26 +98,35 @@ fun Op.cpgGetAllNodes(): Collection = * [Definition]s. */ context(CokoBackend) -fun Op.cpgGetNodes(): Collection = +fun Op.cpgGetNodes(): Collection> = when (this@Op) { - is FunctionOp -> - this@Op.definitions - .flatMap { def -> - this@CokoBackend.cpgCallFqn(def.fqn) { - def.signatures.any { sig -> - cpgSignature(*sig.parameters.toTypedArray()) && - sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } - } + is FunctionOp -> { + val results = mutableListOf() + val fqn = this@Op.definitions.flatMap { + def -> + this@CokoBackend.cpgCallFqn(def.fqn) { + def.signatures.any { + sig -> + val result = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(result) + cpgSignature(*sig.parameters.toTypedArray()) && result != INVALID } } - is ConstructorOp -> - this@Op.signatures - .flatMap { sig -> + } + fqn.zip(results) + } + is ConstructorOp -> { + val results = mutableListOf() + val fqn = this@Op.signatures.flatMap { + sig -> this@CokoBackend.cpgConstructor(this@Op.classFqn) { - cpgSignature(*sig.parameters.toTypedArray()) && - sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } + val result = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(result) + cpgSignature(*sig.parameters.toTypedArray()) && result != INVALID } - } + } + fqn.zip(results) + } is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes() } is ConditionalOp -> { val resultNodes = resultOp.cpgGetNodes() @@ -90,7 +134,7 @@ fun Op.cpgGetNodes(): Collection = resultNodes.filter { resultNode -> conditionNodes.any { conditionNode -> // TODO: Is it correct to use the EOG relationship here? - val result = executionPath(conditionNode, resultNode) + val result = executionPath(conditionNode.first, resultNode.first) result.value } } @@ -151,7 +195,7 @@ context(CallExpression) * - If this is a Collection, we check if at least one of the elements flows to [that] * - If this is a [Node], we use the DFG of the CPG. */ -infix fun Any.cpgFlowsTo(that: Node): Boolean = +infix fun Any.cpgFlowsTo(that: Node): Result = this.cpgFlowsTo(listOf(that)) // it should only be available in the context of a CallExpression @@ -162,22 +206,27 @@ context(CallExpression) * - If this is a Collection, we check if at least one of the elements flows to [that] * - If this is a [Node], we use the DFG of the CPG. */ -infix fun Any.cpgFlowsTo(that: Collection): Boolean = - when (this) { - is Wildcard -> true - is String -> that.any { - val regex = Regex(this) - regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) +infix fun Any.cpgFlowsTo(that: Collection): Result = + Result.convert( + when (this) { + is Wildcard -> true + is String -> that.any { + val regex = Regex(this) + regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) + } + // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges + is LongRange, is IntRange -> checkRange(that) + is Iterable<*> -> this.any { it?.cpgFlowsTo(that) == VALID } + is Array<*> -> this.any { it?.cpgFlowsTo(that) == VALID } + is Node -> that.any { dataFlow(this, it).value } + is ParameterGroup -> this.parameters.all { + val flow = it?.cpgFlowsTo(that) + if (flow == OPEN) return OPEN else it?.cpgFlowsTo(that) == VALID + } + is Length -> checkLength(that) + else -> this in that.map { (it as Expression).evaluate() } } - // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges - is LongRange, is IntRange -> checkRange(that) - is Iterable<*> -> this.any { it?.cpgFlowsTo(that) ?: false } - is Array<*> -> this.any { it?.cpgFlowsTo(that) ?: false } - is Node -> that.any { dataFlow(this, it).value } - is ParameterGroup -> this.parameters.all { it?.cpgFlowsTo(that) ?: false } - is Length -> checkLength(that) - else -> this in that.map { (it as Expression).evaluate() } - } + ) private fun Any.checkRange(that: Collection): Boolean { when (this) { @@ -201,14 +250,15 @@ private fun Any.checkRange(that: Collection): Boolean { } } -private fun Length.checkLength(that: Collection): Boolean { - return that.all { +private fun Length.checkLength(that: Collection): Result { + return Result.convert(that.all { val size = sizeof(it).value if (size == -1) { - // TODO: handle case where size could not be determined -> OPEN Finding + // Handle case where size could not be determined -> OPEN Finding + return Result.OPEN } size in this.value - } + }) } context(CokoBackend) From 46b86ea2bf67a7e580229b18bd77b4a346310e29 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 26 Jun 2024 10:18:29 +0200 Subject: [PATCH 04/19] move result to own file and rewrite cpgSignature to use results --- .../cpg/coko/dsl/ImplementationDsl.kt | 80 ++++++------------- .../codyze/backends/cpg/coko/dsl/Result.kt | 67 ++++++++++++++++ 2 files changed, 91 insertions(+), 56 deletions(-) create mode 100644 codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 0f1186973..7426c938f 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -18,7 +18,6 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* @@ -32,6 +31,7 @@ import de.fraunhofer.aisec.cpg.query.executionPath import de.fraunhofer.aisec.cpg.query.max import de.fraunhofer.aisec.cpg.query.min import de.fraunhofer.aisec.cpg.query.sizeof +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* // // all functions/properties defined here must use CokoBackend @@ -40,39 +40,6 @@ import de.fraunhofer.aisec.cpg.query.sizeof val CokoBackend.cpg: TranslationResult get() = this.backendData as TranslationResult -enum class Result { - VALID, - INVALID, - OPEN; - - companion object { - fun convert(from: Any?): Result { - return when(from) { - is Result -> from - is Boolean -> if (from) VALID else INVALID - else -> OPEN - } - } - } -} - -inline fun Iterable.allResult(predicate: (T) -> Result?): Result { - for (element in this) { - if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID) return INVALID - } - return VALID -} - -inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { - var openFlag = false - for (element in this) { - if (predicate(element) == VALID) return VALID - else if (predicate(element) == OPEN) openFlag = true - } - return if (openFlag) OPEN else INVALID -} - /** Get all [Nodes] that are associated with this [Op]. */ context(CokoBackend) @@ -107,9 +74,12 @@ fun Op.cpgGetNodes(): Collection> = this@CokoBackend.cpgCallFqn(def.fqn) { def.signatures.any { sig -> - val result = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(result) - cpgSignature(*sig.parameters.toTypedArray()) && result != INVALID + // We consider a result, when both the signature and the flow are not invalid + // However, if at least one of them is OPEN, we propagate this information to the caller + val signature = cpgSignature(*sig.parameters.toTypedArray()) + val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(signature.and(flow)) + signature != INVALID && flow != INVALID } } } @@ -120,9 +90,10 @@ fun Op.cpgGetNodes(): Collection> = val fqn = this@Op.signatures.flatMap { sig -> this@CokoBackend.cpgConstructor(this@Op.classFqn) { - val result = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(result) - cpgSignature(*sig.parameters.toTypedArray()) && result != INVALID + val signature = cpgSignature(*sig.parameters.toTypedArray()) + val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(signature.and(flow)) + signature != INVALID && flow != INVALID } } fqn.zip(results) @@ -216,13 +187,10 @@ infix fun Any.cpgFlowsTo(that: Collection): Result = } // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges is LongRange, is IntRange -> checkRange(that) - is Iterable<*> -> this.any { it?.cpgFlowsTo(that) == VALID } - is Array<*> -> this.any { it?.cpgFlowsTo(that) == VALID } + is Iterable<*> -> this.anyResult { it?.cpgFlowsTo(that) } + is Array<*> -> this.anyResult { it?.cpgFlowsTo(that) } is Node -> that.any { dataFlow(this, it).value } - is ParameterGroup -> this.parameters.all { - val flow = it?.cpgFlowsTo(that) - if (flow == OPEN) return OPEN else it?.cpgFlowsTo(that) == VALID - } + is ParameterGroup -> this.parameters.allResult { it?.cpgFlowsTo(that) } is Length -> checkLength(that) else -> this in that.map { (it as Expression).evaluate() } } @@ -280,29 +248,29 @@ context(CokoBackend) * are not important to the analysis */ @Suppress("UnsafeCallOnNullableType") -fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Boolean { +fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Result { // checks if amount of parameters is the same as amount of arguments of this CallExpression - return cpgCheckArgsSize(parameters, hasVarargs) && + return cpgCheckArgsSize(parameters, hasVarargs).and( // checks if the CallExpression matches with the parameters - parameters.withIndex().all { (i: Int, parameter: Any?) -> + parameters.withIndex().allResult { (i: Int, parameter: Any?) -> when (parameter) { // if any parameter is null, signature returns false - null -> false + null -> INVALID is ParamWithType -> // if `parameter` is a `ParamWithType` object we want to check the type and // if there is dataflow - cpgCheckType(parameter.type, i) && - parameter.param cpgFlowsTo arguments[i] + cpgCheckType(parameter.type, i).and(parameter.param cpgFlowsTo arguments[i]) // checks if the type of the argument is the same - is Type -> cpgCheckType(parameter, i) + is Type -> Result.convert(cpgCheckType(parameter, i)) // check if any of the Nodes of the Op flow to the argument - is Op -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] + is Op -> Result.convert(parameter.cpgGetNodes() cpgFlowsTo arguments[i]) // check if any of the Nodes of the DataItem flow to the argument - is DataItem<*> -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] + is DataItem<*> -> Result.convert(parameter.cpgGetNodes() cpgFlowsTo arguments[i]) // checks if there is dataflow from the parameter to the argument in the same position - else -> parameter cpgFlowsTo arguments[i] + else -> Result.convert(parameter cpgFlowsTo arguments[i]) } } + ) } /** Checks the [type] against the type of the argument at [index] for the Call Expression */ diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt new file mode 100644 index 000000000..75d8dd260 --- /dev/null +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -0,0 +1,67 @@ +package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl + +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* + +enum class Result { + VALID, + INVALID, + OPEN; + + companion object { + fun convert(from: Any?): Result { + return when(from) { + is Result -> from + is Boolean -> if (from) VALID else INVALID + else -> OPEN + } + } + } +} + +inline fun Iterable.allResult(predicate: (T) -> Result?): Result { + for (element in this) { + if (predicate(element) == OPEN) return OPEN + else if (predicate(element) == INVALID) return INVALID + } + return VALID +} + +inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { + var openFlag = false + for (element in this) { + if (predicate(element) == VALID) return VALID + else if (predicate(element) == OPEN) openFlag = true + } + return if (openFlag) OPEN else INVALID +} + +inline fun Array.allResult(predicate: (T) -> Result?): Result { + for (element in this) { + if (predicate(element) == OPEN) return OPEN + else if (predicate(element) == INVALID) return INVALID + } + return VALID +} + +inline fun Array.anyResult(predicate: (T) -> Result?): Result { + var openFlag = false + for (element in this) { + if (predicate(element) == VALID) return VALID + else if (predicate(element) == OPEN) openFlag = true + } + return if (openFlag) OPEN else INVALID +} + +fun Result.and(other: Result): Result { + return if (this == OPEN || other == OPEN) OPEN + else if (this == INVALID || other == INVALID) INVALID + else VALID +} + +fun Boolean.and(other: Result): Result { + return Companion.convert(this).and(other) +} + +fun Result.and(other: Boolean): Result { + return this.and(Companion.convert(other)) +} \ No newline at end of file From 1cb9af503ccf6238225eb5f8af48e725bf33d7d3 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 11:48:46 +0200 Subject: [PATCH 05/19] have cpgGetNodes return a Map and adapt the Evaluators --- .../cpg/coko/dsl/ImplementationDsl.kt | 10 ++++----- .../coko/evaluators/CpgWheneverEvaluator.kt | 2 +- .../cpg/coko/evaluators/FollowsEvaluator.kt | 4 ++-- .../cpg/coko/evaluators/NeverEvaluator.kt | 14 ++++++++++-- .../cpg/coko/evaluators/OnlyEvaluator.kt | 22 ++++++++++++++----- .../cpg/coko/evaluators/OrderEvaluator.kt | 2 +- 6 files changed, 38 insertions(+), 16 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 7426c938f..9e45a6358 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -65,7 +65,7 @@ fun Op.cpgGetAllNodes(): Collection = * [Definition]s. */ context(CokoBackend) -fun Op.cpgGetNodes(): Collection> = +fun Op.cpgGetNodes(): Map = when (this@Op) { is FunctionOp -> { val results = mutableListOf() @@ -83,7 +83,7 @@ fun Op.cpgGetNodes(): Collection> = } } } - fqn.zip(results) + fqn.zip(results).toMap() } is ConstructorOp -> { val results = mutableListOf() @@ -96,16 +96,16 @@ fun Op.cpgGetNodes(): Collection> = signature != INVALID && flow != INVALID } } - fqn.zip(results) + fqn.zip(results).toMap() } - is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes() } + is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } is ConditionalOp -> { val resultNodes = resultOp.cpgGetNodes() val conditionNodes = conditionOp.cpgGetNodes() resultNodes.filter { resultNode -> conditionNodes.any { conditionNode -> // TODO: Is it correct to use the EOG relationship here? - val result = executionPath(conditionNode.first, resultNode.first) + val result = executionPath(conditionNode.key, resultNode.key) result.value } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt index c6595faa0..088e5eb1e 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt @@ -331,7 +331,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem callConditionComponent: CallConditionComponent, premiseNode: Node? = null ): EvaluationResult { - val callNodes = callConditionComponent.op.cpgGetNodes().filterWithDistanceToPremise(premiseNode) + val callNodes = callConditionComponent.op.cpgGetNodes().keys.filterWithDistanceToPremise(premiseNode) return EvaluationResult(callNodes, emptyList(), Problems()) } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt index 898ef1e45..659c49725 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt @@ -38,10 +38,10 @@ class FollowsEvaluator(val ifOp: Op, val thenOp: Op) : Evaluator { override fun evaluate(context: EvaluationContext): List { val (unreachableThisNodes, thisNodes) = - with(this@CokoCpgBackend) { ifOp.cpgGetNodes().toSet() } + with(this@CokoCpgBackend) { ifOp.cpgGetNodes().keys } .partition { it.isUnreachable() } - val thatNodes = with(this@CokoCpgBackend) { thenOp.cpgGetNodes().toSet() } + val thatNodes = with(this@CokoCpgBackend) { thenOp.cpgGetNodes().keys } val findings = mutableListOf() diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt index 5b0cf63da..7bed28b7c 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt @@ -17,6 +17,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator @@ -51,11 +52,20 @@ class NeverEvaluator(val forbiddenOps: List) : Evaluator { if (nodes.isNotEmpty()) { // This means there are calls to the forbidden op, so Fail findings are added for (node in nodes) { + if (node.value == Result.OPEN) { + findings.add( + CpgFinding( + message = "Not enough information to evaluate \"${node.key.code}\"", + kind = Finding.Kind.Open, + node = node.key + ) + ) + } findings.add( CpgFinding( - message = "Violation against rule: \"${node.code}\". $failMessage", + message = "Violation against rule: \"${node.key.code}\". $failMessage", kind = Finding.Kind.Fail, - node = node + node = node.key ) ) } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt index bec29b8ec..780ded2c6 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt @@ -17,6 +17,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetAllNodes import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext @@ -39,9 +40,11 @@ class OnlyEvaluator(val ops: List) : Evaluator { private val defaultPassMessage = "Call is in compliance with rule" override fun evaluate(context: EvaluationContext): List { - val correctNodes = - with(this@CokoCpgBackend) { ops.flatMap { it.cpgGetNodes() } } - .toSet() + + val correctAndOpen = with(this@CokoCpgBackend) { + ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } + } + val correctAndOpenNodes = correctAndOpen.keys.toSet() val distinctOps = ops.toSet() val allNodes = @@ -50,7 +53,7 @@ class OnlyEvaluator(val ops: List) : Evaluator { // `correctNodes` is a subset of `allNodes` // we want to find nodes in `allNodes` that are not contained in `correctNodes` since they are violations - val violatingNodes = allNodes.minus(correctNodes) + val violatingNodes = allNodes.minus(correctAndOpenNodes) val ruleAnnotation = context.rule.findAnnotation() val failMessage = ruleAnnotation?.failMessage?.takeIf { it.isNotEmpty() } ?: defaultFailMessage @@ -67,7 +70,16 @@ class OnlyEvaluator(val ops: List) : Evaluator { ) } - for (node in correctNodes) { + for (node in correctAndOpenNodes) { + if (correctAndOpen[node] == Result.OPEN) { + findings.add( + CpgFinding( + message = "Not enough information to evaluate \"${node.code}\"", + kind = Finding.Kind.Open, + node = node + ) + ) + } findings.add( CpgFinding( message = "Complies with rule: \"${node.code}\". $passMessage", diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt index ef4132ce0..8de639bb5 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt @@ -153,7 +153,7 @@ class OrderEvaluator(val baseNodes: Collection, val order: Order) : Evalua // the nodes from +testObj.start(123) <- userDefined Ops are used // only allow the start nodes that take '123' as argument for ((_, op) in order.userDefinedOps.entries) { - val nodes = op.cpgGetNodes() + val nodes = op.cpgGetNodes().keys registerOpAndNodes(op, nodes, hashToMethod, nodesToOp) } From aee5df37cda3d6e12ee80f0ebfdfc91f4f00d978 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 12:09:56 +0200 Subject: [PATCH 06/19] changes around new cpgGetNodes --- .../backends/cpg/coko/CokoCpgBackend.kt | 2 +- .../backends/cpg/coko/dsl/DataItemCpgDsl.kt | 4 ++-- .../cpg/coko/dsl/ImplementationDsl.kt | 2 +- .../codyze/backends/cpg/SignatureTest.kt | 20 +++++++++---------- .../cpg/coko/dsl/ImplementationDslTest.kt | 20 +++++++++++++++---- 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt index a9533e0a8..34721ad3a 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt @@ -77,7 +77,7 @@ class CokoCpgBackend(config: BackendConfiguration) : */ override fun order(baseNodes: Op, block: Order.() -> Unit): OrderEvaluator = OrderEvaluator( - baseNodes = baseNodes.cpgGetNodes(), + baseNodes = baseNodes.cpgGetNodes().keys, order = Order().apply(block) ) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt index 287ca5986..cc1f4d497 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt @@ -42,9 +42,9 @@ fun DataItem<*>.cpgGetAllNodes(): Nodes = context(CokoBackend) fun DataItem<*>.cpgGetNodes(): Nodes { return when (this@DataItem) { - is ReturnValueItem -> op.cpgGetNodes().flatMap { it.getVariableInNextDFGOrThis() } + is ReturnValueItem -> op.cpgGetNodes().flatMap { it.key.getVariableInNextDFGOrThis() } is Value -> this@DataItem.getNodes() - is ArgumentItem -> op.cpgGetNodes().map { it.arguments[index] } // TODO: Do we count starting at 0 or 1? + is ArgumentItem -> op.cpgGetNodes().map { it.key.arguments[index] } // TODO: Do we count starting at 0 or 1? } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 9e45a6358..4521779f5 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -223,7 +223,7 @@ private fun Length.checkLength(that: Collection): Result { val size = sizeof(it).value if (size == -1) { // Handle case where size could not be determined -> OPEN Finding - return Result.OPEN + return OPEN } size in this.value }) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt index ce1508e7e..2859e6143 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt @@ -43,14 +43,14 @@ class SignatureTest { fun `test signature with wrong number of params`() { every { node.arguments } returns listOf() - assertFalse { with(backend) { with(node) { cpgSignature("test") } } } + assertFalse { with(backend) { with(node) { cpgSignature("test") == Result.VALID } } } } @Test fun `test signature with null`() { every { node.arguments } returns listOf(mockk()) - assertFalse { with(backend) { with(node) { cpgSignature(null) } } } + assertFalse { with(backend) { with(node) { cpgSignature(null) } == Result.VALID } } } @Test @@ -58,7 +58,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } } } + assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } == Result.VALID } } } @Test @@ -66,7 +66,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertFalse { with(backend) { with(node) { cpgSignature(Type("kotlin.Int")) } } } + assertFalse { with(backend) { with(node) { cpgSignature(Type("kotlin.Int")) } } == Result.VALID } } @Test @@ -75,7 +75,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertTrue { with(backend) { with(node) { cpgSignature("test" withType "kotlin.String") } } } + assertTrue { with(backend) { with(node) { cpgSignature("test" withType "kotlin.String") } } == Result.VALID } } @Test @@ -84,7 +84,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertFalse { with(backend) { with(node) { cpgSignature("test" withType "kotlin.Int") } } } + assertFalse { with(backend) { with(node) { cpgSignature("test" withType "kotlin.Int") } } == Result.VALID } } @Test @@ -93,7 +93,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertFalse { with(backend) { with(node) { cpgSignature(1 withType "kotlin.String") } } } + assertFalse { with(backend) { with(node) { cpgSignature(1 withType "kotlin.String") } } == Result.VALID } } @Test @@ -104,7 +104,7 @@ class SignatureTest { every { pairArgument.value } returns (1 to "one") mockkStatic("de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.ImplementationDslKt") - every { with(node) { param.cpgFlowsTo(pairArgument) } } returns true + every { with(node) { param.cpgFlowsTo(pairArgument) } } returns Result.VALID // tests that with normal pair only flowsTo is called with(backend) { with(node) { cpgSignature(param) } } @@ -118,7 +118,7 @@ class SignatureTest { every { stringArgument.value } returns "test" mockkStatic("de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.ImplementationDslKt") - every { with(node) { param.cpgFlowsTo(stringArgument) } } returns true + every { with(node) { param.cpgFlowsTo(stringArgument) } } returns Result.VALID // assert that signature checks the dataflow from the parameter to the argument with(backend) { with(node) { cpgSignature(param) } } @@ -134,7 +134,7 @@ class SignatureTest { val a = mockk>() args.add(a) every { a.value } returns "test" - every { with(node) { p.cpgFlowsTo(a) } } returns true + every { with(node) { p.cpgFlowsTo(a) } } returns Result.VALID } every { node.arguments } returns args diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index a16b6d5a9..94c047989 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -97,10 +97,16 @@ class ImplementationDslTest { for (i in 0..3) { with(lengthBackend) { val nodes = opX[i].cpgGetNodes() + val validNodes = nodes.filter { it.value == Result.VALID } assertEquals( results[i], - nodes.size, - "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + validNodes.size, + "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + ) + assertEquals( + 1, + nodes.filter { it.value == Result.OPEN }.size, + "cpgGetNodes did not return exactly one OPEN result as expected." ) } } @@ -123,10 +129,16 @@ class ImplementationDslTest { for (i in 0..3) { with(lengthBackend) { val nodes = opX[i].cpgGetNodes() + val validNodes = nodes.filter { it.value == Result.VALID } assertEquals( results[i], - nodes.size, - "cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + validNodes.size, + "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + ) + assertEquals( + 1, + nodes.filter { it.value == Result.OPEN }.size, + "cpgGetNodes did not return exactly one OPEN result as expected." ) } } From e2371a4fefaa10bb5de58d0f6731c7d1099a0063 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 12:20:03 +0200 Subject: [PATCH 07/19] fixes to the signatureTest --- .../aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt | 6 +++--- .../fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 4521779f5..b166dd156 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -263,11 +263,11 @@ fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = f // checks if the type of the argument is the same is Type -> Result.convert(cpgCheckType(parameter, i)) // check if any of the Nodes of the Op flow to the argument - is Op -> Result.convert(parameter.cpgGetNodes() cpgFlowsTo arguments[i]) + is Op -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] // check if any of the Nodes of the DataItem flow to the argument - is DataItem<*> -> Result.convert(parameter.cpgGetNodes() cpgFlowsTo arguments[i]) + is DataItem<*> -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] // checks if there is dataflow from the parameter to the argument in the same position - else -> Result.convert(parameter cpgFlowsTo arguments[i]) + else -> parameter cpgFlowsTo arguments[i] } } ) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt index 2859e6143..de1362529 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt @@ -43,14 +43,14 @@ class SignatureTest { fun `test signature with wrong number of params`() { every { node.arguments } returns listOf() - assertFalse { with(backend) { with(node) { cpgSignature("test") == Result.VALID } } } + assertFalse { with(backend) { with(node) { cpgSignature("test") } } == Result.VALID } } @Test fun `test signature with null`() { every { node.arguments } returns listOf(mockk()) - assertFalse { with(backend) { with(node) { cpgSignature(null) } == Result.VALID } } + assertFalse { with(backend) { with(node) { cpgSignature(null) } } == Result.VALID } } @Test @@ -58,7 +58,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } == Result.VALID } } + assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } } == Result.VALID } } @Test From 945768eb1f086ade5d541589be1fa23514a6faf6 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 12:28:25 +0200 Subject: [PATCH 08/19] remove custom and implementation between Boolean and Result because of missing lazy evaluation --- .../codyze/backends/cpg/coko/dsl/ImplementationDsl.kt | 9 +++++---- .../aisec/codyze/backends/cpg/coko/dsl/Result.kt | 9 +-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index b166dd156..7b52a2f26 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -250,16 +250,16 @@ context(CokoBackend) @Suppress("UnsafeCallOnNullableType") fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Result { // checks if amount of parameters is the same as amount of arguments of this CallExpression - return cpgCheckArgsSize(parameters, hasVarargs).and( + if(cpgCheckArgsSize(parameters, hasVarargs)) { // checks if the CallExpression matches with the parameters - parameters.withIndex().allResult { (i: Int, parameter: Any?) -> + return parameters.withIndex().allResult { (i: Int, parameter: Any?) -> when (parameter) { // if any parameter is null, signature returns false null -> INVALID is ParamWithType -> // if `parameter` is a `ParamWithType` object we want to check the type and // if there is dataflow - cpgCheckType(parameter.type, i).and(parameter.param cpgFlowsTo arguments[i]) + if (cpgCheckType(parameter.type, i)) parameter.param cpgFlowsTo arguments[i] else INVALID // checks if the type of the argument is the same is Type -> Result.convert(cpgCheckType(parameter, i)) // check if any of the Nodes of the Op flow to the argument @@ -270,7 +270,8 @@ fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = f else -> parameter cpgFlowsTo arguments[i] } } - ) + } + return INVALID } /** Checks the [type] against the type of the argument at [index] for the Call Expression */ diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt index 75d8dd260..0c3ef463d 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -52,16 +52,9 @@ inline fun Array.anyResult(predicate: (T) -> Result?): Result { return if (openFlag) OPEN else INVALID } +/** precedence order for ternary and: OPEN > INVALID > VALID */ fun Result.and(other: Result): Result { return if (this == OPEN || other == OPEN) OPEN else if (this == INVALID || other == INVALID) INVALID else VALID } - -fun Boolean.and(other: Result): Result { - return Companion.convert(this).and(other) -} - -fun Result.and(other: Boolean): Result { - return this.and(Companion.convert(other)) -} \ No newline at end of file From 300154292b6855d60d197e40d368156453bdd65b Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 1 Jul 2024 12:35:33 +0200 Subject: [PATCH 09/19] add code documentation --- .../codyze/backends/cpg/coko/dsl/Result.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt index 0c3ef463d..11d98eb66 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -2,6 +2,11 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* +/** + * A data class that serves as a ternary value for the analysis result. + * + * OPEN is used where we cannot deduce either VALID or INVALID results because of lack of information. + */ enum class Result { VALID, INVALID, @@ -18,14 +23,17 @@ enum class Result { } } +/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Iterable.allResult(predicate: (T) -> Result?): Result { + var invalidFlag = false for (element in this) { if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID) return INVALID + else if (predicate(element) == INVALID)invalidFlag = true } - return VALID + return if (invalidFlag) INVALID else VALID } +/** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { var openFlag = false for (element in this) { @@ -35,14 +43,17 @@ inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { return if (openFlag) OPEN else INVALID } +/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Array.allResult(predicate: (T) -> Result?): Result { + var invalidFlag = false for (element in this) { if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID) return INVALID + else if (predicate(element) == INVALID)invalidFlag = true } - return VALID + return if (invalidFlag) INVALID else VALID } +/** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Array.anyResult(predicate: (T) -> Result?): Result { var openFlag = false for (element in this) { From f9f02e9aeb48977ec94fa2d0859f191d843ea5c7 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 3 Jul 2024 10:55:10 +0200 Subject: [PATCH 10/19] fix code style issues --- .../cpg/coko/dsl/ImplementationDsl.kt | 62 +++++++++---------- .../codyze/backends/cpg/coko/dsl/Result.kt | 53 ++++++++++++---- .../cpg/coko/evaluators/NeverEvaluator.kt | 30 ++++----- .../cpg/coko/evaluators/OnlyEvaluator.kt | 1 - .../cpg/coko/dsl/ImplementationDslTest.kt | 6 +- 5 files changed, 92 insertions(+), 60 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 7b52a2f26..bdd4c2cf6 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -18,20 +18,22 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Definition +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ParameterGroup +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Signature import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.declarations.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.query.dataFlow -import de.fraunhofer.aisec.cpg.query.executionPath -import de.fraunhofer.aisec.cpg.query.max -import de.fraunhofer.aisec.cpg.query.min -import de.fraunhofer.aisec.cpg.query.sizeof -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.query.* // // all functions/properties defined here must use CokoBackend @@ -40,7 +42,6 @@ import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* val CokoBackend.cpg: TranslationResult get() = this.backendData as TranslationResult - /** Get all [Nodes] that are associated with this [Op]. */ context(CokoBackend) fun Op.cpgGetAllNodes(): Collection = @@ -69,11 +70,9 @@ fun Op.cpgGetNodes(): Map = when (this@Op) { is FunctionOp -> { val results = mutableListOf() - val fqn = this@Op.definitions.flatMap { - def -> - this@CokoBackend.cpgCallFqn(def.fqn) { - def.signatures.any { - sig -> + val fqn = this@Op.definitions.flatMap { def -> + this@CokoBackend.cpgCallFqn(def.fqn) { + def.signatures.any { sig -> // We consider a result, when both the signature and the flow are not invalid // However, if at least one of them is OPEN, we propagate this information to the caller val signature = cpgSignature(*sig.parameters.toTypedArray()) @@ -87,14 +86,13 @@ fun Op.cpgGetNodes(): Map = } is ConstructorOp -> { val results = mutableListOf() - val fqn = this@Op.signatures.flatMap { - sig -> - this@CokoBackend.cpgConstructor(this@Op.classFqn) { - val signature = cpgSignature(*sig.parameters.toTypedArray()) - val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(signature.and(flow)) - signature != INVALID && flow != INVALID - } + val fqn = this@Op.signatures.flatMap { sig -> + this@CokoBackend.cpgConstructor(this@Op.classFqn) { + val signature = cpgSignature(*sig.parameters.toTypedArray()) + val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + results.add(signature.and(flow)) + signature != INVALID && flow != INVALID + } } fqn.zip(results).toMap() } @@ -219,14 +217,16 @@ private fun Any.checkRange(that: Collection): Boolean { } private fun Length.checkLength(that: Collection): Result { - return Result.convert(that.all { - val size = sizeof(it).value - if (size == -1) { - // Handle case where size could not be determined -> OPEN Finding - return OPEN + return Result.convert( + that.all { + val size = sizeof(it).value + if (size == -1) { + // Handle case where size could not be determined -> OPEN Finding + return OPEN + } + size in this.value } - size in this.value - }) + ) } context(CokoBackend) @@ -250,7 +250,7 @@ context(CokoBackend) @Suppress("UnsafeCallOnNullableType") fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Result { // checks if amount of parameters is the same as amount of arguments of this CallExpression - if(cpgCheckArgsSize(parameters, hasVarargs)) { + if (cpgCheckArgsSize(parameters, hasVarargs)) { // checks if the CallExpression matches with the parameters return parameters.withIndex().allResult { (i: Int, parameter: Any?) -> when (parameter) { diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt index 11d98eb66..a07dfebd8 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* @@ -14,7 +29,7 @@ enum class Result { companion object { fun convert(from: Any?): Result { - return when(from) { + return when (from) { is Result -> from is Boolean -> if (from) VALID else INVALID else -> OPEN @@ -27,8 +42,11 @@ enum class Result { inline fun Iterable.allResult(predicate: (T) -> Result?): Result { var invalidFlag = false for (element in this) { - if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID)invalidFlag = true + if (predicate(element) == OPEN) { + return OPEN + } else if (predicate(element) == INVALID) { + invalidFlag = true + } } return if (invalidFlag) INVALID else VALID } @@ -37,8 +55,11 @@ inline fun Iterable.allResult(predicate: (T) -> Result?): Result { inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { var openFlag = false for (element in this) { - if (predicate(element) == VALID) return VALID - else if (predicate(element) == OPEN) openFlag = true + if (predicate(element) == VALID) { + return VALID + } else if (predicate(element) == OPEN) { + openFlag = true + } } return if (openFlag) OPEN else INVALID } @@ -47,8 +68,9 @@ inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { inline fun Array.allResult(predicate: (T) -> Result?): Result { var invalidFlag = false for (element in this) { - if (predicate(element) == OPEN) return OPEN - else if (predicate(element) == INVALID)invalidFlag = true + if (predicate(element) == OPEN) { + return OPEN + } else if (predicate(element) == INVALID)invalidFlag = true } return if (invalidFlag) INVALID else VALID } @@ -57,15 +79,22 @@ inline fun Array.allResult(predicate: (T) -> Result?): Result { inline fun Array.anyResult(predicate: (T) -> Result?): Result { var openFlag = false for (element in this) { - if (predicate(element) == VALID) return VALID - else if (predicate(element) == OPEN) openFlag = true + if (predicate(element) == VALID) { + return VALID + } else if (predicate(element) == OPEN) { + openFlag = true + } } return if (openFlag) OPEN else INVALID } /** precedence order for ternary and: OPEN > INVALID > VALID */ fun Result.and(other: Result): Result { - return if (this == OPEN || other == OPEN) OPEN - else if (this == INVALID || other == INVALID) INVALID - else VALID + return if (this == OPEN || other == OPEN) { + OPEN + } else if (this == INVALID || other == INVALID) { + INVALID + } else { + VALID + } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt index 7bed28b7c..36b92a5d1 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt @@ -49,26 +49,28 @@ class NeverEvaluator(val forbiddenOps: List) : Evaluator { for (op in forbiddenOps) { val nodes = op.cpgGetNodes() - if (nodes.isNotEmpty()) { - // This means there are calls to the forbidden op, so Fail findings are added - for (node in nodes) { - if (node.value == Result.OPEN) { - findings.add( - CpgFinding( - message = "Not enough information to evaluate \"${node.key.code}\"", - kind = Finding.Kind.Open, - node = node.key - ) - ) - } + if (nodes.isEmpty()) { + continue + } + + // This means there are calls to the forbidden op, so Fail findings are added + for (node in nodes) { + if (node.value == Result.OPEN) { findings.add( CpgFinding( - message = "Violation against rule: \"${node.key.code}\". $failMessage", - kind = Finding.Kind.Fail, + message = "Not enough information to evaluate \"${node.key.code}\"", + kind = Finding.Kind.Open, node = node.key ) ) } + findings.add( + CpgFinding( + message = "Violation against rule: \"${node.key.code}\". $failMessage", + kind = Finding.Kind.Fail, + node = node.key + ) + ) } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt index 780ded2c6..9865ec563 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt @@ -40,7 +40,6 @@ class OnlyEvaluator(val ops: List) : Evaluator { private val defaultPassMessage = "Call is in compliance with rule" override fun evaluate(context: EvaluationContext): List { - val correctAndOpen = with(this@CokoCpgBackend) { ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } } diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index 94c047989..d4739e82b 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -101,7 +101,8 @@ class ImplementationDslTest { assertEquals( results[i], validNodes.size, - "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes " + + "for the Op: ${opX[i]}." ) assertEquals( 1, @@ -133,7 +134,8 @@ class ImplementationDslTest { assertEquals( results[i], validNodes.size, - "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes for the Op: ${opX[i]}." + "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes " + + "for the Op: ${opX[i]}." ) assertEquals( 1, From 9c03a5bb37dcc26253088e9e32d8ede6ce8b863d Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 3 Jul 2024 11:09:27 +0200 Subject: [PATCH 11/19] only add the result when the CallExpression is not invalid --- .../backends/cpg/coko/dsl/ImplementationDsl.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index bdd4c2cf6..64346e6b1 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -73,12 +73,15 @@ fun Op.cpgGetNodes(): Map = val fqn = this@Op.definitions.flatMap { def -> this@CokoBackend.cpgCallFqn(def.fqn) { def.signatures.any { sig -> - // We consider a result, when both the signature and the flow are not invalid - // However, if at least one of them is OPEN, we propagate this information to the caller + // We consider a result when both the signature and the flow are not invalid + // However, if at least one of them is OPEN we propagate this information to the caller val signature = cpgSignature(*sig.parameters.toTypedArray()) val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(signature.and(flow)) - signature != INVALID && flow != INVALID + if (signature != INVALID && flow != INVALID) { + results.add(signature.and(flow)) + } else { + false + } } } } @@ -90,8 +93,11 @@ fun Op.cpgGetNodes(): Map = this@CokoBackend.cpgConstructor(this@Op.classFqn) { val signature = cpgSignature(*sig.parameters.toTypedArray()) val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } - results.add(signature.and(flow)) - signature != INVALID && flow != INVALID + if (signature != INVALID && flow != INVALID) { + results.add(signature.and(flow)) + } else { + false + } } } fqn.zip(results).toMap() From 5985bf991e3d57eb498308eb42879f5121c4b30f Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 8 Jul 2024 10:36:35 +0200 Subject: [PATCH 12/19] disable tests that rely on missing CPG features --- .../codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index d4739e82b..10871daa1 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -19,6 +19,7 @@ import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import kotlin.io.path.toPath import kotlin.test.assertEquals @@ -80,6 +81,7 @@ class ImplementationDslTest { } } + @Disabled("sizeof in CPG currently does not support InitListExpression as initializer") @Test fun `test Array Length`() { val opX: MutableList = mutableListOf() @@ -113,6 +115,7 @@ class ImplementationDslTest { } } + @Disabled("sizeof in CPG currently does not support MemberCallExpression as Initializer") @Test fun `test List Length`() { val opX: MutableList = mutableListOf() From 44ef97653d8611346afe9bd1839a6182635fbb41 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 10 Jul 2024 10:51:32 +0200 Subject: [PATCH 13/19] remove the Length type --- .../cpg/coko/dsl/ImplementationDsl.kt | 14 ---- .../cpg/coko/dsl/ImplementationDslTest.kt | 73 ------------------- .../ImplementationDslTest/LengthJavaFile.java | 42 ----------- .../coko/core/dsl/Types.kt | 3 - 4 files changed, 132 deletions(-) delete mode 100644 codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 64346e6b1..07e1fdbbd 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -195,7 +195,6 @@ infix fun Any.cpgFlowsTo(that: Collection): Result = is Array<*> -> this.anyResult { it?.cpgFlowsTo(that) } is Node -> that.any { dataFlow(this, it).value } is ParameterGroup -> this.parameters.allResult { it?.cpgFlowsTo(that) } - is Length -> checkLength(that) else -> this in that.map { (it as Expression).evaluate() } } ) @@ -222,19 +221,6 @@ private fun Any.checkRange(that: Collection): Boolean { } } -private fun Length.checkLength(that: Collection): Result { - return Result.convert( - that.all { - val size = sizeof(it).value - if (size == -1) { - // Handle case where size could not be determined -> OPEN Finding - return OPEN - } - size in this.value - } - ) -} - context(CokoBackend) // TODO: better description // TODO: in mark there is "..." to symbolize that the last arguments don't matter diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index 10871daa1..5aca136c1 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -81,78 +81,9 @@ class ImplementationDslTest { } } - @Disabled("sizeof in CPG currently does not support InitListExpression as initializer") - @Test - fun `test Array Length`() { - val opX: MutableList = mutableListOf() - for (i in 0..3) { - opX += op { - "Foo.fun" { - signature( - Length(i..i) - ) - } - } - } - - val results = arrayOf(1, 0, 1, 2) - for (i in 0..3) { - with(lengthBackend) { - val nodes = opX[i].cpgGetNodes() - val validNodes = nodes.filter { it.value == Result.VALID } - assertEquals( - results[i], - validNodes.size, - "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes " + - "for the Op: ${opX[i]}." - ) - assertEquals( - 1, - nodes.filter { it.value == Result.OPEN }.size, - "cpgGetNodes did not return exactly one OPEN result as expected." - ) - } - } - } - - @Disabled("sizeof in CPG currently does not support MemberCallExpression as Initializer") - @Test - fun `test List Length`() { - val opX: MutableList = mutableListOf() - for (i in 0..3) { - opX += op { - "Foo.bar" { - signature( - Length(i..i) - ) - } - } - } - - val results = arrayOf(1, 0, 1, 2) - for (i in 0..3) { - with(lengthBackend) { - val nodes = opX[i].cpgGetNodes() - val validNodes = nodes.filter { it.value == Result.VALID } - assertEquals( - results[i], - validNodes.size, - "cpgGetNodes returned ${validNodes.size} node(s) instead of ${results[i]} nodes " + - "for the Op: ${opX[i]}." - ) - assertEquals( - 1, - nodes.filter { it.value == Result.OPEN }.size, - "cpgGetNodes did not return exactly one OPEN result as expected." - ) - } - } - } - companion object { lateinit var simpleBackend: CokoCpgBackend - lateinit var lengthBackend: CokoCpgBackend @BeforeAll @JvmStatic @@ -160,16 +91,12 @@ class ImplementationDslTest { val classLoader = ImplementationDslTest::class.java.classLoader val simpleFileResource = classLoader.getResource("ImplementationDslTest/SimpleJavaFile.java") - val lengthFileResource = classLoader.getResource("ImplementationDslTest/LengthJavaFile.java") assertNotNull(simpleFileResource) - assertNotNull(lengthFileResource) val simpleFile = simpleFileResource.toURI().toPath() - val lengthFile = lengthFileResource.toURI().toPath() simpleBackend = CokoCpgBackend(config = createCpgConfiguration(simpleFile)) - lengthBackend = CokoCpgBackend(config = createCpgConfiguration(lengthFile)) } } } diff --git a/codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java b/codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java deleted file mode 100644 index 53b1ef7f4..000000000 --- a/codyze-backends/cpg/src/test/resources/ImplementationDslTest/LengthJavaFile.java +++ /dev/null @@ -1,42 +0,0 @@ -import java.util.List; -import java.lang.String; - -public class Length { - - public void one(int[] e) { - Foo f = new Foo(); - - int[] a = {1, 2, 3}; - int[] b = { }; - String[] c = {"a", "b", "c"}; - String[] d = {"ab", "c"}; - - f.fun(a); - f.fun(b); - f.fun(c); - f.fun(d); - f.fun(e); - } - - public void two(List e) { - Foo f = new Foo(); - - List a = List.of(1, 2, 3); - List b = List.of(); - List c = List.of("a", "b", "c"); - List c = List.of("ab", "c"); - - f.bar(a); - f.bar(b); - f.bar(c); - f.bar(d); - f.bar(e); - } -} - - -public class Foo { - public void fun(int[] a) {} - - public void bar(List l) {} -} diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt index 28c097db4..c0363f54e 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Types.kt @@ -29,7 +29,4 @@ data class Type(val fqn: String) data class ParamWithType(val param: Any, val type: Type) -/** Marks that we want to check the length of the argument, not the contents */ -data class Length(val value: IntRange) - infix fun Any.withType(fqn: String) = ParamWithType(this, Type(fqn)) From 64b0f2111983c7a3de299f387bfe55c6a35c6ada Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 10 Jul 2024 11:15:47 +0200 Subject: [PATCH 14/19] cleanup --- .../aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index 5aca136c1..6577b4c8a 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -19,7 +19,6 @@ import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import kotlin.io.path.toPath import kotlin.test.assertEquals From bcf9437d5ace833cd75e1feaa5e6b436137f435d Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 22 Jul 2024 10:08:34 +0200 Subject: [PATCH 15/19] add regression tests for the new Result type --- .../codyze/backends/cpg/coko/dsl/Result.kt | 18 +--- .../backends/cpg/coko/dsl/ResultTest.kt | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt index a07dfebd8..6fcde49e3 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -66,26 +66,12 @@ inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { /** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Array.allResult(predicate: (T) -> Result?): Result { - var invalidFlag = false - for (element in this) { - if (predicate(element) == OPEN) { - return OPEN - } else if (predicate(element) == INVALID)invalidFlag = true - } - return if (invalidFlag) INVALID else VALID + return this.asIterable().allResult(predicate) } /** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ inline fun Array.anyResult(predicate: (T) -> Result?): Result { - var openFlag = false - for (element in this) { - if (predicate(element) == VALID) { - return VALID - } else if (predicate(element) == OPEN) { - openFlag = true - } - } - return if (openFlag) OPEN else INVALID + return this.asIterable().anyResult(predicate) } /** precedence order for ternary and: OPEN > INVALID > VALID */ diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt new file mode 100644 index 000000000..062bfda6d --- /dev/null +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt @@ -0,0 +1,86 @@ +package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class ResultTest { + @Test + fun testIterableAll() { + val allValid = listOf(Result.VALID, Result.VALID, Result.VALID) + val oneInvalid = listOf(Result.VALID, Result.VALID, Result.INVALID) + val oneOpen = listOf(Result.VALID, Result.VALID, Result.OPEN) + val oneInvalidOneOpen = listOf(Result.VALID, Result.INVALID, Result.OPEN) + + val resultA = allValid.allResult { it } + val resultB = oneInvalid.allResult { it } + val resultC = oneOpen.allResult { it } + val resultD = oneInvalidOneOpen.allResult { it } + + assertEquals(Result.VALID, resultA) + assertEquals(Result.INVALID, resultB) + assertEquals(Result.OPEN, resultC) + assertEquals(Result.OPEN, resultD) + } + + @Test + fun testIterableAny() { + val allValid = listOf(Result.VALID, Result.VALID, Result.VALID) + val oneValid = listOf(Result.VALID, Result.OPEN, Result.INVALID) + val oneOpen = listOf(Result.OPEN, Result.INVALID, Result.INVALID) + val onlyInvalid = listOf(Result.INVALID, Result.INVALID, Result.INVALID) + + val resultA = allValid.anyResult { it } + val resultB = oneValid.anyResult { it } + val resultC = oneOpen.anyResult { it } + val resultD = onlyInvalid.anyResult { it } + + assertEquals(Result.VALID, resultA) + assertEquals(Result.VALID, resultB) + assertEquals(Result.OPEN, resultC) + assertEquals(Result.INVALID, resultD) + } + + @Test + fun testArrayAll() { + val allValid = arrayOf(Result.VALID, Result.VALID, Result.VALID) + val oneInvalid = arrayOf(Result.VALID, Result.VALID, Result.INVALID) + val oneOpen = arrayOf(Result.VALID, Result.VALID, Result.OPEN) + val oneInvalidOneOpen = arrayOf(Result.VALID, Result.INVALID, Result.OPEN) + + val resultA = allValid.allResult { it } + val resultB = oneInvalid.allResult { it } + val resultC = oneOpen.allResult { it } + val resultD = oneInvalidOneOpen.allResult { it } + + assertEquals(Result.VALID, resultA) + assertEquals(Result.INVALID, resultB) + assertEquals(Result.OPEN, resultC) + assertEquals(Result.OPEN, resultD) + } + + @Test + fun testArrayAny() { + val allValid = listOf(Result.VALID, Result.VALID, Result.VALID) + val oneValid = listOf(Result.VALID, Result.OPEN, Result.INVALID) + val oneOpen = listOf(Result.OPEN, Result.INVALID, Result.INVALID) + val onlyInvalid = listOf(Result.INVALID, Result.INVALID, Result.INVALID) + + val resultA = allValid.anyResult { it } + val resultB = oneValid.anyResult { it } + val resultC = oneOpen.anyResult { it } + val resultD = onlyInvalid.anyResult { it } + + assertEquals(Result.VALID, resultA) + assertEquals(Result.VALID, resultB) + assertEquals(Result.OPEN, resultC) + assertEquals(Result.INVALID, resultD) + } + + @Test + fun testResultAnd() { + assertEquals(Result.VALID, Result.VALID.and(Result.VALID)) + assertEquals(Result.OPEN, Result.OPEN.and(Result.VALID)) + assertEquals(Result.INVALID, Result.INVALID.and(Result.VALID)) + assertEquals(Result.OPEN, Result.OPEN.and(Result.INVALID)) + } +} \ No newline at end of file From 5cc9ce4a3416657cb2196309033363120acfebe5 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 22 Jul 2024 10:54:35 +0200 Subject: [PATCH 16/19] also test result conversion --- .../aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt index 062bfda6d..2f74861ff 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt @@ -83,4 +83,14 @@ class ResultTest { assertEquals(Result.INVALID, Result.INVALID.and(Result.VALID)) assertEquals(Result.OPEN, Result.OPEN.and(Result.INVALID)) } + + @Test + fun testConvert() { + assertEquals(Result.VALID, Result.convert(Result.VALID)) + assertEquals(Result.INVALID, Result.convert(Result.INVALID)) + assertEquals(Result.OPEN, Result.convert(Result.OPEN)) + assertEquals(Result.VALID, Result.convert(true)) + assertEquals(Result.INVALID, Result.convert(false)) + assertEquals(Result.OPEN, Result.convert("123")) + } } \ No newline at end of file From 14aa02c5d24980ecda7c907e007287b8128c6b66 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 22 Jul 2024 11:01:23 +0200 Subject: [PATCH 17/19] apply less spots --- .../codyze/backends/cpg/coko/dsl/ResultTest.kt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt index 2f74861ff..381b6ceb8 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import org.junit.jupiter.api.Test @@ -93,4 +108,4 @@ class ResultTest { assertEquals(Result.INVALID, Result.convert(false)) assertEquals(Result.OPEN, Result.convert("123")) } -} \ No newline at end of file +} From 15818cb8cced0306659c0ba7e641d9811611f2f0 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 22 Jul 2024 11:11:17 +0200 Subject: [PATCH 18/19] try not inlining result operations --- .../aisec/codyze/backends/cpg/coko/dsl/Result.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt index 6fcde49e3..6d9b22108 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -39,7 +39,7 @@ enum class Result { } /** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ -inline fun Iterable.allResult(predicate: (T) -> Result?): Result { +fun Iterable.allResult(predicate: (T) -> Result?): Result { var invalidFlag = false for (element in this) { if (predicate(element) == OPEN) { @@ -52,7 +52,7 @@ inline fun Iterable.allResult(predicate: (T) -> Result?): Result { } /** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ -inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { +fun Iterable.anyResult(predicate: (T) -> Result?): Result { var openFlag = false for (element in this) { if (predicate(element) == VALID) { @@ -65,12 +65,12 @@ inline fun Iterable.anyResult(predicate: (T) -> Result?): Result { } /** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ -inline fun Array.allResult(predicate: (T) -> Result?): Result { +fun Array.allResult(predicate: (T) -> Result?): Result { return this.asIterable().allResult(predicate) } /** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ -inline fun Array.anyResult(predicate: (T) -> Result?): Result { +fun Array.anyResult(predicate: (T) -> Result?): Result { return this.asIterable().anyResult(predicate) } From 7768611e971114cb5c52b7056a927c2b9a424889 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 22 Jul 2024 12:03:13 +0200 Subject: [PATCH 19/19] refactor Only and Never evaluator to be more testable --- .../cpg/coko/evaluators/NeverEvaluator.kt | 85 ++++++++++++------- .../cpg/coko/evaluators/OnlyEvaluator.kt | 41 ++++++--- .../backends/cpg/coko/dsl/ResultTest.kt | 4 +- .../coko/evaluators/NeverEvaluationTest.kt | 72 +++++++++++++++- .../cpg/coko/evaluators/OnlyEvaluationTest.kt | 68 +++++++++++++++ 5 files changed, 226 insertions(+), 44 deletions(-) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt index 36b92a5d1..bccb8d14d 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluator.kt @@ -18,16 +18,21 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetAllNodes import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Rule +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import kotlin.reflect.full.findAnnotation context(CokoCpgBackend) -class NeverEvaluator(val forbiddenOps: List) : Evaluator { +class NeverEvaluator(private val forbiddenOps: List) : Evaluator { + var violating = with(this@CokoCpgBackend) { + forbiddenOps.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } + } /** Default message if a violation is found */ private val defaultFailMessage: String by lazy { @@ -40,49 +45,69 @@ class NeverEvaluator(val forbiddenOps: List) : Evaluator { } override fun evaluate(context: EvaluationContext): Collection { + val (violatingNodes, correctAndOpenNodes) = getNodes() + val (failMessage, passMessage) = getMessages(context) + return createFindings(violatingNodes, correctAndOpenNodes, failMessage, passMessage) + } + + private fun getNodes(): Pair, Set> { + val violatingNodes = violating.keys.toSet() + + val distinctOps = forbiddenOps.toSet() + val allNodes = + with(this@CokoCpgBackend) { distinctOps.flatMap { it.cpgGetAllNodes() } } + .toSet() + + // `correctNodes` is a subset of `allNodes` + // we want to find nodes in `allNodes` that are not contained in `correctNodes` since they are violations + val correctAndOpenNodes = allNodes.minus(violatingNodes) + return violatingNodes to correctAndOpenNodes + } + + private fun getMessages(context: EvaluationContext): Pair { val ruleAnnotation = context.rule.findAnnotation() val failMessage = ruleAnnotation?.failMessage?.takeIf { it.isNotEmpty() } ?: defaultFailMessage val passMessage = ruleAnnotation?.passMessage?.takeIf { it.isNotEmpty() } ?: defaultPassMessage + return failMessage to passMessage + } + fun createFindings( + violatingNodes: Set, + correctAndOpenNodes: Set, + failMessage: String, + passMessage: String + ): List { val findings = mutableListOf() + for (node in violatingNodes) { + findings.add( + CpgFinding( + message = "Violation against rule: \"${node.code}\". $failMessage", + kind = Finding.Kind.Fail, + node = node + ) + ) + } - for (op in forbiddenOps) { - val nodes = op.cpgGetNodes() - - if (nodes.isEmpty()) { - continue - } - - // This means there are calls to the forbidden op, so Fail findings are added - for (node in nodes) { - if (node.value == Result.OPEN) { - findings.add( - CpgFinding( - message = "Not enough information to evaluate \"${node.key.code}\"", - kind = Finding.Kind.Open, - node = node.key - ) + for (node in correctAndOpenNodes) { + if (violating[node] == Result.OPEN) { + findings.add( + CpgFinding( + message = "Not enough information to evaluate \"${node.code}\"", + kind = Finding.Kind.Open, + node = node ) - } + ) + } else { findings.add( CpgFinding( - message = "Violation against rule: \"${node.key.code}\". $failMessage", - kind = Finding.Kind.Fail, - node = node.key + message = "Complies with rule: \"${node.code}\". $passMessage", + kind = Finding.Kind.Pass, + node = node ) ) } } - // If there are no findings, there were no violations, so a Pass finding is added - if (findings.isEmpty()) { - findings.add( - CpgFinding( - message = passMessage, - kind = Finding.Kind.Pass, - ) - ) - } return findings } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt index 9865ec563..2abcb7478 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluator.kt @@ -25,10 +25,14 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Rule +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import kotlin.reflect.full.findAnnotation context(CokoCpgBackend) -class OnlyEvaluator(val ops: List) : Evaluator { +class OnlyEvaluator(private val ops: List) : Evaluator { + var correctAndOpen = with(this@CokoCpgBackend) { + ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } + } /** Default message if a violation is found */ private val defaultFailMessage: String by lazy { @@ -40,9 +44,12 @@ class OnlyEvaluator(val ops: List) : Evaluator { private val defaultPassMessage = "Call is in compliance with rule" override fun evaluate(context: EvaluationContext): List { - val correctAndOpen = with(this@CokoCpgBackend) { - ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } - } + val (violatingNodes, correctAndOpenNodes) = getNodes() + val (failMessage, passMessage) = getMessages(context) + return createFindings(violatingNodes, correctAndOpenNodes, failMessage, passMessage) + } + + private fun getNodes(): Pair, Set> { val correctAndOpenNodes = correctAndOpen.keys.toSet() val distinctOps = ops.toSet() @@ -53,11 +60,22 @@ class OnlyEvaluator(val ops: List) : Evaluator { // `correctNodes` is a subset of `allNodes` // we want to find nodes in `allNodes` that are not contained in `correctNodes` since they are violations val violatingNodes = allNodes.minus(correctAndOpenNodes) + return violatingNodes to correctAndOpenNodes + } + private fun getMessages(context: EvaluationContext): Pair { val ruleAnnotation = context.rule.findAnnotation() val failMessage = ruleAnnotation?.failMessage?.takeIf { it.isNotEmpty() } ?: defaultFailMessage val passMessage = ruleAnnotation?.passMessage?.takeIf { it.isNotEmpty() } ?: defaultPassMessage + return failMessage to passMessage + } + fun createFindings( + violatingNodes: Set, + correctAndOpenNodes: Set, + failMessage: String, + passMessage: String + ): List { val findings = mutableListOf() for (node in violatingNodes) { findings.add( @@ -78,14 +96,15 @@ class OnlyEvaluator(val ops: List) : Evaluator { node = node ) ) - } - findings.add( - CpgFinding( - message = "Complies with rule: \"${node.code}\". $passMessage", - kind = Finding.Kind.Pass, - node = node + } else { + findings.add( + CpgFinding( + message = "Complies with rule: \"${node.code}\". $passMessage", + kind = Finding.Kind.Pass, + node = node + ) ) - ) + } } return findings diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt index 381b6ceb8..1c5ea2679 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt @@ -94,8 +94,10 @@ class ResultTest { @Test fun testResultAnd() { assertEquals(Result.VALID, Result.VALID.and(Result.VALID)) - assertEquals(Result.OPEN, Result.OPEN.and(Result.VALID)) assertEquals(Result.INVALID, Result.INVALID.and(Result.VALID)) + assertEquals(Result.INVALID, Result.VALID.and(Result.INVALID)) + assertEquals(Result.OPEN, Result.OPEN.and(Result.VALID)) + assertEquals(Result.OPEN, Result.VALID.and(Result.OPEN)) assertEquals(Result.OPEN, Result.OPEN.and(Result.INVALID)) } diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt index 5068a2333..84a76caf6 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt @@ -16,6 +16,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext @@ -23,8 +24,12 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.signature +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +import de.fraunhofer.aisec.cpg.sarif.Region import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test +import java.net.URI import java.nio.file.Path import kotlin.io.path.* import kotlin.reflect.full.valueParameters @@ -93,7 +98,70 @@ class NeverEvaluationTest { findings.all { it.kind == Finding.Kind.Pass } } - assertEquals(1, findings.size, "Found ${findings.size} finding(s) instead of one pass finding") + assertEquals(4, findings.size, "Found ${findings.size} finding(s) instead of four pass findings") + } + } + + @Test + fun `test finding creation`() { + val backend = CokoCpgBackend(config = createCpgConfiguration(violationFile)) + with(backend) { + val evaluator = NeverEvaluator(listOf()) + // Set violating Regions to 0, 1, 2 as Line and Column + val violating = listOf(CallExpression(), CallExpression(), CallExpression()) + violating.forEachIndexed { + index, expression -> + expression.location = PhysicalLocation(URI("uri"), Region(index, index, index, index)) + } + // Set correct and open Regions to 3, 4, 5 as Line and Column + val correctAndOpen = listOf(CallExpression(), CallExpression(), CallExpression()) + correctAndOpen.forEachIndexed { + index, expression -> + run { + val i = index + 3 + expression.location = PhysicalLocation(URI("uri"), Region(i, i, i, i)) + } + } + + // Associate INVALID to violating expressions, VALID to correct result with index 3 and OPEN to the others + evaluator.violating = violating.associateWith { Result.INVALID } + correctAndOpen.associateWith { + if (it.location!!.region.startLine < 4) { + Result.VALID + } else { + Result.OPEN + } + } + + val findings = evaluator.createFindings(violating.toSet(), correctAndOpen.toSet(), "", "") + val failFindings = findings.filter { it.kind == Finding.Kind.Fail } + val passFindings = findings.filter { it.kind == Finding.Kind.Pass } + val openFindings = findings.filter { it.kind == Finding.Kind.Open } + // Assert the right number of findings + assertEquals(3, failFindings.size) + assertEquals(1, passFindings.size) + assertEquals(2, openFindings.size) + // Assert the correct location of findings + assertEquals( + setOf( + Region(0, 0, 0, 0), + Region(1, 1, 1, 1), + Region(2, 2, 2, 2) + ), + failFindings.map { it.node!!.location!!.region }.toSet() + ) + assertEquals( + setOf( + Region(3, 3, 3, 3) + ), + passFindings.map { it.node!!.location!!.region }.toSet() + ) + assertEquals( + setOf( + Region(4, 4, 4, 4), + Region(5, 5, 5, 5) + ), + openFindings.map { it.node!!.location!!.region }.toSet() + ) } } @@ -105,7 +173,7 @@ class NeverEvaluationTest { @BeforeAll @JvmStatic fun startup() { - val classLoader = OnlyEvaluationTest::class.java.classLoader + val classLoader = NeverEvaluationTest::class.java.classLoader val violationFileResource = classLoader.getResource("NeverEvaluationTest/NeverViolation.java") assertNotNull(violationFileResource) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt index 7d4d3e394..756f76e01 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt @@ -16,6 +16,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext @@ -23,8 +24,12 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.signature +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +import de.fraunhofer.aisec.cpg.sarif.Region import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test +import java.net.URI import java.nio.file.Path import kotlin.io.path.* import kotlin.reflect.full.valueParameters @@ -64,6 +69,69 @@ class OnlyEvaluationTest { } } + @Test + fun `test finding creation`() { + val backend = CokoCpgBackend(config = createCpgConfiguration(testFile)) + with(backend) { + val evaluator = OnlyEvaluator(listOf()) + // Set violating Regions to 0, 1, 2 as Line and Column + val violating = listOf(CallExpression(), CallExpression(), CallExpression()) + violating.forEachIndexed { + index, expression -> + expression.location = PhysicalLocation(URI("uri"), Region(index, index, index, index)) + } + // Set correct and open Regions to 3, 4, 5 as Line and Column + val correctAndOpen = listOf(CallExpression(), CallExpression(), CallExpression()) + correctAndOpen.forEachIndexed { + index, expression -> + run { + val i = index + 3 + expression.location = PhysicalLocation(URI("uri"), Region(i, i, i, i)) + } + } + + // Associate INVALID to violating expressions, VALID to correct result with index 3 and OPEN to the others + evaluator.correctAndOpen = violating.associateWith { Result.INVALID } + correctAndOpen.associateWith { + if (it.location!!.region.startLine < 4) { + Result.VALID + } else { + Result.OPEN + } + } + + val findings = evaluator.createFindings(violating.toSet(), correctAndOpen.toSet(), "", "") + val failFindings = findings.filter { it.kind == Finding.Kind.Fail } + val passFindings = findings.filter { it.kind == Finding.Kind.Pass } + val openFindings = findings.filter { it.kind == Finding.Kind.Open } + // Assert the right number of findings + assertEquals(3, failFindings.size) + assertEquals(1, passFindings.size) + assertEquals(2, openFindings.size) + // Assert the correct location of findings + assertEquals( + setOf( + Region(0, 0, 0, 0), + Region(1, 1, 1, 1), + Region(2, 2, 2, 2) + ), + failFindings.map { it.node!!.location!!.region }.toSet() + ) + assertEquals( + setOf( + Region(3, 3, 3, 3) + ), + passFindings.map { it.node!!.location!!.region }.toSet() + ) + assertEquals( + setOf( + Region(4, 4, 4, 4), + Region(5, 5, 5, 5) + ), + openFindings.map { it.node!!.location!!.region }.toSet() + ) + } + } + companion object { lateinit var testFile: Path