From 3e76a77c969037903b3ab9e16c9c2685e86c6938 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 24 Jul 2024 12:22:57 +0200 Subject: [PATCH 01/58] create initial Size evaluator for modifiable lists --- .../collectioneval/LatticeInterval.kt | 139 ++++++++++++++++++ .../collectioneval/ListSizeEvaluator.kt | 78 ++++++++++ .../collectioneval/collection/Collection.kt | 33 +++++ .../collectioneval/collection/MutableList.kt | 75 ++++++++++ .../aisec/cpg/analysis/SizeEvaluatorTest.kt | 22 +++ .../cpg/testcases/ValueEvaluationTests.kt | 23 +++ 6 files changed, 370 insertions(+) create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt new file mode 100644 index 0000000000..2c248bf76c --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt @@ -0,0 +1,139 @@ +/* + * 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.cpg.analysis.collectioneval + +sealed class LatticeInterval { + object BOTTOM : LatticeInterval() + + data class Bounded(val lower: Bound, val upper: Bound) : LatticeInterval() { + constructor(lower: Int, upper: Int) : this(Bound.Value(lower), Bound.Value(upper)) + + constructor(lower: Int, upper: Bound) : this(Bound.Value(lower), upper) + + constructor(lower: Bound, upper: Int) : this(lower, Bound.Value(upper)) + } + + sealed class Bound { + data class Value(val value: Int) : Bound() + + data object TOP : Bound() + } + + // Addition operator + operator fun plus(other: LatticeInterval): LatticeInterval { + return when { + this is BOTTOM || other is BOTTOM -> BOTTOM + this is Bounded && other is Bounded -> { + val newLower = addBounds(this.lower, other.lower) + val newUpper = addBounds(this.upper, other.upper) + Bounded(newLower, newUpper) + } + else -> throw IllegalArgumentException("Unsupported interval type") + } + } + + // Subtraction operator + operator fun minus(other: LatticeInterval): LatticeInterval { + return when { + this is BOTTOM || other is BOTTOM -> BOTTOM + this is Bounded && other is Bounded -> { + val newLower = subtractBounds(this.lower, other.lower) + val newUpper = subtractBounds(this.upper, other.upper) + Bounded(newLower, newUpper) + } + else -> throw IllegalArgumentException("Unsupported interval type") + } + } + + // Join Operation + fun join(other: LatticeInterval): LatticeInterval { + return when { + this is BOTTOM || other is BOTTOM -> BOTTOM + this is Bounded && other is Bounded -> { + val newLower = min(this.lower, other.lower) + val newUpper = max(this.upper, other.upper) + Bounded(newLower, newUpper) + } + else -> throw IllegalArgumentException("Unsupported interval type") + } + } + + // Meet Operation + fun meet(other: LatticeInterval): LatticeInterval { + return when { + this is BOTTOM -> other + other is BOTTOM -> this + this is Bounded && other is Bounded -> { + val newLower = max(this.lower, other.lower) + val newUpper = min(this.upper, other.upper) + Bounded(newLower, newUpper) + } + else -> throw IllegalArgumentException("Unsupported interval type") + } + } + + private fun min(one: Bound, other: Bound): Bound { + return when { + one is Bound.TOP -> other + other is Bound.TOP -> one + one is Bound.Value && other is Bound.Value -> + Bound.Value(kotlin.math.min(one.value, other.value)) + else -> throw IllegalArgumentException("Unsupported interval type") + } + } + + private fun max(one: Bound, other: Bound): Bound { + return when { + one is Bound.TOP || other is Bound.TOP -> Bound.TOP + one is Bound.Value && other is Bound.Value -> + Bound.Value(kotlin.math.max(one.value, other.value)) + else -> throw IllegalArgumentException("Unsupported interval type") + } + } + + private fun addBounds(a: Bound, b: Bound): Bound { + return when { + a is Bound.Value && b is Bound.Value -> Bound.Value(a.value + b.value) + a is Bound.TOP || b is Bound.TOP -> Bound.TOP + else -> throw IllegalArgumentException("Unsupported bound type") + } + } + + private fun subtractBounds(a: Bound, b: Bound): Bound { + return when { + a is Bound.Value && b is Bound.Value -> Bound.Value(a.value - b.value) + a is Bound.TOP || b is Bound.TOP -> Bound.TOP + else -> throw IllegalArgumentException("Unsupported bound type") + } + } + + override fun toString(): String { + return when (this) { + is BOTTOM -> "BOTTOM" + is Bounded -> "[$lower, $upper]" + } + } +} diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt new file mode 100644 index 0000000000..b032c7b35d --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt @@ -0,0 +1,78 @@ +/* + * 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.cpg.analysis.collectioneval + +import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.Collection +import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.MutableList +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import kotlin.reflect.KClass +import kotlin.reflect.full.createInstance + +// We assume that we only work with lists in this operator +class ListSizeEvaluator { + fun evaluate(node: Node): LatticeInterval { + val name = node.name + // TODO: add check whether node operates on a list -> DFG flow to java.util.List? + val initializer = getInitializerOf(node) + var range = getInitialRange(initializer) + val type = MutableList::class + // TODO: evaluate effect of each operation on the list until we reach "node" + var current = initializer + do { + val next = current.nextEOG.first() + // TODO: apply each effect only once if EOG branches + range = range.applyEffect(next, name.toString(), type) + current = next + } while (next != node) + + return range + } + + private fun getInitializerOf(node: Node?): Node { + return when (node) { + null -> null!! + is Reference -> getInitializerOf(node.refersTo) + is VariableDeclaration -> node.initializer!! + else -> getInitializerOf(node.prevDFG.firstOrNull()) + } + } + + private fun getInitialRange(initializer: Node): LatticeInterval { + val size = (initializer as MemberCallExpression).arguments.size + return LatticeInterval.Bounded(size, size) + } + + private fun LatticeInterval.applyEffect( + node: Node, + name: String, + type: KClass + ): LatticeInterval { + return type.createInstance().applyEffect(this, node, name) + } +} diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt new file mode 100644 index 0000000000..205a95fcf6 --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt @@ -0,0 +1,33 @@ +/* + * 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.cpg.analysis.collectioneval.collection + +import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval +import de.fraunhofer.aisec.cpg.graph.Node + +interface Collection { + fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval +} diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt new file mode 100644 index 0000000000..e3635bae51 --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt @@ -0,0 +1,75 @@ +/* + * 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.cpg.analysis.collectioneval.collection + +import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.graph.types.IntegerType + +class MutableList : Collection { + override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { + // State can only be changed via MemberCalls (add, clear, ...) + if (node !is MemberCallExpression) { + return current + } + return when (node.name.toString()) { + "$name.add" -> { + val oneInterval = LatticeInterval.Bounded(1, 1) + current + oneInterval + } + // TODO: this should trigger another List size evaluation for the argument! + // also check and prevent -1 result + "$name.addAll" -> { + val openUpper = LatticeInterval.Bounded(0, LatticeInterval.Bound.TOP) + current + openUpper + } + "$name.clear" -> { + LatticeInterval.Bounded(0, 0) + } + "$name.remove" -> { + // We have to differentiate between remove with index or object argument + // Latter may do nothing if the element is not in the list + if (node.arguments.first().type is IntegerType) { + val oneInterval = LatticeInterval.Bounded(1, 1) + current - oneInterval + } else { + // TODO: If we know the list is empty, we know the operation has no effect + val oneZeroInterval = LatticeInterval.Bounded(1, 0) + current - oneZeroInterval + } + } + // TODO: as optimization we could check whether the argument list is empty. + // The size of the argument list is (almost) irrelevant as it has no influence on the + // possible outcomes + "$name.removeAll" -> { + val zeroInterval = LatticeInterval.Bounded(0, 0) + current.join(zeroInterval) + } + else -> current + } + } +} diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index 41dd4bc3a9..49acda8fea 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt @@ -25,6 +25,8 @@ */ package de.fraunhofer.aisec.cpg.analysis +import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval +import de.fraunhofer.aisec.cpg.analysis.collectioneval.ListSizeEvaluator import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -114,4 +116,24 @@ class SizeEvaluatorTest { val strValue = evaluator.evaluate("abcd") as Int assertEquals(4, strValue) } + + @Test + fun testListSize() { + val mainClass = tu.records["MainClass"] + assertNotNull(mainClass) + val main = mainClass.methods["main"] + assertNotNull(main) + + val list = main.bodyOrNull(7)?.singleDeclaration + assertNotNull(list) + + val printCall = main.calls("println").getOrNull(2) + assertNotNull(printCall) + val printArg = printCall.arguments.first() + assertNotNull(printArg) + + val evaluator = ListSizeEvaluator() + val value = evaluator.evaluate(printArg) + assertEquals(LatticeInterval.Bounded(0, 2), value) + } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/ValueEvaluationTests.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/ValueEvaluationTests.kt index 62a578e9b7..46dd31222b 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/ValueEvaluationTests.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/ValueEvaluationTests.kt @@ -86,6 +86,29 @@ class ValueEvaluationTests { memberCall("println", member("out", ref("System"))) { ref("str") } + + declare { + variable("list", t("list")) { + val init = + newMemberCallExpression( + null, + true, + ) + init.addArgument(newLiteral("1")) + init.name = Name("of", Name("List")) + this.initializer = init + } + } + newMemberCallExpression( + memberCall("add", ref("list"), false) { literal("2") } + ) + newMemberCallExpression( + memberCall("removeAll", ref("list"), false) { literal("3") } + ) + memberCall("println", member("out", ref("System")), true) { + ref("list") + } + returnStmt {} } } From 7d9db537452d0c8a26e91c2c3738f9c3ece33215 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 29 Jul 2024 09:33:55 +0200 Subject: [PATCH 02/58] move all collection dependant logic into collection class --- .../collectioneval/ListSizeEvaluator.kt | 20 +++++++------------ .../collectioneval/collection/Collection.kt | 4 ++++ .../collectioneval/collection/MutableList.kt | 16 +++++++++++++++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt index b032c7b35d..de156a2718 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt @@ -38,10 +38,10 @@ import kotlin.reflect.full.createInstance class ListSizeEvaluator { fun evaluate(node: Node): LatticeInterval { val name = node.name - // TODO: add check whether node operates on a list -> DFG flow to java.util.List? - val initializer = getInitializerOf(node) - var range = getInitialRange(initializer) val type = MutableList::class + // TODO: add check whether node operates on a list -> DFG flow to java.util.List? + val initializer = getInitializerOf(node, type)!! + var range = getInitialRange(initializer, type) // TODO: evaluate effect of each operation on the list until we reach "node" var current = initializer do { @@ -54,18 +54,12 @@ class ListSizeEvaluator { return range } - private fun getInitializerOf(node: Node?): Node { - return when (node) { - null -> null!! - is Reference -> getInitializerOf(node.refersTo) - is VariableDeclaration -> node.initializer!! - else -> getInitializerOf(node.prevDFG.firstOrNull()) - } + private fun getInitializerOf(node: Node, type: KClass): Node? { + return type.createInstance().getInitializer(node) } - private fun getInitialRange(initializer: Node): LatticeInterval { - val size = (initializer as MemberCallExpression).arguments.size - return LatticeInterval.Bounded(size, size) + private fun getInitialRange(initializer: Node, type: KClass): LatticeInterval { + return type.createInstance().getInitialRange(initializer) } private fun LatticeInterval.applyEffect( diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt index 205a95fcf6..c31f91cc12 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt @@ -30,4 +30,8 @@ import de.fraunhofer.aisec.cpg.graph.Node interface Collection { fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval + + fun getInitializer(node: Node?): Node? + + fun getInitialRange(initializer: Node): LatticeInterval } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt index e3635bae51..e89e66bf67 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt @@ -27,7 +27,9 @@ package de.fraunhofer.aisec.cpg.analysis.collectioneval.collection import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.IntegerType class MutableList : Collection { @@ -72,4 +74,18 @@ class MutableList : Collection { else -> current } } + + override fun getInitializer(node: Node?): Node? { + return when (node) { + null -> null!! + is Reference -> getInitializer(node.refersTo) + is VariableDeclaration -> node.initializer!! + else -> getInitializer(node.prevDFG.firstOrNull()) + } + } + + override fun getInitialRange(initializer: Node): LatticeInterval { + val size = (initializer as MemberCallExpression).arguments.size + return LatticeInterval.Bounded(size, size) + } } From 3abfa1c124114b872a0e8ed7159205f7bb4ca561 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 31 Jul 2024 12:36:09 +0200 Subject: [PATCH 03/58] add support for static arrays --- ...valuator.kt => CollectionSizeEvaluator.kt} | 20 ++++- .../collectioneval/collection/Array.kt | 85 +++++++++++++++++++ .../collectioneval/collection/MutableList.kt | 6 +- .../aisec/cpg/analysis/SizeEvaluatorTest.kt | 4 +- 4 files changed, 108 insertions(+), 7 deletions(-) rename cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/{ListSizeEvaluator.kt => CollectionSizeEvaluator.kt} (82%) create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt similarity index 82% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt index de156a2718..315fa1a943 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/ListSizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt @@ -25,20 +25,20 @@ */ package de.fraunhofer.aisec.cpg.analysis.collectioneval +import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.Array import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.Collection import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.MutableList import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import kotlin.reflect.KClass import kotlin.reflect.full.createInstance +import org.apache.commons.lang3.NotImplementedException // We assume that we only work with lists in this operator -class ListSizeEvaluator { +class CollectionSizeEvaluator { fun evaluate(node: Node): LatticeInterval { val name = node.name - val type = MutableList::class + val type = getType(node) // TODO: add check whether node operates on a list -> DFG flow to java.util.List? val initializer = getInitializerOf(node, type)!! var range = getInitialRange(initializer, type) @@ -69,4 +69,16 @@ class ListSizeEvaluator { ): LatticeInterval { return type.createInstance().applyEffect(this, node, name) } + + private fun getType(node: Node): KClass { + if (node !is Reference) { + throw NotImplementedException() + } + val name = node.type.name.toString() + return when { + name.startsWith("java.util.List") -> MutableList::class + name.endsWith("[]") -> Array::class + else -> throw NotImplementedException() + } + } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt new file mode 100644 index 0000000000..5cfc02e3a0 --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt @@ -0,0 +1,85 @@ +/* + * 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.cpg.analysis.collectioneval.collection + +import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.graph.types.IntegerType +import de.fraunhofer.aisec.cpg.query.value +import org.apache.commons.lang3.NotImplementedException + +class Array : Collection { + override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { + // There are no functions that change the size of a Java array without destroying it + return current + } + + override fun getInitializer(node: Node?): Node? { + return when (node) { + null -> null!! + is Reference -> getInitializer(node.refersTo) + is VariableDeclaration -> node.initializer!! + else -> getInitializer(node.prevDFG.firstOrNull()) + } + } + + override fun getInitialRange(initializer: Node): LatticeInterval { + // Consider multi-dimensional arrays (matrices) + val size = getSize(initializer) + return LatticeInterval.Bounded(size, size) + } + + private fun getSize(node: Node): Int { + return when (node) { + // TODO: could be more performant if you detect that all initializers are Literals and + is Literal<*> -> { + if (node.type !is IntegerType) { + throw NotImplementedException() + } else { + 1 + } + } + is InitializerListExpression -> { + node.initializers.fold(0) { acc, init -> acc + getSize(init) } + } + is NewArrayExpression -> { + if (node.initializer != null) { + getSize(node.initializer!!) + } else { + node.dimensions + .map { it.value.value as Int } + .reduce { acc, dimension -> acc * dimension } + } + } + else -> throw NotImplementedException() + } + } +} diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt index e89e66bf67..f5be8cdc7b 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.IntegerType +import org.apache.commons.lang3.NotImplementedException class MutableList : Collection { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { @@ -85,7 +86,10 @@ class MutableList : Collection { } override fun getInitialRange(initializer: Node): LatticeInterval { - val size = (initializer as MemberCallExpression).arguments.size + if (initializer !is MemberCallExpression) { + throw NotImplementedException() + } + val size = initializer.arguments.size return LatticeInterval.Bounded(size, size) } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index 49acda8fea..714905f8fc 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt @@ -26,7 +26,7 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval -import de.fraunhofer.aisec.cpg.analysis.collectioneval.ListSizeEvaluator +import de.fraunhofer.aisec.cpg.analysis.collectioneval.CollectionSizeEvaluator import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -132,7 +132,7 @@ class SizeEvaluatorTest { val printArg = printCall.arguments.first() assertNotNull(printArg) - val evaluator = ListSizeEvaluator() + val evaluator = CollectionSizeEvaluator() val value = evaluator.evaluate(printArg) assertEquals(LatticeInterval.Bounded(0, 2), value) } From cb55e933fc136d8c51c6df3c7c66c57a4a2f721e Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 31 Jul 2024 12:36:21 +0200 Subject: [PATCH 04/58] implement narrowing and widening operations --- .../collectioneval/LatticeInterval.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt index 2c248bf76c..3ec4fd3f14 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt @@ -95,6 +95,46 @@ sealed class LatticeInterval { } } + // Widening + fun widen(other: LatticeInterval): LatticeInterval { + if (this !is Bounded || other !is Bounded) { + return BOTTOM + } + val lower: Bound = when { + max(this.lower, other.lower) == other.lower -> { + this.lower + } + else -> Bound.Value(0) + } + val upper: Bound = when { + max(this.upper, other.upper) == this.upper -> { + this.upper + } + else -> Bound.TOP + } + return Bounded(lower, upper) + } + + // Narrowing + fun narrow(other: LatticeInterval): LatticeInterval { + if (this !is Bounded || other !is Bounded) { + return BOTTOM + } + val lower: Bound = when { + this.lower == Bound.Value(0) -> { + other.lower + } + else -> this.lower + } + val upper: Bound = when { + this.upper == Bound.TOP -> { + other.upper + } + else -> this.upper + } + return Bounded(lower, upper) + } + private fun min(one: Bound, other: Bound): Bound { return when { one is Bound.TOP -> other From f43919d112d436b54b0dfba59f5636dd4af7e787 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 5 Aug 2024 11:24:36 +0200 Subject: [PATCH 05/58] add narrowing and widening to loop analysis --- .../collectioneval/CollectionSizeEvaluator.kt | 125 +++++++++++++++++- .../collectioneval/LatticeInterval.kt | 50 ++++--- .../collectioneval/collection/Array.kt | 10 +- .../collectioneval/collection/Collection.kt | 15 ++- .../collectioneval/collection/MutableList.kt | 55 +++++--- .../aisec/cpg/analysis/SizeEvaluatorTest.kt | 2 +- 6 files changed, 205 insertions(+), 52 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt index 315fa1a943..96363063fa 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt @@ -29,6 +29,9 @@ import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.Array import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.Collection import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.MutableList import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.statements +import de.fraunhofer.aisec.cpg.graph.statements.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import kotlin.reflect.KClass import kotlin.reflect.full.createInstance @@ -39,15 +42,28 @@ class CollectionSizeEvaluator { fun evaluate(node: Node): LatticeInterval { val name = node.name val type = getType(node) - // TODO: add check whether node operates on a list -> DFG flow to java.util.List? val initializer = getInitializerOf(node, type)!! var range = getInitialRange(initializer, type) - // TODO: evaluate effect of each operation on the list until we reach "node" + // evaluate effect of each operation on the list until we reach "node" var current = initializer + // TODO: de preprocessing: remove all node types that we do not need do { - val next = current.nextEOG.first() - // TODO: apply each effect only once if EOG branches - range = range.applyEffect(next, name.toString(), type) + val next: Node + if ( + current is ForStatement || + current is WhileStatement || + current is ForEachStatement || + current is DoStatement + ) { + // TODO: is there an interface for loop heads? + val (lRange, lNext) = handleLoop(range, current, name.toString(), type, node) + range = lRange + next = lNext + } else { + // TODO: apply each effect only once if EOG branches (see interface BranchingNode) + range = range.applyEffect(current, name.toString(), type).first + next = current.nextEOG.first() + } current = next } while (next != node) @@ -66,7 +82,7 @@ class CollectionSizeEvaluator { node: Node, name: String, type: KClass - ): LatticeInterval { + ): Pair { return type.createInstance().applyEffect(this, node, name) } @@ -76,9 +92,106 @@ class CollectionSizeEvaluator { } val name = node.type.name.toString() return when { + // TODO: could be linkedList, arrayList, ... name.startsWith("java.util.List") -> MutableList::class name.endsWith("[]") -> Array::class + else -> MutableList::class // throw NotImplementedException() + } + } + + private fun handleLoop( + range: LatticeInterval, + node: Node, + name: String, + type: KClass, + goalNode: Node + ): Pair { + val afterLoop = node.nextEOG[1] + val body: kotlin.Array + var newRange = range + when (node) { + is ForStatement -> { + body = + when (node.statement) { + is Block -> node.statement.statements.toTypedArray() + null -> arrayOf() + else -> arrayOf(node.statement!!) + } + } + is WhileStatement -> { + body = + when (node.statement) { + is Block -> node.statement.statements.toTypedArray() + null -> arrayOf() + else -> arrayOf(node.statement!!) + } + } + is ForEachStatement -> { + body = + when (node.statement) { + is Block -> node.statement.statements.toTypedArray() + null -> arrayOf() + else -> arrayOf(node.statement!!) + } + } + is DoStatement -> { + body = + when (node.statement) { + is Block -> node.statement.statements.toTypedArray() + null -> arrayOf() + else -> arrayOf(node.statement!!) + } + } else -> throw NotImplementedException() } + + // Initialize the intervals for the previous loop iteration + val prevBodyIntervals = Array(body.size) { LatticeInterval.BOTTOM } + // WIDENING + // TODO: get max amount of iterations for the loop! + // TODO: maybe only widen at one point (loop separator) for better results + outer@ while (true) { + for (index in body.indices) { + // First apply the effect + val (lRange, effect) = newRange.applyEffect(body[index], name, type) + if (effect) { + newRange = lRange + // Then widen using the previous iteration + newRange = prevBodyIntervals[index].widen(newRange) + // If nothing changed we can abort + if (newRange == prevBodyIntervals[index]) { + break@outer + } else { + prevBodyIntervals[index] = newRange + } + } + } + } + // NARROWING + // TODO: what is the right termination condition? + outer@ while (true) { + for (index in body.indices) { + // First apply the effect + val (lRange, effect) = newRange.applyEffect(body[index], name, type) + if (effect) { + newRange = lRange + // Then widen using the previous iteration + newRange = prevBodyIntervals[index].narrow(newRange) + // If nothing changed we can abort + if (newRange == prevBodyIntervals[index]) { + break@outer + } else { + prevBodyIntervals[index] = newRange + } + } + } + } + + // return goalNode as next node if it was in the loop to prevent skipping loop termination + // condition + if (body.contains(goalNode)) { + return newRange to goalNode + } + return newRange to afterLoop } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt index 3ec4fd3f14..6c73a78cfc 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt @@ -97,21 +97,25 @@ sealed class LatticeInterval { // Widening fun widen(other: LatticeInterval): LatticeInterval { - if (this !is Bounded || other !is Bounded) { - return BOTTOM + if (this !is Bounded) { + return other + } else if (other !is Bounded) { + return this } - val lower: Bound = when { - max(this.lower, other.lower) == other.lower -> { - this.lower + val lower: Bound = + when { + max(this.lower, other.lower) == other.lower -> { + this.lower + } + else -> Bound.Value(0) } - else -> Bound.Value(0) - } - val upper: Bound = when { - max(this.upper, other.upper) == this.upper -> { - this.upper + val upper: Bound = + when { + max(this.upper, other.upper) == this.upper -> { + this.upper + } + else -> Bound.TOP } - else -> Bound.TOP - } return Bounded(lower, upper) } @@ -120,18 +124,20 @@ sealed class LatticeInterval { if (this !is Bounded || other !is Bounded) { return BOTTOM } - val lower: Bound = when { - this.lower == Bound.Value(0) -> { - other.lower + val lower: Bound = + when { + this.lower == Bound.Value(0) -> { + other.lower + } + else -> this.lower } - else -> this.lower - } - val upper: Bound = when { - this.upper == Bound.TOP -> { - other.upper + val upper: Bound = + when { + this.upper == Bound.TOP -> { + other.upper + } + else -> this.upper } - else -> this.upper - } return Bounded(lower, upper) } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt index 5cfc02e3a0..c0c886bf1d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt @@ -36,10 +36,14 @@ import de.fraunhofer.aisec.cpg.graph.types.IntegerType import de.fraunhofer.aisec.cpg.query.value import org.apache.commons.lang3.NotImplementedException -class Array : Collection { - override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { +class Array : Collection { + override fun applyEffect( + current: LatticeInterval, + node: Node, + name: String + ): Pair { // There are no functions that change the size of a Java array without destroying it - return current + return current to false } override fun getInitializer(node: Node?): Node? { diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt index c31f91cc12..f2dbb417cc 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt @@ -29,7 +29,20 @@ import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node interface Collection { - fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval + /** + * Applies the effect of a Node to the Interval describing possible values of a collection. Also + * returns true if the node was "valid" node that could have an influence on the Interval. + * + * Examples: + * - list.add(x) on [0, 0] -> ([1, 1], true) + * - list.clear(x) on [0, 0] -> ([0, 0], true) + * - println(list) on [0, 0] -> ([0, 0], false) + */ + fun applyEffect( + current: LatticeInterval, + node: Node, + name: String + ): Pair fun getInitializer(node: Node?): Node? diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt index f5be8cdc7b..dfa2b07939 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt @@ -29,50 +29,61 @@ import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.IntegerType import org.apache.commons.lang3.NotImplementedException class MutableList : Collection { - override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { + override fun applyEffect( + current: LatticeInterval, + node: Node, + name: String + ): Pair { + // TODO: state can also be estimated by conditions! (if (l.size < 3) ...) // State can only be changed via MemberCalls (add, clear, ...) if (node !is MemberCallExpression) { - return current + return current to false } - return when (node.name.toString()) { - "$name.add" -> { + // Only consider calls that have the subject as base + if ((node.callee as? MemberExpression)?.base?.code != name) { + return current to false + } + return when (node.name.localName) { + "add" -> { val oneInterval = LatticeInterval.Bounded(1, 1) - current + oneInterval + current + oneInterval to true } // TODO: this should trigger another List size evaluation for the argument! // also check and prevent -1 result - "$name.addAll" -> { + "addAll" -> { val openUpper = LatticeInterval.Bounded(0, LatticeInterval.Bound.TOP) - current + openUpper + current + openUpper to true } - "$name.clear" -> { - LatticeInterval.Bounded(0, 0) + "clear" -> { + LatticeInterval.Bounded(0, 0) to true } - "$name.remove" -> { + "remove" -> { // We have to differentiate between remove with index or object argument // Latter may do nothing if the element is not in the list if (node.arguments.first().type is IntegerType) { val oneInterval = LatticeInterval.Bounded(1, 1) - current - oneInterval + current - oneInterval to true } else { // TODO: If we know the list is empty, we know the operation has no effect val oneZeroInterval = LatticeInterval.Bounded(1, 0) - current - oneZeroInterval + current - oneZeroInterval to true } } // TODO: as optimization we could check whether the argument list is empty. // The size of the argument list is (almost) irrelevant as it has no influence on the // possible outcomes - "$name.removeAll" -> { + "removeAll" -> { val zeroInterval = LatticeInterval.Bounded(0, 0) - current.join(zeroInterval) + current.join(zeroInterval) to true } - else -> current + else -> current to false } } @@ -86,10 +97,16 @@ class MutableList : Collection { } override fun getInitialRange(initializer: Node): LatticeInterval { - if (initializer !is MemberCallExpression) { - throw NotImplementedException() + when (initializer) { + is MemberCallExpression -> { + val size = initializer.arguments.size + return LatticeInterval.Bounded(size, size) + } + is NewExpression -> { + // TODO: could have a collection as argument! + return LatticeInterval.Bounded(0, 0) + } + else -> throw NotImplementedException() } - val size = initializer.arguments.size - return LatticeInterval.Bounded(size, size) } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index 714905f8fc..52a2400129 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt @@ -25,8 +25,8 @@ */ package de.fraunhofer.aisec.cpg.analysis -import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval import de.fraunhofer.aisec.cpg.analysis.collectioneval.CollectionSizeEvaluator +import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration From 901f9c2f64ab9fca098b0b43de9cf28569b3a89e Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 5 Aug 2024 12:55:19 +0200 Subject: [PATCH 06/58] handle branches in analysis --- .../collectioneval/CollectionSizeEvaluator.kt | 101 ++++++++++++++---- 1 file changed, 81 insertions(+), 20 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt index 96363063fa..63b6ed4768 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.analysis.collectioneval import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.Array import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.Collection import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.MutableList +import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.statements.* @@ -40,32 +41,18 @@ import org.apache.commons.lang3.NotImplementedException // We assume that we only work with lists in this operator class CollectionSizeEvaluator { fun evaluate(node: Node): LatticeInterval { - val name = node.name + val name = node.name.toString() val type = getType(node) val initializer = getInitializerOf(node, type)!! var range = getInitialRange(initializer, type) // evaluate effect of each operation on the list until we reach "node" var current = initializer - // TODO: de preprocessing: remove all node types that we do not need + // TODO: do preprocessing: remove all node types that we do not need do { - val next: Node - if ( - current is ForStatement || - current is WhileStatement || - current is ForEachStatement || - current is DoStatement - ) { - // TODO: is there an interface for loop heads? - val (lRange, lNext) = handleLoop(range, current, name.toString(), type, node) - range = lRange - next = lNext - } else { - // TODO: apply each effect only once if EOG branches (see interface BranchingNode) - range = range.applyEffect(current, name.toString(), type).first - next = current.nextEOG.first() - } - current = next - } while (next != node) + val (newRange, newCurrent) = handleNext(range, current, name, type, node) + range = newRange + current = newCurrent + } while (current != node) return range } @@ -99,6 +86,23 @@ class CollectionSizeEvaluator { } } + private fun handleNext( + range: LatticeInterval, + node: Node, + name: String, + type: KClass, + goalNode: Node + ): Pair { + return when (node) { + is ForStatement, + is WhileStatement, + is ForEachStatement, + is DoStatement -> handleLoop(range, node, name, type, goalNode) + is BranchingNode -> handleBranch(range, node, name, type, goalNode) + else -> range.applyEffect(node, name, type).first to node.nextEOG.first() + } + } + private fun handleLoop( range: LatticeInterval, node: Node, @@ -194,4 +198,61 @@ class CollectionSizeEvaluator { } return newRange to afterLoop } + + private fun handleBranch( + range: LatticeInterval, + node: Node, + name: String, + type: KClass, + goalNode: Node + ): Pair { + val mergeNode = findMergeNode(node) + val branchNumber = node.nextEOG.size + val finalBranchRanges = Array(branchNumber) { range } + for (i in 0 until branchNumber) { + var current = node.nextEOG[i] + // if we arrive at the mergeNode we are done with this branch + while (current != mergeNode) { + // If at any point we find the goal node in a branch, we stop and ignore other all + // branches + if (current == goalNode) { + return finalBranchRanges[i] to current + } + val (nextRange, nextNode) = + handleNext(finalBranchRanges[i], current, name, type, goalNode) + finalBranchRanges[i] = nextRange + current = nextNode + } + } + val finalMergedRange = finalBranchRanges.reduce { acc, r -> acc.join(r) } + return finalMergedRange to mergeNode + } + + private fun findMergeNode(node: Node): Node { + if (node !is BranchingNode) { + return node.nextEOG.first() + } + + val branchNumber = node.nextEOG.size + val branches = Array(branchNumber) { Node() } + val visited = Array(branchNumber) { mutableSetOf() } + for (index in 0 until branchNumber) { + branches[index] = node.nextEOG[index] + } + while (true) { + for (index in 0 until branchNumber) { + val current = branches[index] + if (current in visited[index]) { + continue + } + visited[index].add(current) + // If all paths contain the current node it merges all branches + if (visited.all { it.contains(current) }) { + return current + } + val next = current.nextEOG.firstOrNull() ?: break + branches[index] = next + } + } + } } From 4f0e95afc657973619563a63ed1c769d820955c5 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Thu, 8 Aug 2024 11:40:12 +0200 Subject: [PATCH 07/58] improve loops by preprocessing, better narrowing and valid starting ranges --- .../collectioneval/CollectionSizeEvaluator.kt | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt index 63b6ed4768..4b7d1b31ac 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt @@ -111,8 +111,9 @@ class CollectionSizeEvaluator { goalNode: Node ): Pair { val afterLoop = node.nextEOG[1] - val body: kotlin.Array + var body: kotlin.Array var newRange = range + // TODO: body could be wrong if we have nested loops / branches in loops! when (node) { is ForStatement -> { body = @@ -149,44 +150,44 @@ class CollectionSizeEvaluator { else -> throw NotImplementedException() } + // Preprocessing: remove all nodes without effect from the loop + body = body.filter { range.applyEffect(it, name, type).second }.toTypedArray() // Initialize the intervals for the previous loop iteration - val prevBodyIntervals = Array(body.size) { LatticeInterval.BOTTOM } + val prevBodyIntervals = Array(body.size) { range } // WIDENING // TODO: get max amount of iterations for the loop! - // TODO: maybe only widen at one point (loop separator) for better results outer@ while (true) { for (index in body.indices) { // First apply the effect - val (lRange, effect) = newRange.applyEffect(body[index], name, type) - if (effect) { - newRange = lRange - // Then widen using the previous iteration + // TODO: change applyEffect to handle to recursively handle loops/branches + val (lRange, _) = newRange.applyEffect(body[index], name, type) + newRange = lRange + // Then widen using the previous iteration + // Only widen for the first effective node in the loop (loop separator) + if (index == 0) { newRange = prevBodyIntervals[index].widen(newRange) - // If nothing changed we can abort - if (newRange == prevBodyIntervals[index]) { - break@outer - } else { - prevBodyIntervals[index] = newRange - } + } + // If nothing changed we can abort + if (newRange == prevBodyIntervals[index]) { + break@outer + } else { + prevBodyIntervals[index] = newRange } } } // NARROWING - // TODO: what is the right termination condition? outer@ while (true) { for (index in body.indices) { // First apply the effect - val (lRange, effect) = newRange.applyEffect(body[index], name, type) - if (effect) { - newRange = lRange - // Then widen using the previous iteration - newRange = prevBodyIntervals[index].narrow(newRange) - // If nothing changed we can abort - if (newRange == prevBodyIntervals[index]) { - break@outer - } else { - prevBodyIntervals[index] = newRange - } + val (lRange, _) = newRange.applyEffect(body[index], name, type) + newRange = lRange + // Then narrow using the previous iteration + newRange = prevBodyIntervals[index].narrow(newRange) + // If ALL loop ranges are stable we can abort + if (index == body.size - 1 && newRange == prevBodyIntervals[index]) { + break@outer + } else { + prevBodyIntervals[index] = newRange } } } From 34412bae53ce858608f420bd5fb9297bdff0a14d Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Thu, 8 Aug 2024 11:40:41 +0200 Subject: [PATCH 08/58] improve subtraction by handling 0 as lowest possible value --- .../aisec/cpg/analysis/collectioneval/LatticeInterval.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt index 6c73a78cfc..58557ee879 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt @@ -162,16 +162,18 @@ sealed class LatticeInterval { private fun addBounds(a: Bound, b: Bound): Bound { return when { - a is Bound.Value && b is Bound.Value -> Bound.Value(a.value + b.value) a is Bound.TOP || b is Bound.TOP -> Bound.TOP + a is Bound.Value && b is Bound.Value -> Bound.Value(a.value + b.value) else -> throw IllegalArgumentException("Unsupported bound type") } } private fun subtractBounds(a: Bound, b: Bound): Bound { return when { + a is Bound.TOP -> Bound.TOP + b is Bound.TOP -> Bound.Value(0) + a == Bound.Value(0) -> Bound.Value(0) a is Bound.Value && b is Bound.Value -> Bound.Value(a.value - b.value) - a is Bound.TOP || b is Bound.TOP -> Bound.TOP else -> throw IllegalArgumentException("Unsupported bound type") } } From 372572cc24246fba40d72fa2420bae00dfb1a973 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Thu, 8 Aug 2024 12:48:09 +0200 Subject: [PATCH 09/58] mark branching nodes as nodes with an effect --- .../cpg/analysis/collectioneval/collection/MutableList.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt index dfa2b07939..855b3bf845 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.analysis.collectioneval.collection import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval +import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression @@ -42,7 +43,11 @@ class MutableList : Collection { name: String ): Pair { // TODO: state can also be estimated by conditions! (if (l.size < 3) ...) - // State can only be changed via MemberCalls (add, clear, ...) + // Branching nodes have to be assumed to have an effect + if (node is BranchingNode) { + return current to true + } + // State can only be directly changed via MemberCalls (add, clear, ...) if (node !is MemberCallExpression) { return current to false } From 0e3bc5b44e30f2a184f5e425f948139519bf4b1a Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Thu, 8 Aug 2024 12:49:22 +0200 Subject: [PATCH 10/58] correctly identify nodes of deeper branch layers in a loop --- .../collectioneval/CollectionSizeEvaluator.kt | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt index 4b7d1b31ac..db127cdd62 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt @@ -111,56 +111,58 @@ class CollectionSizeEvaluator { goalNode: Node ): Pair { val afterLoop = node.nextEOG[1] - var body: kotlin.Array + val body = mutableListOf() var newRange = range - // TODO: body could be wrong if we have nested loops / branches in loops! - when (node) { - is ForStatement -> { - body = + val firstBodyStatement: Node? = + when (node) { + is ForStatement -> { when (node.statement) { - is Block -> node.statement.statements.toTypedArray() - null -> arrayOf() - else -> arrayOf(node.statement!!) + // This cast is important! Otherwise, the wrong statements are returned + is Block -> (node.statement as Block).statements.firstOrNull() + null -> null + else -> node.statement } - } - is WhileStatement -> { - body = + } + is WhileStatement -> { when (node.statement) { - is Block -> node.statement.statements.toTypedArray() - null -> arrayOf() - else -> arrayOf(node.statement!!) + is Block -> (node.statement as Block).statements.firstOrNull() + null -> null + else -> node.statement } - } - is ForEachStatement -> { - body = + } + is ForEachStatement -> { when (node.statement) { - is Block -> node.statement.statements.toTypedArray() - null -> arrayOf() - else -> arrayOf(node.statement!!) + is Block -> (node.statement as Block).statements.firstOrNull() + null -> null + else -> node.statement } - } - is DoStatement -> { - body = + } + is DoStatement -> { when (node.statement) { - is Block -> node.statement.statements.toTypedArray() - null -> arrayOf() - else -> arrayOf(node.statement!!) + is Block -> (node.statement as Block).statements.firstOrNull() + null -> null + else -> node.statement } + } + else -> throw NotImplementedException() } - else -> throw NotImplementedException() + var current: Node? = firstBodyStatement + while (current != null && current != afterLoop && current != node) { + // Only add the Statement if it affects the range + if (range.applyEffect(current, name, type).second) { + body.add(current) + } + // get the next node, skipping nested structures + // we assume that the last nextEOG always points to the node after the branch! + current = current.nextEOG.last() } - - // Preprocessing: remove all nodes without effect from the loop - body = body.filter { range.applyEffect(it, name, type).second }.toTypedArray() // Initialize the intervals for the previous loop iteration val prevBodyIntervals = Array(body.size) { range } // WIDENING - // TODO: get max amount of iterations for the loop! outer@ while (true) { for (index in body.indices) { - // First apply the effect - // TODO: change applyEffect to handle to recursively handle loops/branches - val (lRange, _) = newRange.applyEffect(body[index], name, type) + // First apply the effect of the next node + val (lRange, _) = handleNext(newRange, body[index], name, type, goalNode) newRange = lRange // Then widen using the previous iteration // Only widen for the first effective node in the loop (loop separator) @@ -178,8 +180,8 @@ class CollectionSizeEvaluator { // NARROWING outer@ while (true) { for (index in body.indices) { - // First apply the effect - val (lRange, _) = newRange.applyEffect(body[index], name, type) + // First apply the effect of the next node + val (lRange, _) = handleNext(newRange, body[index], name, type, goalNode) newRange = lRange // Then narrow using the previous iteration newRange = prevBodyIntervals[index].narrow(newRange) @@ -225,6 +227,7 @@ class CollectionSizeEvaluator { current = nextNode } } + // Take the join of all branches since we do not know which was taken val finalMergedRange = finalBranchRanges.reduce { acc, r -> acc.join(r) } return finalMergedRange to mergeNode } From a00220102c82e5a7bf011f087f74ea35e2795e5b Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 12 Aug 2024 11:35:54 +0200 Subject: [PATCH 11/58] add documentation --- .../collectioneval/CollectionSizeEvaluator.kt | 51 ++++++++++++++++++- .../collectioneval/collection/Array.kt | 2 +- .../collectioneval/collection/MutableList.kt | 1 + 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt index db127cdd62..ebcb2fb262 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt @@ -38,7 +38,6 @@ import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import org.apache.commons.lang3.NotImplementedException -// We assume that we only work with lists in this operator class CollectionSizeEvaluator { fun evaluate(node: Node): LatticeInterval { val name = node.name.toString() @@ -47,7 +46,6 @@ class CollectionSizeEvaluator { var range = getInitialRange(initializer, type) // evaluate effect of each operation on the list until we reach "node" var current = initializer - // TODO: do preprocessing: remove all node types that we do not need do { val (newRange, newCurrent) = handleNext(range, current, name, type, node) range = newRange @@ -73,6 +71,13 @@ class CollectionSizeEvaluator { return type.createInstance().applyEffect(this, node, name) } + /** + * Tries to determine the Collection type of the target Node by parsing the type name. + * + * @param node The target node + * @return A Kotlin class representing the collection that contains the necessary analysis + * functions + */ private fun getType(node: Node): KClass { if (node !is Reference) { throw NotImplementedException() @@ -86,6 +91,17 @@ class CollectionSizeEvaluator { } } + /** + * This function delegates to the right handler depending on the next node. Use this instead of + * directly calling _applyEffect_ to correctly handle complex statements. + * + * @param range The previous size range + * @param node The current node + * @param name The name of the collection variable + * @param type The type of the collection + * @param goalNode The target node for the analysis + * @return A Pair containing the new size range and the next node for the analysis + */ private fun handleNext( range: LatticeInterval, node: Node, @@ -103,6 +119,18 @@ class CollectionSizeEvaluator { } } + /** + * Handles the analysis of a Looping statement. It does so by filtering out uninteresting + * statements before applying widening and narrowing in each iteration. If the target node is + * included in the body, the returned node will be the target node. + * + * @param range The previous size range + * @param node The BranchingNode as head of the loop + * @param name The name of the collection variable + * @param type The type of the collection + * @param goalNode The target node for the analysis + * @return A Pair containing the new size range and the next node for the analysis + */ private fun handleLoop( range: LatticeInterval, node: Node, @@ -147,6 +175,7 @@ class CollectionSizeEvaluator { else -> throw NotImplementedException() } var current: Node? = firstBodyStatement + // Preprocessing: filter for valid nodes while (current != null && current != afterLoop && current != node) { // Only add the Statement if it affects the range if (range.applyEffect(current, name, type).second) { @@ -202,6 +231,18 @@ class CollectionSizeEvaluator { return newRange to afterLoop } + /** + * Handles the analysis of a Branching statement. It does so by evaluating the final ranges of + * each branch and taking the join over all of them. If the target node is included in any + * branch, the evaluation only uses this branch. + * + * @param range The previous size range + * @param node The BranchingNode as head of the branch + * @param name The name of the collection variable + * @param type The type of the collection + * @param goalNode The target node for the analysis + * @return A Pair containing the new size range and the next node for the analysis + */ private fun handleBranch( range: LatticeInterval, node: Node, @@ -232,6 +273,12 @@ class CollectionSizeEvaluator { return finalMergedRange to mergeNode } + /** + * Finds the "MergeNode" as the first common node of all branches. + * + * @param node The BranchingNode that is the head of the branching statement + * @return The Node that is the end of the branching statement + */ private fun findMergeNode(node: Node): Node { if (node !is BranchingNode) { return node.nextEOG.first() diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt index c0c886bf1d..aeb532d55e 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt @@ -63,7 +63,7 @@ class Array : Collection { private fun getSize(node: Node): Int { return when (node) { - // TODO: could be more performant if you detect that all initializers are Literals and + // TODO: could be more performant if you detect that all initializers are Literals is Literal<*> -> { if (node.type !is IntegerType) { throw NotImplementedException() diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt index 855b3bf845..cb0e5688b8 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt @@ -43,6 +43,7 @@ class MutableList : Collection { name: String ): Pair { // TODO: state can also be estimated by conditions! (if (l.size < 3) ...) + // TODO: assignment -> new size // Branching nodes have to be assumed to have an effect if (node is BranchingNode) { return current to true From 3648081d5282101d0642755b1d4355860808d909 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 12 Aug 2024 11:36:41 +0200 Subject: [PATCH 12/58] temporarily repurpose cpg-neo4j for debugging --- cpg-neo4j/build.gradle.kts | 2 ++ .../aisec/cpg_vis_neo4j/Application.kt | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/cpg-neo4j/build.gradle.kts b/cpg-neo4j/build.gradle.kts index 82377fb321..41b9e48ed9 100644 --- a/cpg-neo4j/build.gradle.kts +++ b/cpg-neo4j/build.gradle.kts @@ -56,4 +56,6 @@ dependencies { annotationProcessor(libs.picocli.codegen) testImplementation(testFixtures(projects.cpgCore)) + // For testing, remove later! + implementation(projects.cpgAnalysis) } diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index f584b4ca0c..5d44e01588 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -27,7 +27,10 @@ package de.fraunhofer.aisec.cpg_vis_neo4j import com.fasterxml.jackson.databind.ObjectMapper import de.fraunhofer.aisec.cpg.* +import de.fraunhofer.aisec.cpg.analysis.collectioneval.CollectionSizeEvaluator import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile +import de.fraunhofer.aisec.cpg.graph.nodes +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.passes.* import java.io.File @@ -614,6 +617,21 @@ class Application : Callable { "Benchmark: analyzing code in " + (analyzingTime - startTime) / S_TO_MS_FACTOR + " s." ) + // For Testing, remove later! + val nodes = translationResult.nodes + val targetNodes = nodes.filter { it.name.localName == "a" } + val focusNode = + targetNodes.first { + it.location?.region?.startLine == 53 && + it is Reference && + it.type.name.toString() == + "java.util.LinkedList" // "int[]" // + // "java.util.LinkedList" + } + val size = CollectionSizeEvaluator().evaluate(focusNode) + println(size) + return EXIT_SUCCESS + exportJsonFile?.let { exportToJson(translationResult, it) } if (!noNeo4j) { pushToNeo4j(translationResult) From 899f91933230efed33f9f1a83a9ffc0cece51f9b Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 19 Aug 2024 11:02:46 +0200 Subject: [PATCH 13/58] rework lattice intervals to support negative values --- .../LatticeInterval.kt | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) rename cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/{collectioneval => abstracteval}/LatticeInterval.kt (79%) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt similarity index 79% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 58557ee879..8addf20fa7 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis.collectioneval +package de.fraunhofer.aisec.cpg.analysis.abstracteval sealed class LatticeInterval { object BOTTOM : LatticeInterval() @@ -39,7 +39,9 @@ sealed class LatticeInterval { sealed class Bound { data class Value(val value: Int) : Bound() - data object TOP : Bound() + // necessary values for widening and narrowing + data object NEGATIVE_INFINITE: Bound() + data object INFINITE: Bound() } // Addition operator @@ -107,14 +109,14 @@ sealed class LatticeInterval { max(this.lower, other.lower) == other.lower -> { this.lower } - else -> Bound.Value(0) + else -> Bound.NEGATIVE_INFINITE } val upper: Bound = when { max(this.upper, other.upper) == this.upper -> { this.upper } - else -> Bound.TOP + else -> Bound.INFINITE } return Bounded(lower, upper) } @@ -126,14 +128,14 @@ sealed class LatticeInterval { } val lower: Bound = when { - this.lower == Bound.Value(0) -> { + this.lower == Bound.NEGATIVE_INFINITE -> { other.lower } else -> this.lower } val upper: Bound = when { - this.upper == Bound.TOP -> { + this.upper == Bound.INFINITE -> { other.upper } else -> this.upper @@ -143,8 +145,8 @@ sealed class LatticeInterval { private fun min(one: Bound, other: Bound): Bound { return when { - one is Bound.TOP -> other - other is Bound.TOP -> one + one is Bound.INFINITE || other is Bound.NEGATIVE_INFINITE -> other + other is Bound.INFINITE || one is Bound.NEGATIVE_INFINITE -> one one is Bound.Value && other is Bound.Value -> Bound.Value(kotlin.math.min(one.value, other.value)) else -> throw IllegalArgumentException("Unsupported interval type") @@ -153,7 +155,8 @@ sealed class LatticeInterval { private fun max(one: Bound, other: Bound): Bound { return when { - one is Bound.TOP || other is Bound.TOP -> Bound.TOP + one is Bound.INFINITE || other is Bound.NEGATIVE_INFINITE -> one + other is Bound.INFINITE || one is Bound.NEGATIVE_INFINITE -> other one is Bound.Value && other is Bound.Value -> Bound.Value(kotlin.math.max(one.value, other.value)) else -> throw IllegalArgumentException("Unsupported interval type") @@ -162,7 +165,11 @@ sealed class LatticeInterval { private fun addBounds(a: Bound, b: Bound): Bound { return when { - a is Bound.TOP || b is Bound.TOP -> Bound.TOP + // -∞ + ∞ is not an allowed operation + a is Bound.INFINITE && b !is Bound.NEGATIVE_INFINITE -> Bound.INFINITE + a is Bound.NEGATIVE_INFINITE && b !is Bound.INFINITE -> Bound.NEGATIVE_INFINITE + b is Bound.INFINITE && a !is Bound.NEGATIVE_INFINITE -> Bound.INFINITE + b is Bound.NEGATIVE_INFINITE && a !is Bound.INFINITE -> Bound.NEGATIVE_INFINITE a is Bound.Value && b is Bound.Value -> Bound.Value(a.value + b.value) else -> throw IllegalArgumentException("Unsupported bound type") } @@ -170,9 +177,11 @@ sealed class LatticeInterval { private fun subtractBounds(a: Bound, b: Bound): Bound { return when { - a is Bound.TOP -> Bound.TOP - b is Bound.TOP -> Bound.Value(0) - a == Bound.Value(0) -> Bound.Value(0) + // ∞ - ∞ is not an allowed operation + a is Bound.INFINITE && b !is Bound.INFINITE -> Bound.INFINITE + a is Bound.NEGATIVE_INFINITE && b !is Bound.NEGATIVE_INFINITE -> Bound.NEGATIVE_INFINITE + b is Bound.INFINITE && a !is Bound.INFINITE -> Bound.NEGATIVE_INFINITE + b is Bound.NEGATIVE_INFINITE && a !is Bound.NEGATIVE_INFINITE -> Bound.INFINITE a is Bound.Value && b is Bound.Value -> Bound.Value(a.value - b.value) else -> throw IllegalArgumentException("Unsupported bound type") } From ecc8035d49fcd6f48e631a5322c3663baf915417 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 19 Aug 2024 11:06:11 +0200 Subject: [PATCH 14/58] rename target package to circumvent gitignore --- .../AbstractEvaluator.kt} | 0 .../value}/Array.kt | 17 +--- .../analysis/abstracteval/value/Integer.kt | 96 +++++++++++++++++++ .../value}/MutableList.kt | 19 +--- .../value/Value.kt} | 17 +++- 5 files changed, 116 insertions(+), 33 deletions(-) rename cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/{collectioneval/CollectionSizeEvaluator.kt => abstracteval/AbstractEvaluator.kt} (100%) rename cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/{collectioneval/collection => abstracteval/value}/Array.kt (82%) create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt rename cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/{collectioneval/collection => abstracteval/value}/MutableList.kt (87%) rename cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/{collectioneval/collection/Collection.kt => abstracteval/value/Value.kt} (74%) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt similarity index 100% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/CollectionSizeEvaluator.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt similarity index 82% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt index aeb532d55e..b382ddc6e7 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Array.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt @@ -23,20 +23,18 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis.collectioneval.collection +package de.fraunhofer.aisec.cpg.analysis.abstracteval.value -import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.IntegerType import de.fraunhofer.aisec.cpg.query.value import org.apache.commons.lang3.NotImplementedException -class Array : Collection { +class Array : Value { override fun applyEffect( current: LatticeInterval, node: Node, @@ -46,15 +44,6 @@ class Array : Collection { return current to false } - override fun getInitializer(node: Node?): Node? { - return when (node) { - null -> null!! - is Reference -> getInitializer(node.refersTo) - is VariableDeclaration -> node.initializer!! - else -> getInitializer(node.prevDFG.firstOrNull()) - } - } - override fun getInitialRange(initializer: Node): LatticeInterval { // Consider multi-dimensional arrays (matrices) val size = getSize(initializer) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt new file mode 100644 index 0000000000..99c5dab8dc --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -0,0 +1,96 @@ +/* + * 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.cpg.analysis.abstracteval.value + +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator +import org.apache.commons.lang3.NotImplementedException + +class Integer : Value { + override fun applyEffect( + current: LatticeInterval, + node: Node, + name: String + ): Pair { + // Branching nodes have to be assumed to have an effect + if (node is BranchingNode) { + return current to true + } + // TODO: recursively evaluate right-hand-side to narrow down results + if (node is UnaryOperator) { + if (node.input.code == name) { + return when (node.operatorCode) { + "++" -> { + val oneInterval = LatticeInterval.Bounded(1, 1) + current + oneInterval to true + } + "--" -> { + val oneInterval = LatticeInterval.Bounded(1, 1) + current - oneInterval to true + } + else -> current to false + } + } + } else if (node is AssignExpression) { + if (node.lhs.any { it.code == name }) { + return when (node.operatorCode) { + "+=" -> { + val openUpper = LatticeInterval.Bounded(0, LatticeInterval.Bound.INFINITE) + current + openUpper to true + } + "-=" -> { + val zeroInterval = LatticeInterval.Bounded(0, 0) + current.join(zeroInterval) to true + } + "*=" -> { + TODO() + } + "/=" -> { + TODO() + } + "%=" -> { + TODO() + } + else -> current to false + } + } + } + return current to false + } + + override fun getInitialRange(initializer: Node): LatticeInterval { + val value = + when (initializer) { + is Literal<*> -> initializer.value as? Int ?: throw NotImplementedException() + else -> throw NotImplementedException() + } + return LatticeInterval.Bounded(value, value) + } +} diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt similarity index 87% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt index cb0e5688b8..ff996c2a23 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt @@ -23,20 +23,18 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis.collectioneval.collection +package de.fraunhofer.aisec.cpg.analysis.abstracteval.value -import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.graph.types.IntegerType import org.apache.commons.lang3.NotImplementedException -class MutableList : Collection { +class MutableList : Value { override fun applyEffect( current: LatticeInterval, node: Node, @@ -64,7 +62,7 @@ class MutableList : Collection { // TODO: this should trigger another List size evaluation for the argument! // also check and prevent -1 result "addAll" -> { - val openUpper = LatticeInterval.Bounded(0, LatticeInterval.Bound.TOP) + val openUpper = LatticeInterval.Bounded(0, LatticeInterval.Bound.INFINITE) current + openUpper to true } "clear" -> { @@ -93,15 +91,6 @@ class MutableList : Collection { } } - override fun getInitializer(node: Node?): Node? { - return when (node) { - null -> null!! - is Reference -> getInitializer(node.refersTo) - is VariableDeclaration -> node.initializer!! - else -> getInitializer(node.prevDFG.firstOrNull()) - } - } - override fun getInitialRange(initializer: Node): LatticeInterval { when (initializer) { is MemberCallExpression -> { diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt similarity index 74% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt index f2dbb417cc..49d83f7eb6 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/collectioneval/collection/Collection.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt @@ -23,12 +23,14 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis.collectioneval.collection +package de.fraunhofer.aisec.cpg.analysis.abstracteval.value -import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference -interface Collection { +interface Value { /** * Applies the effect of a Node to the Interval describing possible values of a collection. Also * returns true if the node was "valid" node that could have an influence on the Interval. @@ -44,7 +46,14 @@ interface Collection { name: String ): Pair - fun getInitializer(node: Node?): Node? + fun getInitializer(node: Node?): Node? { + return when (node) { + null -> null!! + is Reference -> getInitializer(node.refersTo) + is VariableDeclaration -> node.initializer!! + else -> getInitializer(node.prevDFG.firstOrNull()) + } + } fun getInitialRange(initializer: Node): LatticeInterval } From c6122c6bd4d8b7bfec15440b15a0f18e1d833187 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 19 Aug 2024 12:48:49 +0200 Subject: [PATCH 15/58] implement comparable for the LatticeInterval --- .../abstracteval/AbstractEvaluator.kt | 31 ++++++----- .../analysis/abstracteval/LatticeInterval.kt | 53 ++++++++++++++++++- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index ebcb2fb262..99df9b47b2 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -23,14 +23,14 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis.collectioneval +package de.fraunhofer.aisec.cpg.analysis.abstracteval -import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.Array -import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.Collection -import de.fraunhofer.aisec.cpg.analysis.collectioneval.collection.MutableList +import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Array +import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Integer +import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.MutableList +import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Value import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference @@ -38,7 +38,7 @@ import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import org.apache.commons.lang3.NotImplementedException -class CollectionSizeEvaluator { +class AbstractEvaluator { fun evaluate(node: Node): LatticeInterval { val name = node.name.toString() val type = getType(node) @@ -55,18 +55,18 @@ class CollectionSizeEvaluator { return range } - private fun getInitializerOf(node: Node, type: KClass): Node? { + private fun getInitializerOf(node: Node, type: KClass): Node? { return type.createInstance().getInitializer(node) } - private fun getInitialRange(initializer: Node, type: KClass): LatticeInterval { + private fun getInitialRange(initializer: Node, type: KClass): LatticeInterval { return type.createInstance().getInitialRange(initializer) } private fun LatticeInterval.applyEffect( node: Node, name: String, - type: KClass + type: KClass ): Pair { return type.createInstance().applyEffect(this, node, name) } @@ -78,7 +78,7 @@ class CollectionSizeEvaluator { * @return A Kotlin class representing the collection that contains the necessary analysis * functions */ - private fun getType(node: Node): KClass { + private fun getType(node: Node): KClass { if (node !is Reference) { throw NotImplementedException() } @@ -87,6 +87,7 @@ class CollectionSizeEvaluator { // TODO: could be linkedList, arrayList, ... name.startsWith("java.util.List") -> MutableList::class name.endsWith("[]") -> Array::class + name == "int" -> Integer::class else -> MutableList::class // throw NotImplementedException() } } @@ -106,7 +107,7 @@ class CollectionSizeEvaluator { range: LatticeInterval, node: Node, name: String, - type: KClass, + type: KClass, goalNode: Node ): Pair { return when (node) { @@ -135,7 +136,7 @@ class CollectionSizeEvaluator { range: LatticeInterval, node: Node, name: String, - type: KClass, + type: KClass, goalNode: Node ): Pair { val afterLoop = node.nextEOG[1] @@ -185,6 +186,10 @@ class CollectionSizeEvaluator { // we assume that the last nextEOG always points to the node after the branch! current = current.nextEOG.last() } + // Stop if the body contains no valid nodes + if (body.isEmpty()) { + return range to afterLoop + } // Initialize the intervals for the previous loop iteration val prevBodyIntervals = Array(body.size) { range } // WIDENING @@ -247,7 +252,7 @@ class CollectionSizeEvaluator { range: LatticeInterval, node: Node, name: String, - type: KClass, + type: KClass, goalNode: Node ): Pair { val mergeNode = findMergeNode(node) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 8addf20fa7..5c11bfe32a 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -25,7 +25,9 @@ */ package de.fraunhofer.aisec.cpg.analysis.abstracteval -sealed class LatticeInterval { +import de.fraunhofer.aisec.cpg.helpers.LatticeElement + +sealed class LatticeInterval : Comparable { object BOTTOM : LatticeInterval() data class Bounded(val lower: Bound, val upper: Bound) : LatticeInterval() { @@ -36,12 +38,39 @@ sealed class LatticeInterval { constructor(lower: Bound, upper: Int) : this(lower, Bound.Value(upper)) } - sealed class Bound { + sealed class Bound : Comparable { data class Value(val value: Int) : Bound() // necessary values for widening and narrowing data object NEGATIVE_INFINITE: Bound() data object INFINITE: Bound() + + override fun compareTo(other: Bound): Int { + return when { + this is NEGATIVE_INFINITE && other !is NEGATIVE_INFINITE -> -1 + this is INFINITE && other !is INFINITE -> 1 + other is NEGATIVE_INFINITE && this !is NEGATIVE_INFINITE -> 1 + other is INFINITE && this !is INFINITE -> -1 + this is Value && other is Value -> this.value.compareTo(other.value) + else -> 0 + } + } + } + + // Comparing two Intervals. They are treated as equal if they overlap + override fun compareTo(other: LatticeInterval): Int { + return when { + this is BOTTOM && other !is BOTTOM -> -1 + other is BOTTOM && this !is BOTTOM -> 1 + this is Bounded && other is Bounded -> { + when { + this.lower > other.upper -> 1 + this.upper < other.lower -> -1 + else -> 0 + } + } + else -> 0 + } } // Addition operator @@ -194,3 +223,23 @@ sealed class LatticeInterval { } } } + +class IntervalLattice(override val elements: LatticeInterval) : LatticeElement(elements) { + override fun compareTo(other: LatticeElement): Int { + return this.compareTo(other) + } + + // TODO: What is the LUB and why does a single Element need to implement this operation? + // is seems to just be the operation performed by the worklist... in our case widening (and then narrowing) + override fun lub(other: LatticeElement): LatticeElement { + TODO("Not yet implemented") + } + + override fun duplicate(): LatticeElement { + return when { + elements is LatticeInterval.Bounded -> + IntervalLattice(LatticeInterval.Bounded(elements.lower, elements.upper)) + else -> IntervalLattice(LatticeInterval.BOTTOM) + } + } +} From fb884f57f3dd8ecb58f5833f59d4d06bd5c69178 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 21 Aug 2024 12:38:54 +0200 Subject: [PATCH 16/58] implement the IntervalState --- .../abstracteval/AbstractEvaluator.kt | 14 +++ .../analysis/abstracteval/LatticeInterval.kt | 114 +++++++++++++++++- .../aisec/cpg/helpers/EOGWorklist.kt | 3 +- 3 files changed, 124 insertions(+), 7 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index 99df9b47b2..908d008865 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -34,9 +34,12 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.helpers.State +import de.fraunhofer.aisec.cpg.helpers.Worklist import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import org.apache.commons.lang3.NotImplementedException +import java.util.IdentityHashMap class AbstractEvaluator { fun evaluate(node: Node): LatticeInterval { @@ -190,6 +193,17 @@ class AbstractEvaluator { if (body.isEmpty()) { return range to afterLoop } + + // Initializes the states at the beginning of the widening and the respective worklist + val loopState = IdentityHashMap>() + for (n in body) { + // Initialize as empty state with intention to be widened + loopState[n] = IntervalState(IntervalState.Mode.WIDEN) + } + val worklist = Worklist(loopState) + + // TODO: use the worklist for the looping + // Initialize the intervals for the previous loop iteration val prevBodyIntervals = Array(body.size) { range } // WIDENING diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 5c11bfe32a..03297ce3a7 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -25,7 +25,9 @@ */ package de.fraunhofer.aisec.cpg.analysis.abstracteval +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.helpers.LatticeElement +import de.fraunhofer.aisec.cpg.helpers.State sealed class LatticeInterval : Comparable { object BOTTOM : LatticeInterval() @@ -42,8 +44,9 @@ sealed class LatticeInterval : Comparable { data class Value(val value: Int) : Bound() // necessary values for widening and narrowing - data object NEGATIVE_INFINITE: Bound() - data object INFINITE: Bound() + data object NEGATIVE_INFINITE : Bound() + + data object INFINITE : Bound() override fun compareTo(other: Bound): Int { return when { @@ -224,15 +227,41 @@ sealed class LatticeInterval : Comparable { } } -class IntervalLattice(override val elements: LatticeInterval) : LatticeElement(elements) { +/** + * The [LatticeElement] that is used for worklist iteration. It wraps a single element of the type + * [LatticeInterval] + */ +class IntervalLattice(override val elements: LatticeInterval) : + LatticeElement(elements) { override fun compareTo(other: LatticeElement): Int { return this.compareTo(other) } + // Returns true whenever other is fully within this + fun contains(other: LatticeElement): Boolean { + if (this.elements is LatticeInterval.BOTTOM || other.elements is LatticeInterval.BOTTOM) { + return false + } + val thisInterval = this.elements as LatticeInterval.Bounded + val otherInterval = other.elements as LatticeInterval.Bounded + + return (thisInterval.lower <= otherInterval.lower && thisInterval.upper >= otherInterval.upper) + } + // TODO: What is the LUB and why does a single Element need to implement this operation? - // is seems to just be the operation performed by the worklist... in our case widening (and then narrowing) + // is seems to just be the operation performed by the worklist... in our case widening (and + // then narrowing) + // Use widening as the operation in question override fun lub(other: LatticeElement): LatticeElement { - TODO("Not yet implemented") + return IntervalLattice(this.elements.widen(other.elements)) + } + + fun widen(other: IntervalLattice): IntervalLattice { + return IntervalLattice(this.elements.widen(other.elements)) + } + + fun narrow(other: IntervalLattice): IntervalLattice { + return IntervalLattice(this.elements.narrow(other.elements)) } override fun duplicate(): LatticeElement { @@ -243,3 +272,78 @@ class IntervalLattice(override val elements: LatticeInterval) : LatticeElement() { + var function: (IntervalLattice, IntervalLattice) -> IntervalLattice + + /** + * An enum that holds the current mode of operation as this State may be used to apply either widening or narrowing + */ + enum class Mode { + WIDEN, + NARROW + } + + init { + function = when (mode) { + Mode.WIDEN -> IntervalLattice::widen + else -> IntervalLattice::narrow + } + } + + /** + * Checks if an update is necessary. This applies in the following cases: + * - If [other] contains nodes which are not present in `this` + * - If we want to apply widening and any new interval is not fully contained within the old interval + * - If we want to apply narrowing and any old interval is not fully contained within the new interval + * Otherwise, it does not modify anything. + */ + override fun needsUpdate(other: State): Boolean { + var update = false + for ((node, newLattice) in other) { + newLattice as IntervalLattice // TODO: does this cast make sense? + val current = this[node] as? IntervalLattice + update = update || intervalNeedsUpdate(current, newLattice, mode) + } + return update + } + + private fun intervalNeedsUpdate(current: IntervalLattice?, newLattice: IntervalLattice, mode: Mode): Boolean { + return when (mode) { + Mode.WIDEN -> current == null || !current.contains(newLattice) + else -> current == null || !newLattice.contains(current) + } + } + + /** + * Adds a new mapping from [newNode] to (a copy of) [newLatticeElement] to this object if + * [newNode] does not exist in this state yet. + * If it already exists, it computes either widening or narrowing between the `current` and the new interval. + * It returns whether the state has changed. + */ + override fun push( + newNode: de.fraunhofer.aisec.cpg.graph.Node, + newLatticeElement: LatticeElement? + ): Boolean { + if (newLatticeElement == null) { + return false + } + val current = this[newNode] as? IntervalLattice + newLatticeElement as IntervalLattice + // here we use our "intervalNeedsUpdate" function to determine if we have to do something + if (current != null && intervalNeedsUpdate(current, newLatticeElement, mode)) { + when (mode) { + Mode.WIDEN -> this[newNode] = current.widen(newLatticeElement) + else -> this[newNode] = current.narrow(newLatticeElement) + } + } else if (current != null) { + return false + } + else { + this[newNode] = newLatticeElement + } + return true + } +} \ No newline at end of file diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index 4dba6c4f4e..2bb1726df8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -149,8 +149,7 @@ open class State : HashMap>() { */ class Worklist() { /** A mapping of nodes to the state which is currently available there. */ - var globalState = IdentityHashMap>() - private set + private var globalState = IdentityHashMap>() /** A list of all nodes which have already been visited. */ private val alreadySeen = IdentitySet() From c6d3abbe13d6e3b53fded9782ab4c3dc9889a138 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 26 Aug 2024 10:42:51 +0200 Subject: [PATCH 17/58] update the evaluator name in tests --- .../de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index 52a2400129..56b237bcb3 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt @@ -25,8 +25,8 @@ */ package de.fraunhofer.aisec.cpg.analysis -import de.fraunhofer.aisec.cpg.analysis.collectioneval.CollectionSizeEvaluator -import de.fraunhofer.aisec.cpg.analysis.collectioneval.LatticeInterval +import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration @@ -132,7 +132,7 @@ class SizeEvaluatorTest { val printArg = printCall.arguments.first() assertNotNull(printArg) - val evaluator = CollectionSizeEvaluator() + val evaluator = AbstractEvaluator() val value = evaluator.evaluate(printArg) assertEquals(LatticeInterval.Bounded(0, 2), value) } From d231c6d96c934c407885a5924c6c5d355144e966 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 26 Aug 2024 10:43:28 +0200 Subject: [PATCH 18/58] implement equals check for intervals --- .../analysis/abstracteval/LatticeInterval.kt | 71 +++++++++++++------ 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 03297ce3a7..5105d34785 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -76,6 +76,24 @@ sealed class LatticeInterval : Comparable { } } + // Equals check only true if both Intervals are true or have the same boundaries + // Not the same as a zero result in compareTo! + override fun equals(other: Any?): Boolean { + return when (other) { + !is LatticeInterval -> return false + is BOTTOM -> this is BOTTOM + is Bounded -> { + when (this) { + is Bounded -> { + this.lower == other.lower && this.upper == other.upper + } + else -> false + } + } + else -> false + } + } + // Addition operator operator fun plus(other: LatticeInterval): LatticeInterval { return when { @@ -245,7 +263,8 @@ class IntervalLattice(override val elements: LatticeInterval) : val thisInterval = this.elements as LatticeInterval.Bounded val otherInterval = other.elements as LatticeInterval.Bounded - return (thisInterval.lower <= otherInterval.lower && thisInterval.upper >= otherInterval.upper) + return (thisInterval.lower <= otherInterval.lower && + thisInterval.upper >= otherInterval.upper) } // TODO: What is the LUB and why does a single Element need to implement this operation? @@ -273,13 +292,12 @@ class IntervalLattice(override val elements: LatticeInterval) : } } -class IntervalState( - private val mode: Mode -) : State() { +class IntervalState(private var mode: Mode) : State() { var function: (IntervalLattice, IntervalLattice) -> IntervalLattice /** - * An enum that holds the current mode of operation as this State may be used to apply either widening or narrowing + * An enum that holds the current mode of operation as this State may be used to apply either + * widening or narrowing */ enum class Mode { WIDEN, @@ -287,20 +305,24 @@ class IntervalState( } init { - function = when (mode) { - Mode.WIDEN -> IntervalLattice::widen - else -> IntervalLattice::narrow - } + function = + when (mode) { + Mode.WIDEN -> IntervalLattice::widen + else -> IntervalLattice::narrow + } } /** * Checks if an update is necessary. This applies in the following cases: - * - If [other] contains nodes which are not present in `this` - * - If we want to apply widening and any new interval is not fully contained within the old interval - * - If we want to apply narrowing and any old interval is not fully contained within the new interval - * Otherwise, it does not modify anything. + * - If [other] contains nodes which are not present in `this` + * - If we want to apply widening and any new interval is not fully contained within the old + * interval + * - If we want to apply narrowing and any old interval is not fully contained within the new + * interval Otherwise, it does not modify anything. */ - override fun needsUpdate(other: State): Boolean { + override fun needsUpdate( + other: State + ): Boolean { var update = false for ((node, newLattice) in other) { newLattice as IntervalLattice // TODO: does this cast make sense? @@ -310,7 +332,11 @@ class IntervalState( return update } - private fun intervalNeedsUpdate(current: IntervalLattice?, newLattice: IntervalLattice, mode: Mode): Boolean { + private fun intervalNeedsUpdate( + current: IntervalLattice?, + newLattice: IntervalLattice, + mode: Mode + ): Boolean { return when (mode) { Mode.WIDEN -> current == null || !current.contains(newLattice) else -> current == null || !newLattice.contains(current) @@ -319,9 +345,9 @@ class IntervalState( /** * Adds a new mapping from [newNode] to (a copy of) [newLatticeElement] to this object if - * [newNode] does not exist in this state yet. - * If it already exists, it computes either widening or narrowing between the `current` and the new interval. - * It returns whether the state has changed. + * [newNode] does not exist in this state yet. If it already exists, it computes either widening + * or narrowing between the `current` and the new interval. It returns whether the state has + * changed. */ override fun push( newNode: de.fraunhofer.aisec.cpg.graph.Node, @@ -340,10 +366,13 @@ class IntervalState( } } else if (current != null) { return false - } - else { + } else { this[newNode] = newLatticeElement } return true } -} \ No newline at end of file + + fun changeMode(mode: Mode) { + this.mode = mode + } +} From 54210d95ec1357cdab6a8e259b17164a4be796da Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 26 Aug 2024 10:43:59 +0200 Subject: [PATCH 19/58] add method stub for condition evaluation --- .../abstracteval/AbstractEvaluator.kt | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index 908d008865..bfed3d2d9d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -33,13 +33,11 @@ import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference -import de.fraunhofer.aisec.cpg.helpers.State -import de.fraunhofer.aisec.cpg.helpers.Worklist import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import org.apache.commons.lang3.NotImplementedException -import java.util.IdentityHashMap class AbstractEvaluator { fun evaluate(node: Node): LatticeInterval { @@ -194,16 +192,6 @@ class AbstractEvaluator { return range to afterLoop } - // Initializes the states at the beginning of the widening and the respective worklist - val loopState = IdentityHashMap>() - for (n in body) { - // Initialize as empty state with intention to be widened - loopState[n] = IntervalState(IntervalState.Mode.WIDEN) - } - val worklist = Worklist(loopState) - - // TODO: use the worklist for the looping - // Initialize the intervals for the previous loop iteration val prevBodyIntervals = Array(body.size) { range } // WIDENING @@ -325,4 +313,15 @@ class AbstractEvaluator { } } } + + /** + * Returns 0 if the condition evaluates to True and 1 if it evaluates to false. + * If the outcome cannot be deduced it returns -1. + * @param condition The Expression used as branch condition + * @return 0, 1 or -1 depending on the Boolean evaluation + */ + private fun evaluateCondition(condition: Expression): Int { + // TODO: this method needs to try and evaluate branch conditions to predict the outcome + return 0 + } } From 344ea588406a4b4974ef71e78e4cec698f88fb8a Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 3 Oct 2024 10:46:17 +0200 Subject: [PATCH 20/58] Spotless --- .../aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index bfed3d2d9d..c2e98e9c19 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -315,8 +315,9 @@ class AbstractEvaluator { } /** - * Returns 0 if the condition evaluates to True and 1 if it evaluates to false. - * If the outcome cannot be deduced it returns -1. + * Returns 0 if the condition evaluates to True and 1 if it evaluates to false. If the outcome + * cannot be deduced it returns -1. + * * @param condition The Expression used as branch condition * @return 0, 1 or -1 depending on the Boolean evaluation */ From 52cb857457fc4cc5ef9b13fa310b7e8d3af77102 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 16 Oct 2024 13:31:08 +0200 Subject: [PATCH 21/58] Squashed commit of the following: commit 5c20b05cfab4c0ea1e32a5e01378a512275f943c Author: Robert Haimerl Date: Wed Oct 16 13:25:06 2024 +0200 fix breaking merge changes commit cc8eb48c1d4134cbbdc637fda653e2e4b2e4a4b7 Merge: 3e3c094ef2 344ea58840 Author: Robert Haimerl Date: Wed Oct 16 13:08:46 2024 +0200 Merge branch 'rh/abstract-value-analysis' into rh/abstract-value-analysis-worklist # Conflicts: # cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt # cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt commit 3e3c094ef2fed2e041a994bbbe92eb0871a224d0 Author: Robert Haimerl Date: Wed Oct 16 13:04:45 2024 +0200 remove redundant pushes to the worklist itself commit 6d731f27d2dcad094a623be758b6f97c9cf59ba4 Author: Robert Haimerl Date: Wed Oct 16 13:04:21 2024 +0200 override methods to use custom functionality in IntervalStates commit 1eb5a5194396cd8c9ff2a1f73df0d8e0078a17c5 Author: Robert Haimerl Date: Wed Oct 16 13:03:57 2024 +0200 enhance analysis for simple value operations commit 108f374a6ccddf2f3723b96a9c43ca74d9c55bb4 Author: Robert Haimerl Date: Wed Oct 16 09:56:22 2024 +0200 join intervals for multiple EOG (branch joins) commit d4383b44f494afbceb9e1fbf1cb0167031e20438 Author: Robert Haimerl Date: Wed Oct 16 09:33:41 2024 +0200 revert the change to the worklist pop to make it FIFO again commit 46798c5fce48f78876adfd947043d4999ef94c8a Author: Robert Haimerl Date: Wed Oct 16 09:33:23 2024 +0200 return a new altered state instead of directly modifying the current state commit c6346465864d97e2eb783e7b2e7044f2f621513e Author: Robert Haimerl Date: Mon Oct 14 13:12:46 2024 +0200 remove getInitialRange from the evaluator commit f984a2cce52a084942be6c3555ad6ac14cb50505 Author: Robert Haimerl Date: Mon Oct 14 13:11:58 2024 +0200 remove the "getInitialRange" method for values and instead mark declarations as operations with effect commit beb4d3897c841bc4a3d66314f5bdae11aafd1ae4 Author: Robert Haimerl Date: Mon Oct 14 12:26:22 2024 +0200 simplify evaluator to only use one worklist without special handling for loops and branches commit 6ec77dc6ebb7f3846bf319ee6d0ad45c8198c941 Author: Robert Haimerl Date: Mon Oct 14 12:25:04 2024 +0200 add "until" to iteration, add state information to Worklist, fix pop order commit 253debb2f55ff4ba5f6c4525e17d8957b0151d5c Author: Robert Haimerl Date: Mon Oct 14 12:23:17 2024 +0200 remove all modes from the IntervalState commit 31ca4ac6e3dc284c49c7e3afee56174b225f0d2f Author: Robert Haimerl Date: Mon Oct 14 12:17:45 2024 +0200 remove boolean information about whether the operation had an impact commit ccd839e6b64acb4c4861b3693d95106f6bb00e96 Author: Robert Haimerl Date: Mon Oct 14 09:17:11 2024 +0200 add three different state modes commit e0e1a28274f7d1ade5b4caefd2279acb7b6bffb5 Author: Robert Haimerl Date: Mon Oct 7 12:08:52 2024 +0200 rewrite the handleBranch branch commit 672f27f898242a0f3c144ee9fe78f551482f24cb Author: Robert Haimerl Date: Mon Oct 7 11:45:10 2024 +0200 rewrite the handleLoop branch commit 33f35313bfb573ed2b614a9308f52f41af4a5197 Author: Robert Haimerl Date: Mon Oct 7 10:54:15 2024 +0200 rewrite the applyEffect branch commit ca88bb7c83c4628d53f825ac0e45c3e67d07c4e8 Author: Robert Haimerl Date: Mon Oct 7 10:20:39 2024 +0200 rewrite evaluate function to use "iterateEOG" --- .../abstracteval/AbstractEvaluator.kt | 371 ++++++------------ .../analysis/abstracteval/LatticeInterval.kt | 111 ++---- .../cpg/analysis/abstracteval/value/Array.kt | 19 +- .../analysis/abstracteval/value/Integer.kt | 95 +++-- .../abstracteval/value/MutableList.kt | 57 ++- .../cpg/analysis/abstracteval/value/Value.kt | 20 +- .../aisec/cpg/helpers/EOGWorklist.kt | 108 +++-- .../aisec/cpg_vis_neo4j/Application.kt | 9 +- 8 files changed, 327 insertions(+), 463 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index c2e98e9c19..b5291b3b5d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -29,47 +29,133 @@ import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Array import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Integer import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.MutableList import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Value -import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.statements.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.helpers.State +import de.fraunhofer.aisec.cpg.helpers.Worklist +import de.fraunhofer.aisec.cpg.helpers.iterateEOG import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import org.apache.commons.lang3.NotImplementedException class AbstractEvaluator { + // The node for which we want to get the value + private lateinit var goalNode: Node + // The name of the value we are analyzing + private lateinit var targetName: String + // The type of the value we are analyzing + private lateinit var targetType: KClass + fun evaluate(node: Node): LatticeInterval { - val name = node.name.toString() - val type = getType(node) - val initializer = getInitializerOf(node, type)!! - var range = getInitialRange(initializer, type) + goalNode = node + targetName = node.name.toString() + targetType = getType(node) + val initializer = getInitializerOf(node, targetType)!! + // evaluate effect of each operation on the list until we reach "node" - var current = initializer - do { - val (newRange, newCurrent) = handleNext(range, current, name, type, node) - range = newRange - current = newCurrent - } while (current != node) + val startState = IntervalState() + startState.push(initializer, IntervalLattice(LatticeInterval.BOTTOM)) + val finalState = iterateEOG(initializer, startState, ::handleNode, goalNode) + // TODO: null-safety + return finalState!![node]!!.elements + } + + /** + * This function delegates to the right handler depending on the next node. This is the handler + * used in _iterateEOG_ to correctly handle complex statements. + * + * @param currentNode The current node + * @param state The state for the current node + * @param worklist The whole worklist to manually handle complex scenarios if necessary + * @return The updated state after handling the current node + */ + private fun handleNode( + currentNode: Node, + state: State, + worklist: Worklist + ): State { + // If the current node is already done + // (prevents infinite loop and unnecessary double-checking) + if (worklist.isDone(currentNode)) { + // Mark following nodes as DONE if they only have this as previous EOG or all are DONE + // In other cases, converging branches may still change the node + currentNode.nextEOG.forEach { next -> + if ( + next.prevEOG.singleOrNull() == currentNode || + next.prevEOG.all { worklist.isDone(it) } + ) { + worklist.evaluationStateMap[next] = Worklist.EvaluationState.DONE + } + } + return state + } + + // First calculate the effect + val previousInterval = state[currentNode]?.elements + val newInterval = state.calculateEffect(currentNode) + val newState = state.duplicate() + + // If it was already seen exactly once or is known to need widening + if (worklist.needsWidening(currentNode)) { + // Widen the interval + val widenedInterval = previousInterval!!.widen(newInterval) + // Check if the widening caused a change + if (widenedInterval != previousInterval) { + // YES: mark next nodes as needs widening, add them to worklist + // Overwrite current interval, mark this node as needs narrowing + newState[currentNode] = IntervalLattice(widenedInterval) + currentNode.nextEOG.forEach { + worklist.evaluationStateMap[it] = Worklist.EvaluationState.WIDENING + } + worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.NARROWING + } else { + // NO: mark the current node as DONE + worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.DONE + } + } + + // If it is marked as in need of narrowing + else if (worklist.needsNarrowing(currentNode)) { + // Narrow the interval + val narrowedInterval = previousInterval!!.narrow(newInterval) + // Check if the narrowing caused a change + if (narrowedInterval != previousInterval) { + // YES: overwrite and keep this node marked + // Mark next nodes as need narrowing and add to worklist + newState[currentNode] = IntervalLattice(narrowedInterval) + currentNode.nextEOG.forEach { + worklist.evaluationStateMap[it] = Worklist.EvaluationState.NARROWING + } + } else { + // NO: mark the node as DONE + worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.DONE + } + } + + // If it was seen for the first time apply the effect and mark it as "NEEDS WIDENING" + // We cannot use the "already_seen" field as it is set before this handler is called + else { + newState[currentNode] = IntervalLattice(newInterval) + worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.WIDENING + } + + // We propagate the current Interval to all successor nodes which are empty + // If the next EOG already has a value we need to join all of its predecessors + // This is implemented in IntervalState.push + // Only do this if we have not reached the goal node + if (currentNode != goalNode) { + currentNode.nextEOG.forEach { newState.push(it, newState[currentNode]) } + } - return range + return newState } private fun getInitializerOf(node: Node, type: KClass): Node? { return type.createInstance().getInitializer(node) } - private fun getInitialRange(initializer: Node, type: KClass): LatticeInterval { - return type.createInstance().getInitialRange(initializer) - } - - private fun LatticeInterval.applyEffect( - node: Node, - name: String, - type: KClass - ): Pair { - return type.createInstance().applyEffect(this, node, name) + private fun State.calculateEffect(node: Node): LatticeInterval { + return targetType.createInstance().applyEffect(this[node]!!.elements, node, targetName) } /** @@ -92,237 +178,4 @@ class AbstractEvaluator { else -> MutableList::class // throw NotImplementedException() } } - - /** - * This function delegates to the right handler depending on the next node. Use this instead of - * directly calling _applyEffect_ to correctly handle complex statements. - * - * @param range The previous size range - * @param node The current node - * @param name The name of the collection variable - * @param type The type of the collection - * @param goalNode The target node for the analysis - * @return A Pair containing the new size range and the next node for the analysis - */ - private fun handleNext( - range: LatticeInterval, - node: Node, - name: String, - type: KClass, - goalNode: Node - ): Pair { - return when (node) { - is ForStatement, - is WhileStatement, - is ForEachStatement, - is DoStatement -> handleLoop(range, node, name, type, goalNode) - is BranchingNode -> handleBranch(range, node, name, type, goalNode) - else -> range.applyEffect(node, name, type).first to node.nextEOG.first() - } - } - - /** - * Handles the analysis of a Looping statement. It does so by filtering out uninteresting - * statements before applying widening and narrowing in each iteration. If the target node is - * included in the body, the returned node will be the target node. - * - * @param range The previous size range - * @param node The BranchingNode as head of the loop - * @param name The name of the collection variable - * @param type The type of the collection - * @param goalNode The target node for the analysis - * @return A Pair containing the new size range and the next node for the analysis - */ - private fun handleLoop( - range: LatticeInterval, - node: Node, - name: String, - type: KClass, - goalNode: Node - ): Pair { - val afterLoop = node.nextEOG[1] - val body = mutableListOf() - var newRange = range - val firstBodyStatement: Node? = - when (node) { - is ForStatement -> { - when (node.statement) { - // This cast is important! Otherwise, the wrong statements are returned - is Block -> (node.statement as Block).statements.firstOrNull() - null -> null - else -> node.statement - } - } - is WhileStatement -> { - when (node.statement) { - is Block -> (node.statement as Block).statements.firstOrNull() - null -> null - else -> node.statement - } - } - is ForEachStatement -> { - when (node.statement) { - is Block -> (node.statement as Block).statements.firstOrNull() - null -> null - else -> node.statement - } - } - is DoStatement -> { - when (node.statement) { - is Block -> (node.statement as Block).statements.firstOrNull() - null -> null - else -> node.statement - } - } - else -> throw NotImplementedException() - } - var current: Node? = firstBodyStatement - // Preprocessing: filter for valid nodes - while (current != null && current != afterLoop && current != node) { - // Only add the Statement if it affects the range - if (range.applyEffect(current, name, type).second) { - body.add(current) - } - // get the next node, skipping nested structures - // we assume that the last nextEOG always points to the node after the branch! - current = current.nextEOG.last() - } - // Stop if the body contains no valid nodes - if (body.isEmpty()) { - return range to afterLoop - } - - // Initialize the intervals for the previous loop iteration - val prevBodyIntervals = Array(body.size) { range } - // WIDENING - outer@ while (true) { - for (index in body.indices) { - // First apply the effect of the next node - val (lRange, _) = handleNext(newRange, body[index], name, type, goalNode) - newRange = lRange - // Then widen using the previous iteration - // Only widen for the first effective node in the loop (loop separator) - if (index == 0) { - newRange = prevBodyIntervals[index].widen(newRange) - } - // If nothing changed we can abort - if (newRange == prevBodyIntervals[index]) { - break@outer - } else { - prevBodyIntervals[index] = newRange - } - } - } - // NARROWING - outer@ while (true) { - for (index in body.indices) { - // First apply the effect of the next node - val (lRange, _) = handleNext(newRange, body[index], name, type, goalNode) - newRange = lRange - // Then narrow using the previous iteration - newRange = prevBodyIntervals[index].narrow(newRange) - // If ALL loop ranges are stable we can abort - if (index == body.size - 1 && newRange == prevBodyIntervals[index]) { - break@outer - } else { - prevBodyIntervals[index] = newRange - } - } - } - - // return goalNode as next node if it was in the loop to prevent skipping loop termination - // condition - if (body.contains(goalNode)) { - return newRange to goalNode - } - return newRange to afterLoop - } - - /** - * Handles the analysis of a Branching statement. It does so by evaluating the final ranges of - * each branch and taking the join over all of them. If the target node is included in any - * branch, the evaluation only uses this branch. - * - * @param range The previous size range - * @param node The BranchingNode as head of the branch - * @param name The name of the collection variable - * @param type The type of the collection - * @param goalNode The target node for the analysis - * @return A Pair containing the new size range and the next node for the analysis - */ - private fun handleBranch( - range: LatticeInterval, - node: Node, - name: String, - type: KClass, - goalNode: Node - ): Pair { - val mergeNode = findMergeNode(node) - val branchNumber = node.nextEOG.size - val finalBranchRanges = Array(branchNumber) { range } - for (i in 0 until branchNumber) { - var current = node.nextEOG[i] - // if we arrive at the mergeNode we are done with this branch - while (current != mergeNode) { - // If at any point we find the goal node in a branch, we stop and ignore other all - // branches - if (current == goalNode) { - return finalBranchRanges[i] to current - } - val (nextRange, nextNode) = - handleNext(finalBranchRanges[i], current, name, type, goalNode) - finalBranchRanges[i] = nextRange - current = nextNode - } - } - // Take the join of all branches since we do not know which was taken - val finalMergedRange = finalBranchRanges.reduce { acc, r -> acc.join(r) } - return finalMergedRange to mergeNode - } - - /** - * Finds the "MergeNode" as the first common node of all branches. - * - * @param node The BranchingNode that is the head of the branching statement - * @return The Node that is the end of the branching statement - */ - private fun findMergeNode(node: Node): Node { - if (node !is BranchingNode) { - return node.nextEOG.first() - } - - val branchNumber = node.nextEOG.size - val branches = Array(branchNumber) { Node() } - val visited = Array(branchNumber) { mutableSetOf() } - for (index in 0 until branchNumber) { - branches[index] = node.nextEOG[index] - } - while (true) { - for (index in 0 until branchNumber) { - val current = branches[index] - if (current in visited[index]) { - continue - } - visited[index].add(current) - // If all paths contain the current node it merges all branches - if (visited.all { it.contains(current) }) { - return current - } - val next = current.nextEOG.firstOrNull() ?: break - branches[index] = next - } - } - } - - /** - * Returns 0 if the condition evaluates to True and 1 if it evaluates to false. If the outcome - * cannot be deduced it returns -1. - * - * @param condition The Expression used as branch condition - * @return 0, 1 or -1 depending on the Boolean evaluation - */ - private fun evaluateCondition(condition: Expression): Int { - // TODO: this method needs to try and evaluate branch conditions to predict the outcome - return 0 - } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 5105d34785..3a4204ddaa 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -252,7 +252,7 @@ sealed class LatticeInterval : Comparable { class IntervalLattice(override val elements: LatticeInterval) : LatticeElement(elements) { override fun compareTo(other: LatticeElement): Int { - return this.compareTo(other) + return elements.compareTo(other.elements) } // Returns true whenever other is fully within this @@ -267,12 +267,9 @@ class IntervalLattice(override val elements: LatticeInterval) : thisInterval.upper >= otherInterval.upper) } - // TODO: What is the LUB and why does a single Element need to implement this operation? - // is seems to just be the operation performed by the worklist... in our case widening (and - // then narrowing) - // Use widening as the operation in question + // The least upper bound of two Intervals is given by the join operation override fun lub(other: LatticeElement): LatticeElement { - return IntervalLattice(this.elements.widen(other.elements)) + return IntervalLattice(this.elements.join(other.elements)) } fun widen(other: IntervalLattice): IntervalLattice { @@ -292,62 +289,11 @@ class IntervalLattice(override val elements: LatticeInterval) : } } -class IntervalState(private var mode: Mode) : State() { - var function: (IntervalLattice, IntervalLattice) -> IntervalLattice - - /** - * An enum that holds the current mode of operation as this State may be used to apply either - * widening or narrowing - */ - enum class Mode { - WIDEN, - NARROW - } - - init { - function = - when (mode) { - Mode.WIDEN -> IntervalLattice::widen - else -> IntervalLattice::narrow - } - } - - /** - * Checks if an update is necessary. This applies in the following cases: - * - If [other] contains nodes which are not present in `this` - * - If we want to apply widening and any new interval is not fully contained within the old - * interval - * - If we want to apply narrowing and any old interval is not fully contained within the new - * interval Otherwise, it does not modify anything. - */ - override fun needsUpdate( - other: State - ): Boolean { - var update = false - for ((node, newLattice) in other) { - newLattice as IntervalLattice // TODO: does this cast make sense? - val current = this[node] as? IntervalLattice - update = update || intervalNeedsUpdate(current, newLattice, mode) - } - return update - } - - private fun intervalNeedsUpdate( - current: IntervalLattice?, - newLattice: IntervalLattice, - mode: Mode - ): Boolean { - return when (mode) { - Mode.WIDEN -> current == null || !current.contains(newLattice) - else -> current == null || !newLattice.contains(current) - } - } - +class IntervalState : State() { /** * Adds a new mapping from [newNode] to (a copy of) [newLatticeElement] to this object if - * [newNode] does not exist in this state yet. If it already exists, it computes either widening - * or narrowing between the `current` and the new interval. It returns whether the state has - * changed. + * [newNode] does not exist in this state yet. If it already exists, it will compute the lub + * over the new lattice Element and all predecessors. It returns whether the state has changed. */ override fun push( newNode: de.fraunhofer.aisec.cpg.graph.Node, @@ -357,14 +303,19 @@ class IntervalState(private var mode: Mode) : State() { return false } val current = this[newNode] as? IntervalLattice - newLatticeElement as IntervalLattice - // here we use our "intervalNeedsUpdate" function to determine if we have to do something - if (current != null && intervalNeedsUpdate(current, newLatticeElement, mode)) { - when (mode) { - Mode.WIDEN -> this[newNode] = current.widen(newLatticeElement) - else -> this[newNode] = current.narrow(newLatticeElement) + if (current != null) { + val joinedElement = + newNode.prevEOG.fold(newLatticeElement) { acc, predecessor -> + if (this[predecessor]?.elements != null) { + acc.lub(this[predecessor]!!) + } else { + acc + } + } + if (joinedElement != this[newNode]) { + this[newNode] = joinedElement + return true } - } else if (current != null) { return false } else { this[newNode] = newLatticeElement @@ -372,7 +323,29 @@ class IntervalState(private var mode: Mode) : State() { return true } - fun changeMode(mode: Mode) { - this.mode = mode + /** + * Performs the same duplication as the parent function, but returns a [IntervalState] object + * instead. + */ + override fun duplicate(): State { + val clone = IntervalState() + for ((key, value) in this) { + clone[key] = value.duplicate() + } + return clone + } + + /** + * Performs the same lub function as the parent, but uses the [push] function from + * [LatticeInterval] + */ + override fun lub( + other: State + ): Pair, Boolean> { + var update = false + for ((node, newLattice) in other) { + update = push(node, newLattice) || update + } + return Pair(this, update) } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt index b382ddc6e7..ae8facf7d4 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.analysis.abstracteval.value import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression @@ -35,19 +36,13 @@ import de.fraunhofer.aisec.cpg.query.value import org.apache.commons.lang3.NotImplementedException class Array : Value { - override fun applyEffect( - current: LatticeInterval, - node: Node, - name: String - ): Pair { + override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { // There are no functions that change the size of a Java array without destroying it - return current to false - } - - override fun getInitialRange(initializer: Node): LatticeInterval { - // Consider multi-dimensional arrays (matrices) - val size = getSize(initializer) - return LatticeInterval.Bounded(size, size) + if (node is VariableDeclaration && node.initializer != null) { + val initValue = getSize(node.initializer!!) + return LatticeInterval.Bounded(initValue, initValue) + } + return current } private fun getSize(node: Node): Int { diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt index 99c5dab8dc..0853499e58 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -26,48 +26,96 @@ package de.fraunhofer.aisec.cpg.analysis.abstracteval.value import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval -import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator +import de.fraunhofer.aisec.cpg.query.value import org.apache.commons.lang3.NotImplementedException class Integer : Value { - override fun applyEffect( - current: LatticeInterval, - node: Node, - name: String - ): Pair { - // Branching nodes have to be assumed to have an effect - if (node is BranchingNode) { - return current to true + override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { + if (node is VariableDeclaration && node.initializer != null) { + val initValue = + when (val init = node.initializer) { + is Literal<*> -> init.value as? Int ?: throw NotImplementedException() + else -> throw NotImplementedException() + } + return LatticeInterval.Bounded(initValue, initValue) } - // TODO: recursively evaluate right-hand-side to narrow down results if (node is UnaryOperator) { if (node.input.code == name) { return when (node.operatorCode) { "++" -> { val oneInterval = LatticeInterval.Bounded(1, 1) - current + oneInterval to true + current + oneInterval } "--" -> { val oneInterval = LatticeInterval.Bounded(1, 1) - current - oneInterval to true + current - oneInterval } - else -> current to false + else -> current } } } else if (node is AssignExpression) { if (node.lhs.any { it.code == name }) { + // TODO: we need to evaluate the right hand side for all cases! return when (node.operatorCode) { + "=" -> { + var newInterval: LatticeInterval = current + // If the rhs is only a literal use this exact value + if (node.rhs.size == 1 && node.rhs[0] is Literal<*>) { + val value = node.rhs[0].value.value as? Int + if (value != null) { + newInterval = LatticeInterval.Bounded(value, value) + } + } else { + TODO() + } + newInterval + } "+=" -> { - val openUpper = LatticeInterval.Bounded(0, LatticeInterval.Bound.INFINITE) - current + openUpper to true + var newInterval: LatticeInterval = current + // If the rhs is only a literal we subtract this exact value + if (node.rhs.size == 1 && node.rhs[0] is Literal<*>) { + val value = node.rhs[0].value.value as? Int + if (value != null) { + val valueInterval = LatticeInterval.Bounded(value, value) + newInterval = current.plus(valueInterval) + } + } + // Per default set upper bound to infinite + else { + val joinInterval: LatticeInterval = + LatticeInterval.Bounded( + LatticeInterval.Bound.INFINITE, + LatticeInterval.Bound.INFINITE + ) + newInterval = current.join(joinInterval) + } + newInterval } "-=" -> { - val zeroInterval = LatticeInterval.Bounded(0, 0) - current.join(zeroInterval) to true + var newInterval: LatticeInterval = current + // If the rhs is only a literal we subtract this exact value + if (node.rhs.size == 1 && node.rhs[0] is Literal<*>) { + val value = node.rhs[0].value.value as? Int + if (value != null) { + val valueInterval = LatticeInterval.Bounded(value, value) + newInterval = current.minus(valueInterval) + } + } + // Per default set lower bound to negative infinite + else { + val joinInterval: LatticeInterval = + LatticeInterval.Bounded( + LatticeInterval.Bound.NEGATIVE_INFINITE, + LatticeInterval.Bound.NEGATIVE_INFINITE + ) + newInterval = current.join(joinInterval) + } + newInterval } "*=" -> { TODO() @@ -78,19 +126,10 @@ class Integer : Value { "%=" -> { TODO() } - else -> current to false + else -> current } } } - return current to false - } - - override fun getInitialRange(initializer: Node): LatticeInterval { - val value = - when (initializer) { - is Literal<*> -> initializer.value as? Int ?: throw NotImplementedException() - else -> throw NotImplementedException() - } - return LatticeInterval.Bounded(value, value) + return current } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt index ff996c2a23..d8f9a2c064 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt @@ -26,8 +26,8 @@ package de.fraunhofer.aisec.cpg.analysis.abstracteval.value import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval -import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression @@ -35,49 +35,54 @@ import de.fraunhofer.aisec.cpg.graph.types.IntegerType import org.apache.commons.lang3.NotImplementedException class MutableList : Value { - override fun applyEffect( - current: LatticeInterval, - node: Node, - name: String - ): Pair { + override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { + if (node is VariableDeclaration && node.initializer != null) { + when (val init = node.initializer) { + is MemberCallExpression -> { + val size = init.arguments.size + return LatticeInterval.Bounded(size, size) + } + is NewExpression -> { + // TODO: could have a collection as argument! + return LatticeInterval.Bounded(0, 0) + } + else -> throw NotImplementedException() + } + } // TODO: state can also be estimated by conditions! (if (l.size < 3) ...) // TODO: assignment -> new size - // Branching nodes have to be assumed to have an effect - if (node is BranchingNode) { - return current to true - } // State can only be directly changed via MemberCalls (add, clear, ...) if (node !is MemberCallExpression) { - return current to false + return current } // Only consider calls that have the subject as base if ((node.callee as? MemberExpression)?.base?.code != name) { - return current to false + return current } return when (node.name.localName) { "add" -> { val oneInterval = LatticeInterval.Bounded(1, 1) - current + oneInterval to true + current + oneInterval } // TODO: this should trigger another List size evaluation for the argument! // also check and prevent -1 result "addAll" -> { val openUpper = LatticeInterval.Bounded(0, LatticeInterval.Bound.INFINITE) - current + openUpper to true + current + openUpper } "clear" -> { - LatticeInterval.Bounded(0, 0) to true + LatticeInterval.Bounded(0, 0) } "remove" -> { // We have to differentiate between remove with index or object argument // Latter may do nothing if the element is not in the list if (node.arguments.first().type is IntegerType) { val oneInterval = LatticeInterval.Bounded(1, 1) - current - oneInterval to true + current - oneInterval } else { // TODO: If we know the list is empty, we know the operation has no effect val oneZeroInterval = LatticeInterval.Bounded(1, 0) - current - oneZeroInterval to true + current - oneZeroInterval } } // TODO: as optimization we could check whether the argument list is empty. @@ -85,23 +90,9 @@ class MutableList : Value { // possible outcomes "removeAll" -> { val zeroInterval = LatticeInterval.Bounded(0, 0) - current.join(zeroInterval) to true - } - else -> current to false - } - } - - override fun getInitialRange(initializer: Node): LatticeInterval { - when (initializer) { - is MemberCallExpression -> { - val size = initializer.arguments.size - return LatticeInterval.Bounded(size, size) - } - is NewExpression -> { - // TODO: could have a collection as argument! - return LatticeInterval.Bounded(0, 0) + current.join(zeroInterval) } - else -> throw NotImplementedException() + else -> current } } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt index 49d83f7eb6..fa85e1a082 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt @@ -31,29 +31,15 @@ import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference interface Value { - /** - * Applies the effect of a Node to the Interval describing possible values of a collection. Also - * returns true if the node was "valid" node that could have an influence on the Interval. - * - * Examples: - * - list.add(x) on [0, 0] -> ([1, 1], true) - * - list.clear(x) on [0, 0] -> ([0, 0], true) - * - println(list) on [0, 0] -> ([0, 0], false) - */ - fun applyEffect( - current: LatticeInterval, - node: Node, - name: String - ): Pair + /** Applies the effect of a Node to the Interval describing possible values of a collection. */ + fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval fun getInitializer(node: Node?): Node? { return when (node) { null -> null!! is Reference -> getInitializer(node.refersTo) - is VariableDeclaration -> node.initializer!! + is VariableDeclaration -> node else -> getInitializer(node.prevDFG.firstOrNull()) } } - - fun getInitialRange(initializer: Node): LatticeInterval } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index 59b59d67c9..c04525a8ff 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -73,9 +73,10 @@ class PowersetLattice(override val elements: IdentitySet) : } /** - * Stores the current state. I.e., it maps [K] (e.g. a [Node] or [Edge]) to a [LatticeElement]. It - * provides some useful functions e.g. to check if the mapping has to be updated (e.g. because there - * are new nodes or because a new lattice element is bigger than the old one). + * Stores the current state. I.e., it maps [K] (e.g. a [Node] or [PropertyEdge]) to a + * [LatticeElement]. It provides some useful functions e.g. to check if the mapping has to be + * updated (e.g. because there are new nodes or because a new lattice element is bigger than the old + * one). */ open class State : HashMap>() { @@ -153,6 +154,14 @@ class Worklist() { /** A list of all nodes which have already been visited. */ private val alreadySeen = IdentitySet() + enum class EvaluationState { + WIDENING, + NARROWING, + DONE + } + + val evaluationStateMap = IdentityHashMap() + constructor( globalState: IdentityHashMap> = IdentityHashMap>() ) : this() { @@ -217,6 +226,16 @@ class Worklist() { /** Checks if [currentNode] has already been visited before. */ fun hasAlreadySeen(currentNode: K) = currentNode in alreadySeen + /** Checks if [currentNode] needs to be widened. */ + fun needsWidening(currentNode: K) = evaluationStateMap[currentNode] == EvaluationState.WIDENING + + /** Checks if [currentNode] needs to be narrowed. */ + fun needsNarrowing(currentNode: K) = + evaluationStateMap[currentNode] == EvaluationState.NARROWING + + /** Checks if [currentNode] should not be changed anymore. */ + fun isDone(currentNode: K) = evaluationStateMap[currentNode] == EvaluationState.DONE + /** Computes the meet over paths for all the states in [globalState]. */ fun mop(): State? { val firstKey = globalState.keys.firstOrNull() @@ -246,48 +265,12 @@ inline fun iterateEOG( return iterateEOG(startNode, startState) { k, s, _ -> transformation(k, s) } } -/** - * Iterates through the worklist of the Evaluation Order Graph starting at [startNode] and with the - * [State] [startState]. For each node, the [transformation] is applied which should update the - * state. - * - * [transformation] receives the current [Node] popped from the worklist, the [State] at this node - * which is considered for this analysis and even the current [Worklist]. The worklist is given if - * we have to add more elements out-of-order e.g. because the EOG is traversed in an order which is - * not useful for this analysis. The [transformation] has to return the updated [State]. - */ inline fun iterateEOG( startNode: K, startState: State, transformation: (K, State, Worklist) -> State ): State? { - val initialState = IdentityHashMap>() - initialState[startNode] = startState - val worklist = Worklist(initialState) - worklist.push(startNode, startState) - - while (worklist.isNotEmpty()) { - val (nextNode, state) = worklist.pop() - - // This should check if we're not near the beginning/end of a basic block (i.e., there are - // no merge points or branches of the EOG nearby). If that's the case, we just parse the - // whole basic block and do not want to duplicate the state. Near the beginning/end, we do - // want to copy the state to avoid terminating the iteration too early by messing up with - // the state-changing checks. - val insideBB = - (nextNode.nextEOGEdges.size == 1 && - nextNode.prevEOGEdges.singleOrNull()?.start?.nextEOG?.size == 1) - val newState = - transformation(nextNode, if (insideBB) state else state.duplicate(), worklist) - if (worklist.update(nextNode, newState)) { - nextNode.nextEOGEdges.forEach { - if (it is K) { - worklist.push(it, newState) - } - } - } - } - return worklist.mop() + return iterateEOG(startNode, startState, transformation, null) } inline fun , N : Any, V> iterateEOG( @@ -334,3 +317,48 @@ inline fun , N : Any, V> iterateEOG( } return worklist.mop() } + +/** + * Iterates through the worklist of the Evaluation Order Graph starting at [startNode] and with the + * [State] [startState]. For each node, the [transformation] is applied which should update the + * state. When the [until] node is reached, its successors are not added to the worklist. + * + * [transformation] receives the current [Node] popped from the worklist, the [State] at this node + * which is considered for this analysis and even the current [Worklist]. The worklist is given if + * we have to add more elements out-of-order e.g. because the EOG is traversed in an order which is + * not useful for this analysis. The [transformation] has to return the updated [State]. + */ +inline fun iterateEOG( + startNode: K, + startState: State, + transformation: (K, State, Worklist) -> State, + until: Node? +): State? { + val initialState = IdentityHashMap>() + initialState[startNode] = startState + val worklist = Worklist(initialState) + worklist.push(startNode, startState) + + while (worklist.isNotEmpty()) { + val (nextNode, state) = worklist.pop() + + // This should check if we're not near the beginning/end of a basic block (i.e., there are + // no merge points or branches of the EOG nearby). If that's the case, we just parse the + // whole basic block and do not want to duplicate the state. Near the beginning/end, we do + // want to copy the state to avoid terminating the iteration too early by messing up with + // the state-changing checks. + val insideBB = + (nextNode.nextEOG.size == 1 && nextNode.prevEOG.singleOrNull()?.nextEOG?.size == 1) + val newState = + transformation(nextNode, if (insideBB) state else state.duplicate(), worklist) + + if (worklist.update(nextNode, newState) && nextNode != until) { + nextNode.nextEOG.forEach { + if (it is K && !worklist.isDone(it)) { + worklist.push(it, newState) + } + } + } + } + return worklist.mop() +} diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 8d140fab48..9b5a8ef24c 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -27,7 +27,7 @@ package de.fraunhofer.aisec.cpg_vis_neo4j import com.fasterxml.jackson.databind.ObjectMapper import de.fraunhofer.aisec.cpg.* -import de.fraunhofer.aisec.cpg.analysis.collectioneval.CollectionSizeEvaluator +import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile import de.fraunhofer.aisec.cpg.graph.nodes import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference @@ -624,13 +624,12 @@ class Application : Callable { val targetNodes = nodes.filter { it.name.localName == "a" } val focusNode = targetNodes.first { - it.location?.region?.startLine == 53 && + it.location?.region?.startLine == 101 && it is Reference && it.type.name.toString() == - "java.util.LinkedList" // "int[]" // - // "java.util.LinkedList" + "int" // "java.util.LinkedList" // "int[]" } - val size = CollectionSizeEvaluator().evaluate(focusNode) + val size = AbstractEvaluator().evaluate(focusNode) println(size) return EXIT_SUCCESS From 7028045823ea958d7c0a372fd0e22533be0bd142 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 21 Oct 2024 10:55:40 +0200 Subject: [PATCH 22/58] fix LatticeInterval.push to join with the previous value as it is already propagated from predecessors --- .../cpg/analysis/abstracteval/LatticeInterval.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 3a4204ddaa..1fec4755c7 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -304,14 +304,10 @@ class IntervalState : State() { } val current = this[newNode] as? IntervalLattice if (current != null) { - val joinedElement = - newNode.prevEOG.fold(newLatticeElement) { acc, predecessor -> - if (this[predecessor]?.elements != null) { - acc.lub(this[predecessor]!!) - } else { - acc - } - } + // Calculate the join of the new Element and the previous (propagated) value for the + // node + val joinedElement = current.lub(newLatticeElement) + // Use the joinedElement if it differs from before if (joinedElement != this[newNode]) { this[newNode] = joinedElement return true From 10f2fc21b54227f59e1eec7d04851bb245d64dc7 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 21 Oct 2024 11:11:41 +0200 Subject: [PATCH 23/58] Fix Declaration to only effect matching integers --- .../aisec/cpg/analysis/abstracteval/value/Integer.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt index 0853499e58..1913ea5cde 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -36,7 +36,9 @@ import org.apache.commons.lang3.NotImplementedException class Integer : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { - if (node is VariableDeclaration && node.initializer != null) { + if ( + node is VariableDeclaration && node.initializer != null && node.name.localName == name + ) { val initValue = when (val init = node.initializer) { is Literal<*> -> init.value as? Int ?: throw NotImplementedException() From 39db0ba8712205996fb43768e3928d9726abd0cb Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 21 Oct 2024 12:56:38 +0200 Subject: [PATCH 24/58] correctly propagate widening only within loops --- .../abstracteval/AbstractEvaluator.kt | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index b5291b3b5d..334dbd0a25 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -30,10 +30,12 @@ import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Integer import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.MutableList import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Value import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.statements.DoStatement +import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement +import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.helpers.State -import de.fraunhofer.aisec.cpg.helpers.Worklist -import de.fraunhofer.aisec.cpg.helpers.iterateEOG +import de.fraunhofer.aisec.cpg.helpers.* import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import org.apache.commons.lang3.NotImplementedException @@ -105,12 +107,17 @@ class AbstractEvaluator { // Overwrite current interval, mark this node as needs narrowing newState[currentNode] = IntervalLattice(widenedInterval) currentNode.nextEOG.forEach { - worklist.evaluationStateMap[it] = Worklist.EvaluationState.WIDENING + if (!isLoopEnd(it)) { + worklist.evaluationStateMap[it] = Worklist.EvaluationState.WIDENING + } } worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.NARROWING } else { // NO: mark the current node as DONE - worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.DONE + // We never mark loop heads as DONE as they prevent loop entering otherwise + if (!isLoopHead(currentNode)) { + worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.DONE + } } } @@ -128,19 +135,30 @@ class AbstractEvaluator { } } else { // NO: mark the node as DONE - worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.DONE + // We never mark loop heads as DONE as they prevent loop entering otherwise + if (!isLoopHead(currentNode)) { + worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.DONE + } } } - // If it was seen for the first time apply the effect and mark it as "NEEDS WIDENING" + // If it was seen for the first time apply the effect and maybe mark it as "NEEDS WIDENING" // We cannot use the "already_seen" field as it is set before this handler is called else { newState[currentNode] = IntervalLattice(newInterval) - worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.WIDENING + // We mark the node as needs widening if it is either a loop head or any previous node + // is marked, but not if the current node ends a loop + if ( + isLoopHead(currentNode) || + (currentNode.prevEOG.any { worklist.needsWidening(it) } && + !isLoopEnd(currentNode)) + ) { + worklist.evaluationStateMap[currentNode] = Worklist.EvaluationState.WIDENING + } } // We propagate the current Interval to all successor nodes which are empty - // If the next EOG already has a value we need to join all of its predecessors + // If the next EOG already has a value we need to join them // This is implemented in IntervalState.push // Only do this if we have not reached the goal node if (currentNode != goalNode) { @@ -178,4 +196,24 @@ class AbstractEvaluator { else -> MutableList::class // throw NotImplementedException() } } + + /** This method checks for known loop heads such as For, While or Do. */ + private fun isLoopHead(node: Node): Boolean { + return when (node) { + is ForStatement, + is WhileStatement, + is ForEachStatement, + is DoStatement -> true + else -> false + } + } + + /** + * This method checks if the current node marks the end of a loop. It assumes that nextEOG[1] of + * the loop head always points to the loop end. + */ + private fun isLoopEnd(node: Node): Boolean { + val head = node.prevEOG.firstOrNull { isLoopHead(it) } ?: return false + return head.nextEOG[1] == node + } } From d74c4276a5de58781cb94a65d386793c216e03ae Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 23 Oct 2024 10:48:23 +0200 Subject: [PATCH 25/58] added second evaluate call with more defined arguments --- .../abstracteval/AbstractEvaluator.kt | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index 334dbd0a25..f3a3948537 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -42,24 +42,39 @@ import org.apache.commons.lang3.NotImplementedException class AbstractEvaluator { // The node for which we want to get the value - private lateinit var goalNode: Node + private lateinit var targetNode: Node // The name of the value we are analyzing private lateinit var targetName: String // The type of the value we are analyzing private lateinit var targetType: KClass fun evaluate(node: Node): LatticeInterval { - goalNode = node - targetName = node.name.toString() - targetType = getType(node) - val initializer = getInitializerOf(node, targetType)!! + return evaluate( + node.name.localName, + getInitializerOf(node)!!, + node, + getType(node), + IntervalLattice(LatticeInterval.BOTTOM) + ) + } + + fun evaluate( + name: String, + start: Node, + end: Node, + type: KClass, + interval: IntervalLattice? = null + ): LatticeInterval { + targetNode = end + targetName = name + targetType = type // evaluate effect of each operation on the list until we reach "node" val startState = IntervalState() - startState.push(initializer, IntervalLattice(LatticeInterval.BOTTOM)) - val finalState = iterateEOG(initializer, startState, ::handleNode, goalNode) + startState.push(start, interval) + val finalState = iterateEOG(start, startState, ::handleNode, targetNode) // TODO: null-safety - return finalState!![node]!!.elements + return finalState!![targetNode]!!.elements } /** @@ -161,15 +176,15 @@ class AbstractEvaluator { // If the next EOG already has a value we need to join them // This is implemented in IntervalState.push // Only do this if we have not reached the goal node - if (currentNode != goalNode) { + if (currentNode != targetNode) { currentNode.nextEOG.forEach { newState.push(it, newState[currentNode]) } } return newState } - private fun getInitializerOf(node: Node, type: KClass): Node? { - return type.createInstance().getInitializer(node) + private fun getInitializerOf(node: Node): Node? { + return Value.getInitializer(node) } private fun State.calculateEffect(node: Node): LatticeInterval { From 6e988bb826ef5f0b919f56681c1be2e8ffd538ca Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 23 Oct 2024 10:49:00 +0200 Subject: [PATCH 26/58] support functions with side effects by creating a function-local evaluator --- .../analysis/abstracteval/value/Integer.kt | 30 +++++++++++++++++++ .../cpg/analysis/abstracteval/value/Value.kt | 14 +++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt index 1913ea5cde..1c182ad892 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -25,11 +25,15 @@ */ package de.fraunhofer.aisec.cpg.analysis.abstracteval.value +import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator +import de.fraunhofer.aisec.cpg.analysis.abstracteval.IntervalLattice import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.query.value import org.apache.commons.lang3.NotImplementedException @@ -131,6 +135,32 @@ class Integer : Value { else -> current } } + } else if ( + node is MemberCallExpression && node.arguments.any { it.name.localName == name } + ) { + // This is a function call that uses the variable as an argument. + // To find side effects we need to create a local evaluator for this function and + // return the value of the renamed variable at the last statement + // TODO: this currently does not work if the variable is given for multiple parameters + // TODO: error handling + val function = node.invokes.first() + val argPos = node.arguments.indexOfFirst { it.name.localName == name } + + // We cannot take the "first" as that refers to the Block which has no nextEOG + // Also debugging is ugly because the getter of Node.statements is overwritten + val functionStart = function.body.statements[1] + // This could be a Location but the CPG often just hands us "null" + val functionEnd = function.body.statements.last() + val newTargetName = function.parameters[argPos].name.localName + + val localEvaluator = AbstractEvaluator() + return localEvaluator.evaluate( + newTargetName, + functionStart, + functionEnd, + this::class, + IntervalLattice(current) + ) } return current } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt index fa85e1a082..0b709ad9a8 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt @@ -34,12 +34,14 @@ interface Value { /** Applies the effect of a Node to the Interval describing possible values of a collection. */ fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval - fun getInitializer(node: Node?): Node? { - return when (node) { - null -> null!! - is Reference -> getInitializer(node.refersTo) - is VariableDeclaration -> node - else -> getInitializer(node.prevDFG.firstOrNull()) + companion object { + fun getInitializer(node: Node?): Node? { + return when (node) { + null -> null!! + is Reference -> getInitializer(node.refersTo) + is VariableDeclaration -> node + else -> getInitializer(node.prevDFG.firstOrNull()) + } } } } From e0dbe1c0445c5f012bd5739a50eef0d88824753e Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 23 Oct 2024 11:38:29 +0200 Subject: [PATCH 27/58] moved side effect analysis to list instead of integer... --- .../analysis/abstracteval/value/Integer.kt | 26 -------------- .../abstracteval/value/MutableList.kt | 34 ++++++++++++++++++- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt index 1c182ad892..1fed1f595c 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -135,32 +135,6 @@ class Integer : Value { else -> current } } - } else if ( - node is MemberCallExpression && node.arguments.any { it.name.localName == name } - ) { - // This is a function call that uses the variable as an argument. - // To find side effects we need to create a local evaluator for this function and - // return the value of the renamed variable at the last statement - // TODO: this currently does not work if the variable is given for multiple parameters - // TODO: error handling - val function = node.invokes.first() - val argPos = node.arguments.indexOfFirst { it.name.localName == name } - - // We cannot take the "first" as that refers to the Block which has no nextEOG - // Also debugging is ugly because the getter of Node.statements is overwritten - val functionStart = function.body.statements[1] - // This could be a Location but the CPG often just hands us "null" - val functionEnd = function.body.statements.last() - val newTargetName = function.parameters[argPos].name.localName - - val localEvaluator = AbstractEvaluator() - return localEvaluator.evaluate( - newTargetName, - functionStart, - functionEnd, - this::class, - IntervalLattice(current) - ) } return current } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt index d8f9a2c064..b9e0fbaa9d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt @@ -25,9 +25,12 @@ */ package de.fraunhofer.aisec.cpg.analysis.abstracteval.value +import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator +import de.fraunhofer.aisec.cpg.analysis.abstracteval.IntervalLattice import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression @@ -92,7 +95,36 @@ class MutableList : Value { val zeroInterval = LatticeInterval.Bounded(0, 0) current.join(zeroInterval) } - else -> current + else -> { + // This includes all functions with side effects + if (node.arguments.any { it.name.localName == name }) { + // This is a function call that uses the variable as an argument. + // To find side effects we need to create a local evaluator for this function and + // return the value of the renamed variable at the last statement + // TODO: this currently does not work if the variable is given for multiple parameters + // TODO: error handling + val function = node.invokes.first() + val argPos = node.arguments.indexOfFirst { it.name.localName == name } + + // We cannot take the "first" as that refers to the Block which has no nextEOG + // Also debugging is ugly because the getter of Node.statements is overwritten + val functionStart = function.body.statements[1] + // This could be a Location but the CPG often just hands us "null" + val functionEnd = function.body.statements.last() + val newTargetName = function.parameters[argPos].name.localName + + val localEvaluator = AbstractEvaluator() + return localEvaluator.evaluate( + newTargetName, + functionStart, + functionEnd, + this::class, + IntervalLattice(current) + ) + } else { + return current + } + } } } } From f202ffe2826d2becf0a2c75a4a37740372326198 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 09:26:16 +0100 Subject: [PATCH 28/58] add new 'assignMinus' expression and add the name as code when creating a reference --- .../fraunhofer/aisec/cpg/graph/builder/Fluent.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 1ea29ec589..59806288a3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -968,6 +968,7 @@ fun LanguageFrontend<*, *>.ref( ): Reference { val node = newReference(name) node.type = type + node.code = name.toString() if (init != null) { init(node) @@ -1388,6 +1389,21 @@ infix fun Expression.assignPlus(rhs: Expression): AssignExpression { return node } +/** + * Creates a new [AssignExpression] with a `-=` [AssignExpression.operatorCode] in the Fluent Node + * DSL and adds it to the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend<*, *>, Holder) +infix fun Expression.assignMinus(rhs: Expression): AssignExpression { + val node = (this@LanguageFrontend).newAssignExpression("-=", listOf(this), listOf(rhs)) + + if (this@Holder is StatementHolder) { + this@Holder += node + } + + return node +} + /** * Creates a new [AssignExpression] with a `=` [AssignExpression.operatorCode] in the Fluent Node * DSL and adds it to the nearest enclosing [StatementHolder]. From 223cc64cc25215348402617e48c7a2ff1c63c11a Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 09:26:49 +0100 Subject: [PATCH 29/58] cleanup --- .../aisec/cpg/analysis/abstracteval/value/Integer.kt | 4 ---- .../aisec/cpg/analysis/abstracteval/value/MutableList.kt | 6 ++++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt index 1fed1f595c..1913ea5cde 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -25,15 +25,11 @@ */ package de.fraunhofer.aisec.cpg.analysis.abstracteval.value -import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator -import de.fraunhofer.aisec.cpg.analysis.abstracteval.IntervalLattice import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal -import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.query.value import org.apache.commons.lang3.NotImplementedException diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt index b9e0fbaa9d..7bdf7943ed 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt @@ -99,9 +99,11 @@ class MutableList : Value { // This includes all functions with side effects if (node.arguments.any { it.name.localName == name }) { // This is a function call that uses the variable as an argument. - // To find side effects we need to create a local evaluator for this function and + // To find side effects we need to create a local evaluator for this function + // and // return the value of the renamed variable at the last statement - // TODO: this currently does not work if the variable is given for multiple parameters + // TODO: this currently does not work if the variable is given for multiple + // parameters // TODO: error handling val function = node.invokes.first() val argPos = node.arguments.indexOfFirst { it.name.localName == name } From bd129b13899a7a20d1337002bc8da210a55bb941 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 09:27:10 +0100 Subject: [PATCH 30/58] add tests for the new Evaluator --- .../cpg/analysis/AbstractEvaluatorTest.kt | 151 ++++++++++++++++ .../cpg/testcases/AbstractEvaluationTests.kt | 170 ++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt create mode 100644 cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/AbstractEvaluationTests.kt diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt new file mode 100644 index 0000000000..d02942469d --- /dev/null +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt @@ -0,0 +1,151 @@ +/* + * 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.cpg.analysis + +import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.bodyOrNull +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.testcases.AbstractEvaluationTests +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AbstractEvaluatorTest { + private lateinit var tu: TranslationUnitDeclaration + + @BeforeAll + fun beforeAll() { + tu = AbstractEvaluationTests.getIntegerExample().components.first().translationUnits.first() + } + + /* + Bar f = new Bar(); + int a = 5; + + a = 5; + a -= 2; + a += 3; + + b.f(a); + */ + @Test + fun testSimpleInteger() { + val mainClass = tu.records["Foo"] + assertNotNull(mainClass) + val f1 = mainClass.methods["f1"] + assertNotNull(f1) + + val refA = f1.bodyOrNull(5)!!.arguments.first() + assertNotNull(refA) + + val evaluator = AbstractEvaluator() + val value = evaluator.evaluate(refA) + assertEquals(LatticeInterval.Bounded(1, 1), value) + } + + /* + Bar b = new Bar(); + int a = 5; + + if (new Random().nextBoolean()) { + a -= 1; + } + + b.f(a); + */ + @Test + fun testBranch1Integer() { + val mainClass = tu.records["Foo"] + assertNotNull(mainClass) + val f1 = mainClass.methods["f2"] + assertNotNull(f1) + + val refA = f1.bodyOrNull(3)!!.arguments.first() + assertNotNull(refA) + + val evaluator = AbstractEvaluator() + val value = evaluator.evaluate(refA) + assertEquals(LatticeInterval.Bounded(4, 5), value) + } + + /* + Bar b = new Bar(); + int a = 5; + + if (new Random().nextBoolean()) { + a -= 1; + } else { + a = 3; + } + + b.f(a); + */ + @Test + fun testBranch2Integer() { + val mainClass = tu.records["Foo"] + assertNotNull(mainClass) + val f1 = mainClass.methods["f3"] + assertNotNull(f1) + + val refA = f1.bodyOrNull(3)!!.arguments.first() + assertNotNull(refA) + + val evaluator = AbstractEvaluator() + val value = evaluator.evaluate(refA) + assertEquals(LatticeInterval.Bounded(3, 4), value) + } + + /* + Bar b = new Bar(); + int a = 5; + + for (int i = 0; i < 5; i++) { + a += 1; + } + + b.f(a); + */ + @Test + fun testLoopInteger() { + val mainClass = tu.records["Foo"] + assertNotNull(mainClass) + val f1 = mainClass.methods["f4"] + assertNotNull(f1) + + val refA = f1.bodyOrNull(5)!!.arguments.first() + assertNotNull(refA) + + val evaluator = AbstractEvaluator() + val value = evaluator.evaluate(refA) + assertEquals(LatticeInterval.Bounded(5, LatticeInterval.Bound.INFINITE), value) + } +} diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/AbstractEvaluationTests.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/AbstractEvaluationTests.kt new file mode 100644 index 0000000000..f48a4704d7 --- /dev/null +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/AbstractEvaluationTests.kt @@ -0,0 +1,170 @@ +/* + * 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.cpg.testcases + +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.passes.UnreachableEOGPass + +abstract class AbstractEvaluationTests { + companion object { + /* + public class IntegerTest { + public void f1() { + Bar b = new Bar(); + int a = 5; + + a = 0; + a -= 2; + a += 3; + + b.f(a); + } + + public void f2() { + Bar b = new Bar(); + int a = 5; + + if (new Random().nextBoolean()) { + a -= 1; + } + + b.f(a); + } + + public void f3() { + Bar b = new Bar(); + int a = 5; + + if (new Random().nextBoolean()) { + a -= 1; + } else { + a = 3; + } + + b.f(a); + } + + public void f4() { + Bar b = new Bar(); + int a = 5; + + for (int i = 0; i < 5; i++) { + a += 1; + } + + b.f(a); + } + } + + class Bar { + public void f(int a) {} + } + */ + fun getIntegerExample( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .registerPass() + .build() + ) = + testFrontend(config).build { + translationResult { + translationUnit("integer.java") { + record("Foo") { + method("f1") { + body { + declare { variable("b", t("Bar")) } + declare { variable("a", t("int")) { literal(5, t("int")) } } + + // TODO: set the correct code as the evaluator relies on it + ref("a") assign literal(0, t("int")) + ref("a") assignMinus literal(2, t("int")) + ref("a") assignPlus literal(3, t("int")) + + memberCall("f", ref("Bar")) { ref("a") } + } + } + method("f2") { + body { + declare { variable("b", t("Bar")) } + declare { variable("a", t("int")) { literal(5, t("int")) } } + + ifStmt { + condition { memberCall("nextBoolean", ref("Random")) } + thenStmt { ref("a") assignMinus literal(1, t("int")) } + } + + memberCall("f", ref("Bar")) { ref("a") } + } + } + method("f3") { + body { + declare { variable("b", t("Bar")) } + declare { variable("a", t("int")) { literal(5, t("int")) } } + + ifStmt { + condition { memberCall("nextBoolean", ref("Random")) } + thenStmt { ref("a") assignMinus literal(1, t("int")) } + elseStmt { ref("a") assign literal(3, t("int")) } + } + + memberCall("f", ref("Bar")) { ref("a") } + } + } + method("f4") { + body { + declare { variable("b", t("Bar")) } + declare { variable("a", t("int")) { literal(5, t("int")) } } + + forStmt( + declare { + variable("i", t("int")) { literal(0, t("int")) } + }, + ref("i") lt literal(5, t("int")), + ref("i").inc() + ) { + ref("a") assignPlus literal(1, t("int")) + } + + memberCall("f", ref("Bar")) { ref("a") } + } + } + } + record("Bar") { + method("main") { + param("a", t("int")) + body {} + } + } + } + } + } + } +} From f4c432fcb32acc76d0a8363c5b0ce4bce5a75db8 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 10:28:46 +0100 Subject: [PATCH 31/58] first implementation for other integer operations --- .../analysis/abstracteval/LatticeInterval.kt | 97 +++++++++++++- .../analysis/abstracteval/value/Integer.kt | 118 ++++++++++++------ 2 files changed, 178 insertions(+), 37 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 1fec4755c7..108ea1287d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.helpers.State sealed class LatticeInterval : Comparable { object BOTTOM : LatticeInterval() + // TODO: future iterations should support fractional values data class Bounded(val lower: Bound, val upper: Bound) : LatticeInterval() { constructor(lower: Int, upper: Int) : this(Bound.Value(lower), Bound.Value(upper)) @@ -120,6 +121,45 @@ sealed class LatticeInterval : Comparable { } } + // Multiplication operator + operator fun times(other: LatticeInterval): LatticeInterval { + return when { + this is BOTTOM || other is BOTTOM -> BOTTOM + this is Bounded && other is Bounded -> { + val newLower = multiplyBounds(this.lower, other.lower) + val newUpper = multiplyBounds(this.upper, other.upper) + Bounded(min(newLower, newUpper), max(newLower, newUpper)) + } + else -> throw IllegalArgumentException("Unsupported interval type") + } + } + + // Division operator + operator fun div(other: LatticeInterval): LatticeInterval { + return when { + this is BOTTOM || other is BOTTOM -> BOTTOM + this is Bounded && other is Bounded -> { + val newLower = divideBounds(this.lower, other.lower) + val newUpper = divideBounds(this.upper, other.upper) + Bounded(min(newLower, newUpper), max(newLower, newUpper)) + } + else -> throw IllegalArgumentException("Unsupported interval type") + } + } + + // Modulo operator + operator fun rem(other: LatticeInterval): LatticeInterval { + return when { + this is BOTTOM || other is BOTTOM -> BOTTOM + this is Bounded && other is Bounded -> { + val lowerBracket = modulateBounds(this.lower, other.lower) + val upperBracket = modulateBounds(this.upper, other.upper) + lowerBracket.join(upperBracket) + } + else -> throw IllegalArgumentException("Unsupported interval type") + } + } + // Join Operation fun join(other: LatticeInterval): LatticeInterval { return when { @@ -215,7 +255,7 @@ sealed class LatticeInterval : Comparable { private fun addBounds(a: Bound, b: Bound): Bound { return when { - // -∞ + ∞ is not an allowed operation + // -∞ + ∞ is not a defined operation a is Bound.INFINITE && b !is Bound.NEGATIVE_INFINITE -> Bound.INFINITE a is Bound.NEGATIVE_INFINITE && b !is Bound.INFINITE -> Bound.NEGATIVE_INFINITE b is Bound.INFINITE && a !is Bound.NEGATIVE_INFINITE -> Bound.INFINITE @@ -227,7 +267,7 @@ sealed class LatticeInterval : Comparable { private fun subtractBounds(a: Bound, b: Bound): Bound { return when { - // ∞ - ∞ is not an allowed operation + // ∞ - ∞ is not a defined operation a is Bound.INFINITE && b !is Bound.INFINITE -> Bound.INFINITE a is Bound.NEGATIVE_INFINITE && b !is Bound.NEGATIVE_INFINITE -> Bound.NEGATIVE_INFINITE b is Bound.INFINITE && a !is Bound.INFINITE -> Bound.NEGATIVE_INFINITE @@ -237,6 +277,59 @@ sealed class LatticeInterval : Comparable { } } + private fun multiplyBounds(a: Bound, b: Bound): Bound { + return when { + // ∞ * 0 is not a defined operation + a is Bound.INFINITE && b > Bound.Value(0) -> Bound.INFINITE + a is Bound.INFINITE && b < Bound.Value(0) -> Bound.NEGATIVE_INFINITE + a > Bound.Value(0) && b is Bound.INFINITE -> Bound.INFINITE + a < Bound.Value(0) && b is Bound.INFINITE -> Bound.NEGATIVE_INFINITE + a is Bound.NEGATIVE_INFINITE && b > Bound.Value(0) -> Bound.NEGATIVE_INFINITE + a is Bound.NEGATIVE_INFINITE && b < Bound.Value(0) -> Bound.INFINITE + a > Bound.Value(0) && b is Bound.NEGATIVE_INFINITE -> Bound.NEGATIVE_INFINITE + a < Bound.Value(0) && b is Bound.NEGATIVE_INFINITE -> Bound.INFINITE + a is Bound.Value && b is Bound.Value -> Bound.Value(a.value * b.value) + else -> throw IllegalArgumentException("Unsupported bound type") + } + } + + private fun divideBounds(a: Bound, b: Bound): Bound { + return when { + // ∞ / ∞ is not a defined operation + a is Bound.INFINITE && b > Bound.Value(0) && b !is Bound.INFINITE -> Bound.INFINITE + a is Bound.INFINITE && b < Bound.Value(0) && b !is Bound.NEGATIVE_INFINITE -> + Bound.NEGATIVE_INFINITE + a > Bound.Value(0) && a !is Bound.INFINITE && b is Bound.INFINITE -> Bound.INFINITE + a < Bound.Value(0) && a !is Bound.NEGATIVE_INFINITE && b is Bound.INFINITE -> + Bound.NEGATIVE_INFINITE + a is Bound.NEGATIVE_INFINITE && b > Bound.Value(0) && b !is Bound.INFINITE -> + Bound.NEGATIVE_INFINITE + a is Bound.NEGATIVE_INFINITE && b < Bound.Value(0) && b !is Bound.NEGATIVE_INFINITE -> + Bound.INFINITE + a > Bound.Value(0) && a !is Bound.INFINITE && b is Bound.NEGATIVE_INFINITE -> + Bound.NEGATIVE_INFINITE + a < Bound.Value(0) && a !is Bound.NEGATIVE_INFINITE && b is Bound.NEGATIVE_INFINITE -> + Bound.INFINITE + a is Bound.Value && b is Bound.Value -> Bound.Value(a.value / b.value) + else -> throw IllegalArgumentException("Unsupported bound type") + } + } + + // ∞ mod b can be any number [0, b], therefore we need to return an Interval + private fun modulateBounds(a: Bound, b: Bound): LatticeInterval { + return when { + // ∞ mod ∞ is not a defined operation + a == Bound.Value(0) -> Bounded(0, 0) + (a is Bound.INFINITE || a is Bound.NEGATIVE_INFINITE) && b >= Bound.Value(0) -> + Bounded(0, b) + (a is Bound.INFINITE || a is Bound.NEGATIVE_INFINITE) && b < Bound.Value(0) -> + Bounded(b, 0) + b is Bound.INFINITE || b is Bound.NEGATIVE_INFINITE -> Bounded(a, a) + a is Bound.Value && b is Bound.Value -> Bounded(a.value % b.value, a.value % b.value) + else -> throw IllegalArgumentException("Unsupported bound type") + } + } + override fun toString(): String { return when (this) { is BOTTOM -> "BOTTOM" diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt index 1913ea5cde..a1ff8fe93f 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -62,71 +62,119 @@ class Integer : Value { } } else if (node is AssignExpression) { if (node.lhs.any { it.code == name }) { - // TODO: we need to evaluate the right hand side for all cases! + // TODO: we should evaluate the right hand side expression for all cases! + // currently evaluation only works correctly for literals return when (node.operatorCode) { "=" -> { var newInterval: LatticeInterval = current // If the rhs is only a literal use this exact value - if (node.rhs.size == 1 && node.rhs[0] is Literal<*>) { - val value = node.rhs[0].value.value as? Int + val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int + newInterval = if (value != null) { - newInterval = LatticeInterval.Bounded(value, value) + LatticeInterval.Bounded(value, value) + } + // Per default set the bounds to unknown + else { + LatticeInterval.Bounded( + LatticeInterval.Bound.NEGATIVE_INFINITE, + LatticeInterval.Bound.INFINITE + ) } - } else { - TODO() - } newInterval } "+=" -> { var newInterval: LatticeInterval = current // If the rhs is only a literal we subtract this exact value - if (node.rhs.size == 1 && node.rhs[0] is Literal<*>) { - val value = node.rhs[0].value.value as? Int + val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int + newInterval = if (value != null) { val valueInterval = LatticeInterval.Bounded(value, value) - newInterval = current.plus(valueInterval) + current + valueInterval + } + // Per default set upper bound to infinite + else { + val joinInterval: LatticeInterval = + LatticeInterval.Bounded( + LatticeInterval.Bound.INFINITE, + LatticeInterval.Bound.INFINITE + ) + current.join(joinInterval) } - } - // Per default set upper bound to infinite - else { - val joinInterval: LatticeInterval = - LatticeInterval.Bounded( - LatticeInterval.Bound.INFINITE, - LatticeInterval.Bound.INFINITE - ) - newInterval = current.join(joinInterval) - } newInterval } "-=" -> { var newInterval: LatticeInterval = current // If the rhs is only a literal we subtract this exact value - if (node.rhs.size == 1 && node.rhs[0] is Literal<*>) { - val value = node.rhs[0].value.value as? Int + val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int + newInterval = + if (value != null) { + val valueInterval = LatticeInterval.Bounded(value, value) + current - valueInterval + } + // Per default set lower bound to negative infinite + else { + val joinInterval: LatticeInterval = + LatticeInterval.Bounded( + LatticeInterval.Bound.NEGATIVE_INFINITE, + LatticeInterval.Bound.NEGATIVE_INFINITE + ) + current.join(joinInterval) + } + newInterval + } + "*=" -> { + var newInterval: LatticeInterval = current + // If the rhs is only a literal we subtract this exact value + val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int + newInterval = if (value != null) { val valueInterval = LatticeInterval.Bounded(value, value) - newInterval = current.minus(valueInterval) + current * valueInterval } - } - // Per default set lower bound to negative infinite - else { - val joinInterval: LatticeInterval = + // Per default lose all information + else { LatticeInterval.Bounded( LatticeInterval.Bound.NEGATIVE_INFINITE, - LatticeInterval.Bound.NEGATIVE_INFINITE + LatticeInterval.Bound.INFINITE ) - newInterval = current.join(joinInterval) - } + } newInterval } - "*=" -> { - TODO() - } "/=" -> { - TODO() + var newInterval: LatticeInterval = current + // If the rhs is only a literal we subtract this exact value + val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int + newInterval = + if (value != null) { + val valueInterval = LatticeInterval.Bounded(value, value) + current / valueInterval + } + // Per default lose all information + else { + LatticeInterval.Bounded( + LatticeInterval.Bound.NEGATIVE_INFINITE, + LatticeInterval.Bound.INFINITE + ) + } + newInterval } "%=" -> { - TODO() + var newInterval: LatticeInterval = current + // If the rhs is only a literal we subtract this exact value + val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int + newInterval = + if (value != null) { + val valueInterval = LatticeInterval.Bounded(value, value) + current % valueInterval + } + // Per default lose all information + else { + LatticeInterval.Bounded( + LatticeInterval.Bound.NEGATIVE_INFINITE, + LatticeInterval.Bound.INFINITE + ) + } + newInterval } else -> current } From 02efd1315d9f147cb92ad877e4bcac5548ae447e Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 10:43:26 +0100 Subject: [PATCH 32/58] add prefix versions of the inc and dec operator --- .../aisec/cpg/graph/builder/Fluent.kt | 85 ++++++++++++++++++- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 59806288a3..57335ee7f4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -1183,8 +1183,8 @@ fun reference(input: Expression): UnaryOperator { } /** - * Creates a new [UnaryOperator] with a `--` [UnaryOperator.operatorCode] in the Fluent Node DSL and - * adds it to the nearest enclosing [StatementHolder]. + * Creates a new [UnaryOperator] with a `--` postfix [UnaryOperator.operatorCode] in the Fluent Node + * DSL and adds it to the nearest enclosing [StatementHolder]. */ context(LanguageFrontend<*, *>, Holder) operator fun Expression.dec(): UnaryOperator { @@ -1199,8 +1199,8 @@ operator fun Expression.dec(): UnaryOperator { } /** - * Creates a new [UnaryOperator] with a `++` [UnaryOperator.operatorCode] in the Fluent Node DSL and - * invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. + * Creates a new [UnaryOperator] with a `++` postfix [UnaryOperator.operatorCode] in the Fluent Node + * DSL and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. */ context(LanguageFrontend<*, *>, Holder) operator fun Expression.inc(): UnaryOperator { @@ -1214,6 +1214,38 @@ operator fun Expression.inc(): UnaryOperator { return node } +/** + * Creates a new [UnaryOperator] with a `--` prefix [UnaryOperator.operatorCode] in the Fluent Node + * DSL and adds it to the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend<*, *>, Holder) +fun Expression.decPrefix(): UnaryOperator { + val node = (this@LanguageFrontend).newUnaryOperator("--", false, true) + node.input = this + + if (this@Holder is StatementHolder) { + this@Holder += node + } + + return node +} + +/** + * Creates a new [UnaryOperator] with a `++` prefix [UnaryOperator.operatorCode] in the Fluent Node + * DSL and invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. + */ +context(LanguageFrontend<*, *>, Holder) +fun Expression.incPrefix(): UnaryOperator { + val node = (this@LanguageFrontend).newUnaryOperator("++", false, true) + node.input = this + + if (this@Holder is StatementHolder) { + this@Holder += node + } + + return node +} + /** * Creates a new [UnaryOperator] with a `++` [UnaryOperator.operatorCode] in the Fluent Node DSL and * invokes [ArgumentHolder.addArgument] of the nearest enclosing [ArgumentHolder]. @@ -1404,6 +1436,51 @@ infix fun Expression.assignMinus(rhs: Expression): AssignExpression { return node } +/** + * Creates a new [AssignExpression] with a `*=` [AssignExpression.operatorCode] in the Fluent Node + * DSL and adds it to the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend<*, *>, Holder) +infix fun Expression.assignMult(rhs: Expression): AssignExpression { + val node = (this@LanguageFrontend).newAssignExpression("*=", listOf(this), listOf(rhs)) + + if (this@Holder is StatementHolder) { + this@Holder += node + } + + return node +} + +/** + * Creates a new [AssignExpression] with a `/=` [AssignExpression.operatorCode] in the Fluent Node + * DSL and adds it to the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend<*, *>, Holder) +infix fun Expression.assignDiv(rhs: Expression): AssignExpression { + val node = (this@LanguageFrontend).newAssignExpression("/=", listOf(this), listOf(rhs)) + + if (this@Holder is StatementHolder) { + this@Holder += node + } + + return node +} + +/** + * Creates a new [AssignExpression] with a `%=` [AssignExpression.operatorCode] in the Fluent Node + * DSL and adds it to the nearest enclosing [StatementHolder]. + */ +context(LanguageFrontend<*, *>, Holder) +infix fun Expression.assignMod(rhs: Expression): AssignExpression { + val node = (this@LanguageFrontend).newAssignExpression("%=", listOf(this), listOf(rhs)) + + if (this@Holder is StatementHolder) { + this@Holder += node + } + + return node +} + /** * Creates a new [AssignExpression] with a `=` [AssignExpression.operatorCode] in the Fluent Node * DSL and adds it to the nearest enclosing [StatementHolder]. From 969260bc77919a75c23bd534a47ae02c4118fdab Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 10:44:19 +0100 Subject: [PATCH 33/58] Add test case for all implemented Integer operations --- .../cpg/analysis/AbstractEvaluatorTest.kt | 38 ++++++++++++-- .../cpg/testcases/AbstractEvaluationTests.kt | 51 ++++++++++++++++--- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt index d02942469d..d1bad35a2d 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt @@ -72,6 +72,38 @@ class AbstractEvaluatorTest { assertEquals(LatticeInterval.Bounded(1, 1), value) } + /* + Bar f = new Bar(); + int a = 5; + + a = 3; + a++; + ++a; + a -= 2; + a += 3; + a--; + --a; + a *= 4; + a /= 2; + a %= 3; + + b.f(a); + */ + @Test + fun testIntegerOperations() { + val mainClass = tu.records["Foo"] + assertNotNull(mainClass) + val f1 = mainClass.methods["f2"] + assertNotNull(f1) + + val refA = f1.bodyOrNull(12)!!.arguments.first() + assertNotNull(refA) + + val evaluator = AbstractEvaluator() + val value = evaluator.evaluate(refA) + assertEquals(LatticeInterval.Bounded(2, 2), value) + } + /* Bar b = new Bar(); int a = 5; @@ -86,7 +118,7 @@ class AbstractEvaluatorTest { fun testBranch1Integer() { val mainClass = tu.records["Foo"] assertNotNull(mainClass) - val f1 = mainClass.methods["f2"] + val f1 = mainClass.methods["f3"] assertNotNull(f1) val refA = f1.bodyOrNull(3)!!.arguments.first() @@ -113,7 +145,7 @@ class AbstractEvaluatorTest { fun testBranch2Integer() { val mainClass = tu.records["Foo"] assertNotNull(mainClass) - val f1 = mainClass.methods["f3"] + val f1 = mainClass.methods["f4"] assertNotNull(f1) val refA = f1.bodyOrNull(3)!!.arguments.first() @@ -138,7 +170,7 @@ class AbstractEvaluatorTest { fun testLoopInteger() { val mainClass = tu.records["Foo"] assertNotNull(mainClass) - val f1 = mainClass.methods["f4"] + val f1 = mainClass.methods["f5"] assertNotNull(f1) val refA = f1.bodyOrNull(5)!!.arguments.first() diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/AbstractEvaluationTests.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/AbstractEvaluationTests.kt index f48a4704d7..bab3beff32 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/AbstractEvaluationTests.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/AbstractEvaluationTests.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.testcases import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.frontends.TestLanguage -import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.passes.UnreachableEOGPass @@ -47,6 +46,24 @@ abstract class AbstractEvaluationTests { } public void f2() { + Bar f = new Bar(); + int a = 5; + + a = 3; + a++; + ++a; + a -= 2; + a += 3; + a--; + --a; + a *= 4; + a /= 2; + a %= 3; + + b.f(a); + } + + public void f3() { Bar b = new Bar(); int a = 5; @@ -57,7 +74,7 @@ abstract class AbstractEvaluationTests { b.f(a); } - public void f3() { + public void f4() { Bar b = new Bar(); int a = 5; @@ -70,7 +87,7 @@ abstract class AbstractEvaluationTests { b.f(a); } - public void f4() { + public void f5() { Bar b = new Bar(); int a = 5; @@ -103,7 +120,6 @@ abstract class AbstractEvaluationTests { declare { variable("b", t("Bar")) } declare { variable("a", t("int")) { literal(5, t("int")) } } - // TODO: set the correct code as the evaluator relies on it ref("a") assign literal(0, t("int")) ref("a") assignMinus literal(2, t("int")) ref("a") assignPlus literal(3, t("int")) @@ -112,6 +128,29 @@ abstract class AbstractEvaluationTests { } } method("f2") { + body { + declare { variable("b", t("Bar")) } + declare { variable("a", t("int")) { literal(5, t("int")) } } + + ref("a") assign literal(3, t("int")) + + ref("a").inc() + ref("a").incPrefix() + + ref("a") assignMinus literal(2, t("int")) + ref("a") assignPlus literal(3, t("int")) + + ref("a").dec() + ref("a").decPrefix() + + ref("a") assignMult literal(4, t("int")) + ref("a") assignDiv literal(2, t("int")) + ref("a") assignMod literal(3, t("int")) + + memberCall("f", ref("Bar")) { ref("a") } + } + } + method("f3") { body { declare { variable("b", t("Bar")) } declare { variable("a", t("int")) { literal(5, t("int")) } } @@ -124,7 +163,7 @@ abstract class AbstractEvaluationTests { memberCall("f", ref("Bar")) { ref("a") } } } - method("f3") { + method("f4") { body { declare { variable("b", t("Bar")) } declare { variable("a", t("int")) { literal(5, t("int")) } } @@ -138,7 +177,7 @@ abstract class AbstractEvaluationTests { memberCall("f", ref("Bar")) { ref("a") } } } - method("f4") { + method("f5") { body { declare { variable("b", t("Bar")) } declare { variable("a", t("int")) { literal(5, t("int")) } } From 1bcff47144701a139deb7082f9e834f1d37e99b4 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 10:46:24 +0100 Subject: [PATCH 34/58] add auto-generated hashcode function --- .../aisec/cpg/analysis/abstracteval/LatticeInterval.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 108ea1287d..6022e431cd 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -336,6 +336,10 @@ sealed class LatticeInterval : Comparable { is Bounded -> "[$lower, $upper]" } } + + override fun hashCode(): Int { + return javaClass.hashCode() + } } /** From 974ec6a448d23598a6cd9d0f5cba16196ba09852 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 12:06:34 +0100 Subject: [PATCH 35/58] cleanup and documentation --- .../abstracteval/AbstractEvaluator.kt | 58 ++++++++++++------- .../analysis/abstracteval/LatticeInterval.kt | 30 +++++++--- .../cpg/analysis/abstracteval/value/Array.kt | 18 +++--- .../analysis/abstracteval/value/Integer.kt | 29 +++++----- .../abstracteval/value/MutableList.kt | 45 +++++++------- .../cpg/analysis/abstracteval/value/Value.kt | 8 ++- .../aisec/cpg_vis_neo4j/Application.kt | 14 ----- 7 files changed, 112 insertions(+), 90 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index f3a3948537..b62abe0be9 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -27,7 +27,6 @@ package de.fraunhofer.aisec.cpg.analysis.abstracteval import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Array import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Integer -import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.MutableList import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Value import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.statements.DoStatement @@ -40,6 +39,13 @@ import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import org.apache.commons.lang3.NotImplementedException +/** + * An evaluator performing abstract evaluation for a singular [Value]. It takes a target [Node] and + * walks back to its Declaration. From there it uses the [Worklist] to traverse the EOG graph until + * it reaches the node. All statements encountered may influence the result as implemented in the + * respective [Value] class. The result is a [LatticeInterval] defining both a lower and upper bound + * for the final value. + */ class AbstractEvaluator { // The node for which we want to get the value private lateinit var targetNode: Node @@ -48,6 +54,9 @@ class AbstractEvaluator { // The type of the value we are analyzing private lateinit var targetType: KClass + /** + * Takes a node (e.g. Reference) and tries to evaluate its value at this point in the program. + */ fun evaluate(node: Node): LatticeInterval { return evaluate( node.name.localName, @@ -58,6 +67,14 @@ class AbstractEvaluator { ) } + /** + * Takes a manual configuration and tries to evaluate the value of the node at the end. + * @param name The name of the target node + * @param start The beginning of the analysis, usually the start of the target's life + * @param end The place at which we want to know the target's value + * @param type The Type of the target + * @param interval The starting value of the analysis, optional + */ fun evaluate( name: String, start: Node, @@ -73,17 +90,16 @@ class AbstractEvaluator { val startState = IntervalState() startState.push(start, interval) val finalState = iterateEOG(start, startState, ::handleNode, targetNode) - // TODO: null-safety - return finalState!![targetNode]!!.elements + return finalState?.get(targetNode)?.elements ?: LatticeInterval.BOTTOM } /** - * This function delegates to the right handler depending on the next node. This is the handler - * used in _iterateEOG_ to correctly handle complex statements. + * This function changes the state depending on the current node. This is the handler used in + * _iterateEOG_ to correctly handle complex statements. * * @param currentNode The current node * @param state The state for the current node - * @param worklist The whole worklist to manually handle complex scenarios if necessary + * @param worklist The whole worklist to manually handle complex scenarios * @return The updated state after handling the current node */ private fun handleNode( @@ -91,10 +107,10 @@ class AbstractEvaluator { state: State, worklist: Worklist ): State { - // If the current node is already done + // Check if the current node is already DONE // (prevents infinite loop and unnecessary double-checking) if (worklist.isDone(currentNode)) { - // Mark following nodes as DONE if they only have this as previous EOG or all are DONE + // Mark following nodes as DONE if they only have this as previousEOG or all are DONE // In other cases, converging branches may still change the node currentNode.nextEOG.forEach { next -> if ( @@ -107,12 +123,12 @@ class AbstractEvaluator { return state } - // First calculate the effect + // Calculate the effect of the currentNode val previousInterval = state[currentNode]?.elements val newInterval = state.calculateEffect(currentNode) val newState = state.duplicate() - // If it was already seen exactly once or is known to need widening + // Check if it is marked as in need of widening if (worklist.needsWidening(currentNode)) { // Widen the interval val widenedInterval = previousInterval!!.widen(newInterval) @@ -136,7 +152,7 @@ class AbstractEvaluator { } } - // If it is marked as in need of narrowing + // Otherwise, check if it is marked as in need of narrowing else if (worklist.needsNarrowing(currentNode)) { // Narrow the interval val narrowedInterval = previousInterval!!.narrow(newInterval) @@ -157,7 +173,8 @@ class AbstractEvaluator { } } - // If it was seen for the first time apply the effect and maybe mark it as "NEEDS WIDENING" + // Otherwise, if it was seen for the first time directly apply the effect. + // If it is within a loop mark it as "NEEDS WIDENING". // We cannot use the "already_seen" field as it is set before this handler is called else { newState[currentNode] = IntervalLattice(newInterval) @@ -172,10 +189,10 @@ class AbstractEvaluator { } } - // We propagate the current Interval to all successor nodes which are empty - // If the next EOG already has a value we need to join them - // This is implemented in IntervalState.push - // Only do this if we have not reached the goal node + // Finally, we propagate the current Interval to all successor nodes which are empty. + // If the next EOG already has a value we need to join them. + // This is implemented in IntervalState.push. + // Only do this if we have not yet reached the goal node if (currentNode != targetNode) { currentNode.nextEOG.forEach { newState.push(it, newState[currentNode]) } } @@ -192,11 +209,10 @@ class AbstractEvaluator { } /** - * Tries to determine the Collection type of the target Node by parsing the type name. + * Tries to determine the type of the target Node by parsing the type name. * * @param node The target node - * @return A Kotlin class representing the collection that contains the necessary analysis - * functions + * @return A [Value] class that models the effects on the node type */ private fun getType(node: Node): KClass { if (node !is Reference) { @@ -204,11 +220,9 @@ class AbstractEvaluator { } val name = node.type.name.toString() return when { - // TODO: could be linkedList, arrayList, ... - name.startsWith("java.util.List") -> MutableList::class name.endsWith("[]") -> Array::class name == "int" -> Integer::class - else -> MutableList::class // throw NotImplementedException() + else -> throw NotImplementedException() } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 6022e431cd..82ffae985a 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -29,10 +29,16 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.helpers.LatticeElement import de.fraunhofer.aisec.cpg.helpers.State +/** + * The [LatticeInterval] class implements the functionality of intervals that is needed for the + * [AbstractEvaluator]. It is either a [BOTTOM] object signaling no knowledge or a [Bounded] object + * with a lower and upper [Bound]. Each [Bound] can then be [Bound.NEGATIVE_INFINITE], + * [Bound.INFINITE] or a [Bound.Value]. This class implements many convenience methods to handle the + * [LatticeInterval]. + */ sealed class LatticeInterval : Comparable { object BOTTOM : LatticeInterval() - // TODO: future iterations should support fractional values data class Bounded(val lower: Bound, val upper: Bound) : LatticeInterval() { constructor(lower: Int, upper: Int) : this(Bound.Value(lower), Bound.Value(upper)) @@ -41,6 +47,7 @@ sealed class LatticeInterval : Comparable { constructor(lower: Bound, upper: Int) : this(lower, Bound.Value(upper)) } + // TODO: future iterations should support fractional values sealed class Bound : Comparable { data class Value(val value: Int) : Bound() @@ -344,7 +351,7 @@ sealed class LatticeInterval : Comparable { /** * The [LatticeElement] that is used for worklist iteration. It wraps a single element of the type - * [LatticeInterval] + * [LatticeInterval]. */ class IntervalLattice(override val elements: LatticeInterval) : LatticeElement(elements) { @@ -352,7 +359,7 @@ class IntervalLattice(override val elements: LatticeInterval) : return elements.compareTo(other.elements) } - // Returns true whenever other is fully within this + /** Returns true iff [other] is fully within this */ fun contains(other: LatticeElement): Boolean { if (this.elements is LatticeInterval.BOTTOM || other.elements is LatticeInterval.BOTTOM) { return false @@ -364,7 +371,7 @@ class IntervalLattice(override val elements: LatticeInterval) : thisInterval.upper >= otherInterval.upper) } - // The least upper bound of two Intervals is given by the join operation + /** The least upper bound of two Intervals is given by the join operation. */ override fun lub(other: LatticeElement): LatticeElement { return IntervalLattice(this.elements.join(other.elements)) } @@ -386,11 +393,16 @@ class IntervalLattice(override val elements: LatticeInterval) : } } +/** + * A [State] that maps analyzed [Node]s to their [LatticeInterval]. Whenever new information for a + * known node is pushed, we join it with the previous known value to properly handle branch merges. + */ class IntervalState : State() { /** * Adds a new mapping from [newNode] to (a copy of) [newLatticeElement] to this object if - * [newNode] does not exist in this state yet. If it already exists, it will compute the lub - * over the new lattice Element and all predecessors. It returns whether the state has changed. + * [newNode] does not exist in this state yet. If it already exists, it will compute the [lub] + * over the new [LatticeElement] and the previous one. It returns whether the [LatticeElement] + * has changed. */ override fun push( newNode: de.fraunhofer.aisec.cpg.graph.Node, @@ -399,7 +411,7 @@ class IntervalState : State() { if (newLatticeElement == null) { return false } - val current = this[newNode] as? IntervalLattice + val current = this[newNode] if (current != null) { // Calculate the join of the new Element and the previous (propagated) value for the // node @@ -417,7 +429,7 @@ class IntervalState : State() { } /** - * Performs the same duplication as the parent function, but returns a [IntervalState] object + * Implements the same duplication as the parent function, but returns a [IntervalState] object * instead. */ override fun duplicate(): State { @@ -429,7 +441,7 @@ class IntervalState : State() { } /** - * Performs the same lub function as the parent, but uses the [push] function from + * Implements the same [lub] function as the parent, but uses the [push] function from * [LatticeInterval] */ override fun lub( diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt index ae8facf7d4..007c3f397b 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt @@ -31,13 +31,16 @@ import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression -import de.fraunhofer.aisec.cpg.graph.types.IntegerType import de.fraunhofer.aisec.cpg.query.value import org.apache.commons.lang3.NotImplementedException -class Array : Value { +/** + * This class implements the [Value] interface for Arrays, tracking the size of the collection. We + * assume that there is no operation that changes an array's size apart from re-declaring it. + */ +class Array : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { - // There are no functions that change the size of a Java array without destroying it + // (Re-)Declaration if (node is VariableDeclaration && node.initializer != null) { val initValue = getSize(node.initializer!!) return LatticeInterval.Bounded(initValue, initValue) @@ -47,13 +50,10 @@ class Array : Value { private fun getSize(node: Node): Int { return when (node) { - // TODO: could be more performant if you detect that all initializers are Literals + // TODO: depending on the desired behavior we could distinguish between included types + // (e.g. String and Int Literals) is Literal<*> -> { - if (node.type !is IntegerType) { - throw NotImplementedException() - } else { - 1 - } + 1 } is InitializerListExpression -> { node.initializers.fold(0) { acc, init -> acc + getSize(init) } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt index a1ff8fe93f..7c94a91126 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -34,8 +34,10 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import de.fraunhofer.aisec.cpg.query.value import org.apache.commons.lang3.NotImplementedException +/** This class implements the [Value] interface for Integer values. */ class Integer : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { + // (Re-)Declarations of the Variable if ( node is VariableDeclaration && node.initializer != null && node.name.localName == name ) { @@ -46,6 +48,7 @@ class Integer : Value { } return LatticeInterval.Bounded(initValue, initValue) } + // Unary Operators if (node is UnaryOperator) { if (node.input.code == name) { return when (node.operatorCode) { @@ -60,16 +63,17 @@ class Integer : Value { else -> current } } - } else if (node is AssignExpression) { + } + // Assignments and combined assign expressions + // TODO: we should aim to evaluate the right hand side expression for all cases! + // currently evaluation only works correctly for literals + else if (node is AssignExpression) { if (node.lhs.any { it.code == name }) { - // TODO: we should evaluate the right hand side expression for all cases! - // currently evaluation only works correctly for literals return when (node.operatorCode) { "=" -> { - var newInterval: LatticeInterval = current // If the rhs is only a literal use this exact value val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int - newInterval = + val newInterval = if (value != null) { LatticeInterval.Bounded(value, value) } @@ -83,10 +87,9 @@ class Integer : Value { newInterval } "+=" -> { - var newInterval: LatticeInterval = current // If the rhs is only a literal we subtract this exact value val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int - newInterval = + val newInterval = if (value != null) { val valueInterval = LatticeInterval.Bounded(value, value) current + valueInterval @@ -103,10 +106,9 @@ class Integer : Value { newInterval } "-=" -> { - var newInterval: LatticeInterval = current // If the rhs is only a literal we subtract this exact value val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int - newInterval = + val newInterval = if (value != null) { val valueInterval = LatticeInterval.Bounded(value, value) current - valueInterval @@ -123,10 +125,9 @@ class Integer : Value { newInterval } "*=" -> { - var newInterval: LatticeInterval = current // If the rhs is only a literal we subtract this exact value val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int - newInterval = + val newInterval = if (value != null) { val valueInterval = LatticeInterval.Bounded(value, value) current * valueInterval @@ -141,10 +142,9 @@ class Integer : Value { newInterval } "/=" -> { - var newInterval: LatticeInterval = current // If the rhs is only a literal we subtract this exact value val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int - newInterval = + val newInterval = if (value != null) { val valueInterval = LatticeInterval.Bounded(value, value) current / valueInterval @@ -159,10 +159,9 @@ class Integer : Value { newInterval } "%=" -> { - var newInterval: LatticeInterval = current // If the rhs is only a literal we subtract this exact value val value = (node.rhs.getOrNull(0) as? Literal<*>)?.value as? Int - newInterval = + val newInterval = if (value != null) { val valueInterval = LatticeInterval.Bounded(value, value) current % valueInterval diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt index 7bdf7943ed..4a394c6580 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt @@ -37,6 +37,12 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression import de.fraunhofer.aisec.cpg.graph.types.IntegerType import org.apache.commons.lang3.NotImplementedException +/** + * This class implements the [Value] interface for Mutable Lists, tracking the size of the + * collection. We assume that there is no operation that changes an array's size apart from + * re-declaring it. NOTE: This is an unpolished example implementation. Before actual usage consider + * the below TODOs and write a test file. + */ class MutableList : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { if (node is VariableDeclaration && node.initializer != null) { @@ -46,14 +52,12 @@ class MutableList : Value { return LatticeInterval.Bounded(size, size) } is NewExpression -> { - // TODO: could have a collection as argument! + // TODO: consider collection as argument! return LatticeInterval.Bounded(0, 0) } else -> throw NotImplementedException() } } - // TODO: state can also be estimated by conditions! (if (l.size < 3) ...) - // TODO: assignment -> new size // State can only be directly changed via MemberCalls (add, clear, ...) if (node !is MemberCallExpression) { return current @@ -79,18 +83,20 @@ class MutableList : Value { "remove" -> { // We have to differentiate between remove with index or object argument // Latter may do nothing if the element is not in the list - if (node.arguments.first().type is IntegerType) { - val oneInterval = LatticeInterval.Bounded(1, 1) - current - oneInterval - } else { - // TODO: If we know the list is empty, we know the operation has no effect - val oneZeroInterval = LatticeInterval.Bounded(1, 0) - current - oneZeroInterval - } + val modificationInterval = + if (node.arguments.first().type is IntegerType) { + LatticeInterval.Bounded(1, 1) + } else { + LatticeInterval.Bounded(1, 0) + } + // This meet makes sure we do not drop below zero + (current - modificationInterval).meet( + LatticeInterval.Bounded(0, LatticeInterval.Bound.INFINITE) + ) } - // TODO: as optimization we could check whether the argument list is empty. // The size of the argument list is (almost) irrelevant as it has no influence on the - // possible outcomes + // possible outcomes. + // We could check if it is empty, but that causes significant overhead "removeAll" -> { val zeroInterval = LatticeInterval.Bounded(0, 0) current.join(zeroInterval) @@ -100,18 +106,17 @@ class MutableList : Value { if (node.arguments.any { it.name.localName == name }) { // This is a function call that uses the variable as an argument. // To find side effects we need to create a local evaluator for this function - // and - // return the value of the renamed variable at the last statement - // TODO: this currently does not work if the variable is given for multiple - // parameters - // TODO: error handling + // and return the value of the renamed variable at the last statement. + // TODO: unfinished: + // this currently does not work if the variable is given for multiple + // parameters! val function = node.invokes.first() val argPos = node.arguments.indexOfFirst { it.name.localName == name } // We cannot take the "first" as that refers to the Block which has no nextEOG - // Also debugging is ugly because the getter of Node.statements is overwritten val functionStart = function.body.statements[1] - // This could be a Location but the CPG often just hands us "null" + // This variable should be a PhysicalLocation but the CPG often just hands us + // "null" val functionEnd = function.body.statements.last() val newTargetName = function.parameters[argPos].name.localName diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt index 0b709ad9a8..3a68e87751 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt @@ -30,8 +30,14 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +/** + * The [Value] interface is used by the AbstractEvaluator to store the behaviour of different + * analysis targets. Each class implementing this interface is expected to define all operations + * that might affect its internal value in [applyEffect]. When adding new classes remember to add + * them to AbstractEvaluator.getType and add tests. + */ interface Value { - /** Applies the effect of a Node to the Interval describing possible values of a collection. */ + /** Applies the effect of a Node to the interval containing its possible values. */ fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval companion object { diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index fe6efa4576..9bcf977be6 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -620,20 +620,6 @@ class Application : Callable { "Benchmark: analyzing code in " + (analyzingTime - startTime) / S_TO_MS_FACTOR + " s." ) - // For Testing, remove later! - val nodes = translationResult.nodes - val targetNodes = nodes.filter { it.name.localName == "a" } - val focusNode = - targetNodes.first { - it.location?.region?.startLine == 101 && - it is Reference && - it.type.name.toString() == - "int" // "java.util.LinkedList" // "int[]" - } - val size = AbstractEvaluator().evaluate(focusNode) - println(size) - return EXIT_SUCCESS - exportJsonFile?.let { exportToJson(translationResult, it) } if (!noNeo4j) { pushToNeo4j(translationResult) From 68e3b27fb69529e6dc6c95c909712ded2679a945 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 12:10:12 +0100 Subject: [PATCH 36/58] remove old code --- .../abstracteval/AbstractEvaluator.kt | 1 + .../aisec/cpg/analysis/SizeEvaluatorTest.kt | 20 ---------------- .../cpg/testcases/ValueEvaluationTests.kt | 23 ------------------- cpg-neo4j/build.gradle.kts | 2 -- .../aisec/cpg_vis_neo4j/Application.kt | 3 --- 5 files changed, 1 insertion(+), 48 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index b62abe0be9..31323736d6 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -69,6 +69,7 @@ class AbstractEvaluator { /** * Takes a manual configuration and tries to evaluate the value of the node at the end. + * * @param name The name of the target node * @param start The beginning of the analysis, usually the start of the target's life * @param end The place at which we want to know the target's value diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index 56b237bcb3..89a3d9ec42 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt @@ -116,24 +116,4 @@ class SizeEvaluatorTest { val strValue = evaluator.evaluate("abcd") as Int assertEquals(4, strValue) } - - @Test - fun testListSize() { - val mainClass = tu.records["MainClass"] - assertNotNull(mainClass) - val main = mainClass.methods["main"] - assertNotNull(main) - - val list = main.bodyOrNull(7)?.singleDeclaration - assertNotNull(list) - - val printCall = main.calls("println").getOrNull(2) - assertNotNull(printCall) - val printArg = printCall.arguments.first() - assertNotNull(printArg) - - val evaluator = AbstractEvaluator() - val value = evaluator.evaluate(printArg) - assertEquals(LatticeInterval.Bounded(0, 2), value) - } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/ValueEvaluationTests.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/ValueEvaluationTests.kt index 2eca132843..256ece4b8d 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/ValueEvaluationTests.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/testcases/ValueEvaluationTests.kt @@ -84,29 +84,6 @@ class ValueEvaluationTests { memberCall("println", member("out", ref("System"))) { ref("str") } - - declare { - variable("list", t("list")) { - val init = - newMemberCallExpression( - null, - true, - ) - init.addArgument(newLiteral("1")) - init.name = Name("of", Name("List")) - this.initializer = init - } - } - newMemberCallExpression( - memberCall("add", ref("list"), false) { literal("2") } - ) - newMemberCallExpression( - memberCall("removeAll", ref("list"), false) { literal("3") } - ) - memberCall("println", member("out", ref("System")), true) { - ref("list") - } - returnStmt {} } } diff --git a/cpg-neo4j/build.gradle.kts b/cpg-neo4j/build.gradle.kts index 41b9e48ed9..82377fb321 100644 --- a/cpg-neo4j/build.gradle.kts +++ b/cpg-neo4j/build.gradle.kts @@ -56,6 +56,4 @@ dependencies { annotationProcessor(libs.picocli.codegen) testImplementation(testFixtures(projects.cpgCore)) - // For testing, remove later! - implementation(projects.cpgAnalysis) } diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 9bcf977be6..1ae5207abd 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -27,10 +27,7 @@ package de.fraunhofer.aisec.cpg_vis_neo4j import com.fasterxml.jackson.databind.ObjectMapper import de.fraunhofer.aisec.cpg.* -import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile -import de.fraunhofer.aisec.cpg.graph.nodes -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.passes.* import java.io.File From e48b709d4c21b4dfdb433a0c9ecb618b9ebfc245 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 28 Oct 2024 12:18:02 +0100 Subject: [PATCH 37/58] import cleanup --- .../fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt | 1 - .../aisec/cpg/analysis/abstracteval/value/MutableList.kt | 1 + .../de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt index 7c94a91126..07147c43cc 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -31,7 +31,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator -import de.fraunhofer.aisec.cpg.query.value import org.apache.commons.lang3.NotImplementedException /** This class implements the [Value] interface for Integer values. */ diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt index 4a394c6580..565aff5b5d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt @@ -43,6 +43,7 @@ import org.apache.commons.lang3.NotImplementedException * re-declaring it. NOTE: This is an unpolished example implementation. Before actual usage consider * the below TODOs and write a test file. */ +@Suppress("UNUSED") class MutableList : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { if (node is VariableDeclaration && node.initializer != null) { diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index 89a3d9ec42..41dd4bc3a9 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt @@ -25,8 +25,6 @@ */ package de.fraunhofer.aisec.cpg.analysis -import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator -import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration From 44e80791c02efaffbcbea21509d037704a13d874 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 11:31:49 +0100 Subject: [PATCH 38/58] automatically switch interval bounds if the lower bound is greater than the upper bound --- .../analysis/abstracteval/LatticeInterval.kt | 22 +++++++++++++++---- .../cpg/analysis/abstracteval/value/Value.kt | 2 +- .../AbstractEvaluatorTest.kt | 4 +--- 3 files changed, 20 insertions(+), 8 deletions(-) rename cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/{ => abstracteval}/AbstractEvaluatorTest.kt (96%) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 82ffae985a..7c762f004d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -39,12 +39,26 @@ import de.fraunhofer.aisec.cpg.helpers.State sealed class LatticeInterval : Comparable { object BOTTOM : LatticeInterval() - data class Bounded(val lower: Bound, val upper: Bound) : LatticeInterval() { - constructor(lower: Int, upper: Int) : this(Bound.Value(lower), Bound.Value(upper)) + data class Bounded(val arg1: Bound, val arg2: Bound) : LatticeInterval() { + val lower: Bound + val upper: Bound - constructor(lower: Int, upper: Bound) : this(Bound.Value(lower), upper) + constructor(arg1: Int, arg2: Int) : this(Bound.Value(arg1), Bound.Value(arg2)) - constructor(lower: Bound, upper: Int) : this(lower, Bound.Value(upper)) + constructor(arg1: Int, arg2: Bound) : this(Bound.Value(arg1), arg2) + + constructor(arg1: Bound, arg2: Int) : this(arg1, Bound.Value(arg2)) + + // Automatically switch the arguments if the upper bound is lower than the lower bound + init { + if (arg1 > arg2) { + lower = arg2 + upper = arg1 + } else { + lower = arg1 + upper = arg2 + } + } } // TODO: future iterations should support fractional values diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt index 3a68e87751..41c473917a 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Value.kt @@ -43,7 +43,7 @@ interface Value { companion object { fun getInitializer(node: Node?): Node? { return when (node) { - null -> null!! + null -> null is Reference -> getInitializer(node.refersTo) is VariableDeclaration -> node else -> getInitializer(node.prevDFG.firstOrNull()) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluatorTest.kt similarity index 96% rename from cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt rename to cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluatorTest.kt index d1bad35a2d..40be65a747 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AbstractEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluatorTest.kt @@ -23,10 +23,8 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis +package de.fraunhofer.aisec.cpg.analysis.abstracteval -import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator -import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration From eac9e892db9a3646c1a54a0e5cf5b3631dea12f4 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 11:33:35 +0100 Subject: [PATCH 39/58] remove min and max comparisons after multiplication as the constructor now handles the order --- .../aisec/cpg/analysis/abstracteval/LatticeInterval.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 7c762f004d..627b84bb07 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -149,7 +149,7 @@ sealed class LatticeInterval : Comparable { this is Bounded && other is Bounded -> { val newLower = multiplyBounds(this.lower, other.lower) val newUpper = multiplyBounds(this.upper, other.upper) - Bounded(min(newLower, newUpper), max(newLower, newUpper)) + Bounded(newLower, newUpper) } else -> throw IllegalArgumentException("Unsupported interval type") } @@ -162,7 +162,7 @@ sealed class LatticeInterval : Comparable { this is Bounded && other is Bounded -> { val newLower = divideBounds(this.lower, other.lower) val newUpper = divideBounds(this.upper, other.upper) - Bounded(min(newLower, newUpper), max(newLower, newUpper)) + Bounded(newLower, newUpper) } else -> throw IllegalArgumentException("Unsupported interval type") } From 6a3965bddfb5f0378adce78cf53f984e4209c20a Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 11:37:15 +0100 Subject: [PATCH 40/58] convert Bounded from data class to real class to remove constructor variables --- .../aisec/cpg/analysis/abstracteval/LatticeInterval.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 627b84bb07..f201a6311d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -39,7 +39,7 @@ import de.fraunhofer.aisec.cpg.helpers.State sealed class LatticeInterval : Comparable { object BOTTOM : LatticeInterval() - data class Bounded(val arg1: Bound, val arg2: Bound) : LatticeInterval() { + class Bounded(arg1: Bound, arg2: Bound) : LatticeInterval() { val lower: Bound val upper: Bound From 448363c6bcd8ca5688134034a6edfaeaca2a419b Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 11:42:55 +0100 Subject: [PATCH 41/58] =?UTF-8?q?fix=20division=20to=20estimate=20x=20/=20?= =?UTF-8?q?=E2=88=9E=20=3D=200?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aisec/cpg/analysis/abstracteval/LatticeInterval.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index f201a6311d..5085cd6e64 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -317,6 +317,7 @@ sealed class LatticeInterval : Comparable { private fun divideBounds(a: Bound, b: Bound): Bound { return when { // ∞ / ∞ is not a defined operation + // x / 0 is not a defined operation a is Bound.INFINITE && b > Bound.Value(0) && b !is Bound.INFINITE -> Bound.INFINITE a is Bound.INFINITE && b < Bound.Value(0) && b !is Bound.NEGATIVE_INFINITE -> Bound.NEGATIVE_INFINITE @@ -327,10 +328,9 @@ sealed class LatticeInterval : Comparable { Bound.NEGATIVE_INFINITE a is Bound.NEGATIVE_INFINITE && b < Bound.Value(0) && b !is Bound.NEGATIVE_INFINITE -> Bound.INFINITE - a > Bound.Value(0) && a !is Bound.INFINITE && b is Bound.NEGATIVE_INFINITE -> - Bound.NEGATIVE_INFINITE - a < Bound.Value(0) && a !is Bound.NEGATIVE_INFINITE && b is Bound.NEGATIVE_INFINITE -> - Bound.INFINITE + // We estimate x / ∞ as 0 + a != Bound.Value(0) && a !is Bound.INFINITE && a !is Bound.NEGATIVE_INFINITE && (b is Bound.NEGATIVE_INFINITE || b is Bound.INFINITE) -> + Bound.Value(0) a is Bound.Value && b is Bound.Value -> Bound.Value(a.value / b.value) else -> throw IllegalArgumentException("Unsupported bound type") } From 7bde4ca76d75c15d562fdc8fe19efa7e6d69d238 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 11:44:57 +0100 Subject: [PATCH 42/58] simplify division cases --- .../aisec/cpg/analysis/abstracteval/LatticeInterval.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 5085cd6e64..3b215dcf76 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -321,15 +321,12 @@ sealed class LatticeInterval : Comparable { a is Bound.INFINITE && b > Bound.Value(0) && b !is Bound.INFINITE -> Bound.INFINITE a is Bound.INFINITE && b < Bound.Value(0) && b !is Bound.NEGATIVE_INFINITE -> Bound.NEGATIVE_INFINITE - a > Bound.Value(0) && a !is Bound.INFINITE && b is Bound.INFINITE -> Bound.INFINITE - a < Bound.Value(0) && a !is Bound.NEGATIVE_INFINITE && b is Bound.INFINITE -> - Bound.NEGATIVE_INFINITE a is Bound.NEGATIVE_INFINITE && b > Bound.Value(0) && b !is Bound.INFINITE -> Bound.NEGATIVE_INFINITE a is Bound.NEGATIVE_INFINITE && b < Bound.Value(0) && b !is Bound.NEGATIVE_INFINITE -> Bound.INFINITE // We estimate x / ∞ as 0 - a != Bound.Value(0) && a !is Bound.INFINITE && a !is Bound.NEGATIVE_INFINITE && (b is Bound.NEGATIVE_INFINITE || b is Bound.INFINITE) -> + a != Bound.Value(0) && (b is Bound.NEGATIVE_INFINITE || b is Bound.INFINITE) -> Bound.Value(0) a is Bound.Value && b is Bound.Value -> Bound.Value(a.value / b.value) else -> throw IllegalArgumentException("Unsupported bound type") From 5bb2316acf116f602b523a3e8e5d85eb20170cdb Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 11:49:11 +0100 Subject: [PATCH 43/58] Fix invalid division cases --- .../aisec/cpg/analysis/abstracteval/LatticeInterval.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index 3b215dcf76..e407458005 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -325,10 +325,12 @@ sealed class LatticeInterval : Comparable { Bound.NEGATIVE_INFINITE a is Bound.NEGATIVE_INFINITE && b < Bound.Value(0) && b !is Bound.NEGATIVE_INFINITE -> Bound.INFINITE - // We estimate x / ∞ as 0 - a != Bound.Value(0) && (b is Bound.NEGATIVE_INFINITE || b is Bound.INFINITE) -> - Bound.Value(0) - a is Bound.Value && b is Bound.Value -> Bound.Value(a.value / b.value) + // We estimate x / ∞ as 0 (with x != ∞) + a !is Bound.NEGATIVE_INFINITE && + a !is Bound.INFINITE && + (b is Bound.NEGATIVE_INFINITE || b is Bound.INFINITE) -> Bound.Value(0) + a is Bound.Value && b is Bound.Value && b != Bound.Value(0) -> + Bound.Value(a.value / b.value) else -> throw IllegalArgumentException("Unsupported bound type") } } From 14c719700cd89a27daae4467267cefdfd97105c8 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 12:11:15 +0100 Subject: [PATCH 44/58] make modulo calculation more concise --- .../cpg/analysis/abstracteval/LatticeInterval.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index e407458005..d3b67a5ae7 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -338,14 +338,15 @@ sealed class LatticeInterval : Comparable { // ∞ mod b can be any number [0, b], therefore we need to return an Interval private fun modulateBounds(a: Bound, b: Bound): LatticeInterval { return when { - // ∞ mod ∞ is not a defined operation + // x mod 0 is not a defined operation + // we approximate ∞ mod x as any number [0, b] (with x != ∞) + // x mod -∞ is not a defined operation a == Bound.Value(0) -> Bounded(0, 0) - (a is Bound.INFINITE || a is Bound.NEGATIVE_INFINITE) && b >= Bound.Value(0) -> + (a is Bound.INFINITE || a is Bound.NEGATIVE_INFINITE) && b != Bound.Value(0) -> Bounded(0, b) - (a is Bound.INFINITE || a is Bound.NEGATIVE_INFINITE) && b < Bound.Value(0) -> - Bounded(b, 0) - b is Bound.INFINITE || b is Bound.NEGATIVE_INFINITE -> Bounded(a, a) - a is Bound.Value && b is Bound.Value -> Bounded(a.value % b.value, a.value % b.value) + b is Bound.INFINITE -> Bounded(a, a) + a is Bound.Value && b is Bound.Value && b != Bound.Value(0) -> + Bounded(a.value % b.value, a.value % b.value) else -> throw IllegalArgumentException("Unsupported bound type") } } From 0a29a53717c146a684116661822ab5ab5a0b5470 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 12:26:01 +0100 Subject: [PATCH 45/58] beautify toString representation --- .../analysis/abstracteval/LatticeInterval.kt | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index d3b67a5ae7..a01c3999d4 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -59,11 +59,19 @@ sealed class LatticeInterval : Comparable { upper = arg2 } } + + override fun toString(): String { + return "[$lower, $upper]" + } } // TODO: future iterations should support fractional values sealed class Bound : Comparable { - data class Value(val value: Int) : Bound() + data class Value(val value: Int) : Bound() { + override fun toString(): String { + return value.toString() + } + } // necessary values for widening and narrowing data object NEGATIVE_INFINITE : Bound() @@ -80,6 +88,14 @@ sealed class LatticeInterval : Comparable { else -> 0 } } + + override fun toString(): String { + return when (this) { + is Value -> value.toString() + is INFINITE -> "INFINITE" + is NEGATIVE_INFINITE -> "NEGATIVE_INFINITE" + } + } } // Comparing two Intervals. They are treated as equal if they overlap @@ -354,7 +370,7 @@ sealed class LatticeInterval : Comparable { override fun toString(): String { return when (this) { is BOTTOM -> "BOTTOM" - is Bounded -> "[$lower, $upper]" + is Bounded -> this.toString() } } From d9cb9861b0ca58fc1a95e8b49f012a55eee261b9 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 12:26:24 +0100 Subject: [PATCH 46/58] add first LatticeInterval tests --- .../abstracteval/LatticeIntervalTest.kt | 315 ++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt new file mode 100644 index 0000000000..efa4bb664e --- /dev/null +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt @@ -0,0 +1,315 @@ +/* + * 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.cpg.analysis.abstracteval + +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.BOTTOM +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.Bound.INFINITE +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.Bound.NEGATIVE_INFINITE +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.Bounded +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows + +class LatticeIntervalTest { + @Test + fun testCreate() { + assertDoesNotThrow { BOTTOM } + assertDoesNotThrow { Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) } + assertDoesNotThrow { Bounded(INFINITE, INFINITE) } + assertDoesNotThrow { Bounded(NEGATIVE_INFINITE, INFINITE) } + assertDoesNotThrow { Bounded(0, 0) } + assertDoesNotThrow { Bounded(-5, 5) } + + // Test whether the arguments are switched if necessary + assertEquals(Bounded(NEGATIVE_INFINITE, INFINITE), Bounded(INFINITE, NEGATIVE_INFINITE)) + assertEquals(Bounded(-5, 5), Bounded(5, -5)) + } + + @Test + fun testCompare() { + // comparison including BOTTOM + assertEquals(0, BOTTOM.compareTo(BOTTOM)) + assertEquals(-1, BOTTOM.compareTo(Bounded(-1, 1))) + assertEquals(-1, BOTTOM.compareTo(Bounded(NEGATIVE_INFINITE, INFINITE))) + assertEquals(1, Bounded(-1, 1).compareTo(BOTTOM)) + assertEquals(1, Bounded(NEGATIVE_INFINITE, INFINITE).compareTo(BOTTOM)) + + // comparison with non-overlapping intervals + assertEquals(-1, Bounded(NEGATIVE_INFINITE, -1).compareTo(Bounded(1, INFINITE))) + assertEquals(1, Bounded(1, INFINITE).compareTo(Bounded(NEGATIVE_INFINITE, -1))) + + // comparison with overlapping intervals + assertEquals(0, Bounded(NEGATIVE_INFINITE, 0).compareTo(Bounded(0, INFINITE))) + assertEquals(0, Bounded(-4, 2).compareTo(Bounded(-2, 4))) + assertEquals(0, Bounded(-5, 5).compareTo(Bounded(1, 3))) + assertEquals(0, Bounded(-3, 1).compareTo(Bounded(-5, 5))) + } + + @Test + fun testEquals() { + // comparison with BOTTOM + assertFalse(BOTTOM.equals(AbstractEvaluator())) + assertFalse(BOTTOM.equals(null)) + assertFalse(BOTTOM.equals(Bounded(0, 0))) + assertFalse(Bounded(0, 0).equals(BOTTOM)) + assertEquals(BOTTOM, BOTTOM) + + // comparison with different Intervals + assertNotEquals(Bounded(NEGATIVE_INFINITE, 0), Bounded(0, INFINITE)) + assertNotEquals(Bounded(0, 10), Bounded(0, 9)) + assertNotEquals(Bounded(0, 10), Bounded(1, 10)) + assertNotEquals(Bounded(0, 10), Bounded(5, 6)) + assertNotEquals(Bounded(0, 9), Bounded(0, 10)) + assertNotEquals(Bounded(1, 10), Bounded(0, 10)) + assertNotEquals(Bounded(5, 6), Bounded(0, 10)) + + // comparison with same Intervals + assertEquals(Bounded(-5, 5), Bounded(-5, 5)) + assertEquals(Bounded(0, 0), Bounded(0, 0)) + assertEquals(Bounded(NEGATIVE_INFINITE, INFINITE), Bounded(NEGATIVE_INFINITE, INFINITE)) + } + + @Test + fun testAddition() { + // With BOTTOM + assertEquals(BOTTOM, BOTTOM + Bounded(5, 5)) + assertEquals(BOTTOM, Bounded(5, 5) + BOTTOM) + assertEquals(BOTTOM, BOTTOM + Bounded(NEGATIVE_INFINITE, INFINITE)) + assertEquals(BOTTOM, Bounded(NEGATIVE_INFINITE, INFINITE) + BOTTOM) + assertEquals(BOTTOM, BOTTOM + BOTTOM) + + // Without BOTTOM + assertEquals(Bounded(INFINITE, INFINITE), Bounded(INFINITE, INFINITE) + Bounded(-5, 5)) + assertEquals( + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE), + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) + Bounded(-5, 5) + ) + assertEquals(Bounded(INFINITE, INFINITE), Bounded(-5, 5) + Bounded(INFINITE, INFINITE)) + assertEquals( + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE), + Bounded(-5, 5) + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) + ) + assertEquals(Bounded(-8, 8), Bounded(-5, 5) + Bounded(-3, 3)) + + // Illegal Operations + assertThrows { + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) + Bounded(0, INFINITE) + } + assertThrows { + Bounded(INFINITE, INFINITE) + Bounded(NEGATIVE_INFINITE, 0) + } + } + + @Test + fun testSubtraction() { + // With BOTTOM + assertEquals(BOTTOM, BOTTOM - Bounded(5, 5)) + assertEquals(BOTTOM, Bounded(5, 5) - BOTTOM) + assertEquals(BOTTOM, BOTTOM - Bounded(NEGATIVE_INFINITE, INFINITE)) + assertEquals(BOTTOM, Bounded(NEGATIVE_INFINITE, INFINITE) - BOTTOM) + assertEquals(BOTTOM, BOTTOM - BOTTOM) + + // Without BOTTOM + assertEquals(Bounded(INFINITE, INFINITE), Bounded(INFINITE, INFINITE) - Bounded(-5, 5)) + assertEquals( + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE), + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) - Bounded(-5, 5) + ) + assertEquals( + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE), + Bounded(-5, 5) - Bounded(INFINITE, INFINITE) + ) + assertEquals( + Bounded(INFINITE, INFINITE), + Bounded(-5, 5) - Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) + ) + assertEquals(Bounded(-2, 2), Bounded(-5, 5) - Bounded(-3, 3)) + + // Illegal Operations + assertThrows { Bounded(-10, INFINITE) - Bounded(0, INFINITE) } + assertThrows { + Bounded(NEGATIVE_INFINITE, 10) - Bounded(NEGATIVE_INFINITE, 0) + } + } + + @Test + fun testMultiplication() { + // With BOTTOM + assertEquals(BOTTOM, BOTTOM * Bounded(5, 5)) + assertEquals(BOTTOM, Bounded(5, 5) * BOTTOM) + assertEquals(BOTTOM, BOTTOM * Bounded(NEGATIVE_INFINITE, INFINITE)) + assertEquals(BOTTOM, Bounded(NEGATIVE_INFINITE, INFINITE) * BOTTOM) + assertEquals(BOTTOM, BOTTOM * BOTTOM) + + // Without BOTTOM + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(INFINITE, INFINITE) * Bounded(-5, 5) + ) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) * Bounded(-5, 5) + ) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(-5, 5) * Bounded(INFINITE, INFINITE) + ) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(-5, 5) * Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) + ) + assertEquals(Bounded(15, 15), Bounded(-5, 5) * Bounded(-3, 3)) + + // Illegal Operations + assertThrows { + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) * Bounded(0, INFINITE) + } + assertThrows { + Bounded(INFINITE, INFINITE) * Bounded(NEGATIVE_INFINITE, 0) + } + } + + @Test + fun testDivision() { + // With BOTTOM + assertEquals(BOTTOM, BOTTOM / Bounded(5, 5)) + assertEquals(BOTTOM, Bounded(5, 5) / BOTTOM) + assertEquals(BOTTOM, BOTTOM / Bounded(NEGATIVE_INFINITE, INFINITE)) + assertEquals(BOTTOM, Bounded(NEGATIVE_INFINITE, INFINITE) / BOTTOM) + assertEquals(BOTTOM, BOTTOM / BOTTOM) + + // Without BOTTOM + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(INFINITE, INFINITE) / Bounded(-5, 5) + ) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) / Bounded(-5, 5) + ) + assertEquals(Bounded(0, 0), Bounded(-5, 5) / Bounded(INFINITE, INFINITE)) + assertEquals(Bounded(0, 0), Bounded(-5, 5) / Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE)) + assertEquals(Bounded(5, 5), Bounded(-15, 15) / Bounded(-3, 3)) + + // Illegal Operations + assertThrows { + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) / Bounded(1, INFINITE) + } + assertThrows { + Bounded(INFINITE, INFINITE) / Bounded(NEGATIVE_INFINITE, 1) + } + assertThrows { + Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) / Bounded(NEGATIVE_INFINITE, 1) + } + assertThrows { + Bounded(INFINITE, INFINITE) / Bounded(1, INFINITE) + } + assertThrows { Bounded(2, 4) / Bounded(1, 0) } + } + + @Test + fun testModulo() { + // With BOTTOM + assertEquals(BOTTOM, BOTTOM % Bounded(5, 5)) + assertEquals(BOTTOM, Bounded(5, 5) % BOTTOM) + assertEquals(BOTTOM, BOTTOM % Bounded(NEGATIVE_INFINITE, INFINITE)) + assertEquals(BOTTOM, Bounded(NEGATIVE_INFINITE, INFINITE) % BOTTOM) + assertEquals(BOTTOM, BOTTOM % BOTTOM) + + // Without BOTTOM + assertEquals(Bounded(0, 10), Bounded(INFINITE, INFINITE) % Bounded(5, 10)) + assertEquals(Bounded(0, 10), Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) % Bounded(5, 10)) + assertEquals(Bounded(-5, 5), Bounded(-5, 5) % Bounded(INFINITE, INFINITE)) + assertEquals(Bounded(-1, 1), Bounded(-10, 10) % Bounded(-3, 3)) + + // Illegal Operations + assertThrows { Bounded(-5, 5) % Bounded(0, 5) } + assertThrows { Bounded(-5, 5) % Bounded(-5, 0) } + assertThrows { + Bounded(-5, 5) % Bounded(NEGATIVE_INFINITE, NEGATIVE_INFINITE) + } + } + + @Test + fun testJoin() { + // With BOTTOM + assertEquals(BOTTOM, BOTTOM.join(Bounded(5, 5))) + assertEquals(BOTTOM, Bounded(5, 5).join(BOTTOM)) + assertEquals(BOTTOM, BOTTOM.join(Bounded(NEGATIVE_INFINITE, INFINITE))) + assertEquals(BOTTOM, Bounded(NEGATIVE_INFINITE, INFINITE).join(BOTTOM)) + assertEquals(BOTTOM, BOTTOM.join(BOTTOM)) + + // Without BOTTOM + assertEquals( + Bounded(NEGATIVE_INFINITE, 10), + Bounded(5, 10).join(Bounded(NEGATIVE_INFINITE, -5)) + ) + assertEquals(Bounded(-10, INFINITE), Bounded(-10, -5).join(Bounded(5, INFINITE))) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(0, 0).join(Bounded(NEGATIVE_INFINITE, INFINITE)) + ) + assertEquals(Bounded(-10, 10), Bounded(9, 10).join(Bounded(-10, -9))) + } + + @Test + fun testMeet() { + // TODO + } + + @Test + fun testWiden() { + // TODO + } + + @Test + fun testNarrow() { + // TODO + } + + @Test + fun testMin() { + // TODO + } + + @Test + fun testMax() { + // TODO + } + + @Test + fun testToString() { + // assertEquals("BOTTOM", BOTTOM.toString()) + // assertEquals( + // "[NEGATIVE_INFINITE, INFINITE]", + // Bounded(NEGATIVE_INFINITE, INFINITE).toString() + // ) + assertEquals("[-5, 5]", Bounded(-5, 5).toString()) + } +} From b61a97049de2c64d7080e51b54fb7a088f594537 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Wed, 30 Oct 2024 12:36:51 +0100 Subject: [PATCH 47/58] fix meet of intervals when they do not overlap --- .../aisec/cpg/analysis/abstracteval/LatticeInterval.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index a01c3999d4..ccdc1a038c 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -215,6 +215,8 @@ sealed class LatticeInterval : Comparable { return when { this is BOTTOM -> other other is BOTTOM -> this + // Check if the overlap at all + this.compareTo(other) != 0 -> BOTTOM this is Bounded && other is Bounded -> { val newLower = max(this.lower, other.lower) val newUpper = min(this.upper, other.upper) From 70e6853a6e570abdc1e67ab64a59f48aa1855c37 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 10:43:58 +0100 Subject: [PATCH 48/58] add equals operator for the Interval wrapper --- .../aisec/cpg/analysis/abstracteval/LatticeInterval.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt index ccdc1a038c..8a3aadc0c8 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeInterval.kt @@ -99,6 +99,7 @@ sealed class LatticeInterval : Comparable { } // Comparing two Intervals. They are treated as equal if they overlap + // BOTTOM intervals are considered "smaller" than known intervals override fun compareTo(other: LatticeInterval): Int { return when { this is BOTTOM && other !is BOTTOM -> -1 @@ -391,6 +392,13 @@ class IntervalLattice(override val elements: LatticeInterval) : return elements.compareTo(other.elements) } + override fun equals(other: Any?): Boolean { + if (other !is IntervalLattice) { + return false + } + return this.elements == other.elements + } + /** Returns true iff [other] is fully within this */ fun contains(other: LatticeElement): Boolean { if (this.elements is LatticeInterval.BOTTOM || other.elements is LatticeInterval.BOTTOM) { From dbdb0f7d8f246adf7eb8fa4f1211fec355793a94 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 10:45:04 +0100 Subject: [PATCH 49/58] add tests for meet, widen, narrow and the wrapper --- .../abstracteval/LatticeIntervalTest.kt | 97 ++++++++++++++++--- 1 file changed, 82 insertions(+), 15 deletions(-) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt index efa4bb664e..7d4102fd1d 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt @@ -29,10 +29,8 @@ import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.BOTTOM import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.Bound.INFINITE import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.Bound.NEGATIVE_INFINITE import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.Bounded +import kotlin.test.* import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows @@ -280,27 +278,68 @@ class LatticeIntervalTest { @Test fun testMeet() { - // TODO + // With BOTTOM + assertEquals(BOTTOM, BOTTOM.join(Bounded(5, 5))) + assertEquals(BOTTOM, Bounded(5, 5).join(BOTTOM)) + assertEquals(BOTTOM, BOTTOM.join(Bounded(NEGATIVE_INFINITE, INFINITE))) + assertEquals(BOTTOM, Bounded(NEGATIVE_INFINITE, INFINITE).join(BOTTOM)) + assertEquals(BOTTOM, BOTTOM.join(BOTTOM)) + + // Without BOTTOM + assertEquals(BOTTOM, Bounded(5, 10).join(Bounded(NEGATIVE_INFINITE, -5))) + assertEquals(BOTTOM, Bounded(-10, -5).join(Bounded(5, INFINITE))) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(0, 0).join(Bounded(NEGATIVE_INFINITE, INFINITE)) + ) + assertEquals(Bounded(-10, 10), Bounded(9, 10).join(Bounded(-10, -9))) } @Test fun testWiden() { - // TODO - } + // With BOTTOM + assertEquals(Bounded(5, 5), BOTTOM.widen(Bounded(5, 5))) + assertEquals(Bounded(5, 5), Bounded(5, 5).widen(BOTTOM)) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + BOTTOM.widen(Bounded(NEGATIVE_INFINITE, INFINITE)) + ) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(NEGATIVE_INFINITE, INFINITE).widen(BOTTOM) + ) + assertEquals(BOTTOM, BOTTOM.widen(BOTTOM)) - @Test - fun testNarrow() { - // TODO + // Without BOTTOM + assertEquals( + Bounded(NEGATIVE_INFINITE, 10), + Bounded(5, 10).widen(Bounded(NEGATIVE_INFINITE, -5)) + ) + assertEquals(Bounded(-10, INFINITE), Bounded(-10, -5).widen(Bounded(5, INFINITE))) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(0, 0).widen(Bounded(NEGATIVE_INFINITE, INFINITE)) + ) + assertEquals(Bounded(NEGATIVE_INFINITE, 10), Bounded(9, 10).widen(Bounded(-10, -9))) + assertEquals(Bounded(-10, INFINITE), Bounded(-10, -9).widen(Bounded(9, 10))) } @Test - fun testMin() { - // TODO - } + fun testNarrow() { + // With BOTTOM + assertEquals(BOTTOM, BOTTOM.narrow(Bounded(5, 5))) + assertEquals(BOTTOM, Bounded(5, 5).narrow(BOTTOM)) + assertEquals(BOTTOM, BOTTOM.narrow(Bounded(NEGATIVE_INFINITE, INFINITE))) + assertEquals(BOTTOM, Bounded(NEGATIVE_INFINITE, INFINITE).narrow(BOTTOM)) + assertEquals(BOTTOM, BOTTOM.narrow(BOTTOM)) - @Test - fun testMax() { - // TODO + // Without BOTTOM + assertEquals(Bounded(5, 10), Bounded(5, 10).narrow(Bounded(NEGATIVE_INFINITE, -5))) + assertEquals(Bounded(-10, -5), Bounded(-10, -5).narrow(Bounded(5, INFINITE))) + assertEquals(Bounded(0, 0), Bounded(0, 0).narrow(Bounded(NEGATIVE_INFINITE, INFINITE))) + assertEquals(Bounded(-5, 5), Bounded(NEGATIVE_INFINITE, -5).narrow(Bounded(5, 10))) + assertEquals(Bounded(-5, 5), Bounded(5, INFINITE).narrow(Bounded(-10, -5))) + assertEquals(Bounded(0, 0), Bounded(NEGATIVE_INFINITE, INFINITE).narrow(Bounded(0, 0))) } @Test @@ -312,4 +351,32 @@ class LatticeIntervalTest { // ) assertEquals("[-5, 5]", Bounded(-5, 5).toString()) } + + @Test + fun testWrapper() { + val bottomWrapper = IntervalLattice(BOTTOM) + val zeroWrapper = IntervalLattice(Bounded(0, 0)) + val outerWrapper = IntervalLattice(Bounded(-5, 5)) + val infinityWrapper = IntervalLattice(Bounded(NEGATIVE_INFINITE, INFINITE)) + + // compare to + assertEquals(-1, bottomWrapper.compareTo(zeroWrapper)) + assertEquals(0, zeroWrapper.compareTo(zeroWrapper)) + assertEquals(1, zeroWrapper.compareTo(bottomWrapper)) + + // contains + assertFalse(bottomWrapper.contains(zeroWrapper)) + assertFalse(zeroWrapper.contains(bottomWrapper)) + assertFalse(zeroWrapper.contains(outerWrapper)) + assertTrue(outerWrapper.contains(zeroWrapper)) + + // widen + assertEquals(outerWrapper, outerWrapper.widen(zeroWrapper)) + assertEquals(infinityWrapper, zeroWrapper.widen(outerWrapper)) + + // narrow + assertEquals(outerWrapper, infinityWrapper.narrow(outerWrapper)) + assertEquals(outerWrapper, outerWrapper.narrow(zeroWrapper)) + assertEquals(outerWrapper, outerWrapper.narrow(infinityWrapper)) + } } From ce029f5a589e006a326a770cb78f34e2d22a996a Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 10:50:12 +0100 Subject: [PATCH 50/58] fix meet test method --- .../abstracteval/LatticeIntervalTest.kt | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt index 7d4102fd1d..f289d41880 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/LatticeIntervalTest.kt @@ -279,20 +279,24 @@ class LatticeIntervalTest { @Test fun testMeet() { // With BOTTOM - assertEquals(BOTTOM, BOTTOM.join(Bounded(5, 5))) - assertEquals(BOTTOM, Bounded(5, 5).join(BOTTOM)) - assertEquals(BOTTOM, BOTTOM.join(Bounded(NEGATIVE_INFINITE, INFINITE))) - assertEquals(BOTTOM, Bounded(NEGATIVE_INFINITE, INFINITE).join(BOTTOM)) - assertEquals(BOTTOM, BOTTOM.join(BOTTOM)) - - // Without BOTTOM - assertEquals(BOTTOM, Bounded(5, 10).join(Bounded(NEGATIVE_INFINITE, -5))) - assertEquals(BOTTOM, Bounded(-10, -5).join(Bounded(5, INFINITE))) + assertEquals(Bounded(5, 5), BOTTOM.meet(Bounded(5, 5))) + assertEquals(Bounded(5, 5), Bounded(5, 5).meet(BOTTOM)) assertEquals( Bounded(NEGATIVE_INFINITE, INFINITE), - Bounded(0, 0).join(Bounded(NEGATIVE_INFINITE, INFINITE)) + BOTTOM.meet(Bounded(NEGATIVE_INFINITE, INFINITE)) ) - assertEquals(Bounded(-10, 10), Bounded(9, 10).join(Bounded(-10, -9))) + assertEquals( + Bounded(NEGATIVE_INFINITE, INFINITE), + Bounded(NEGATIVE_INFINITE, INFINITE).meet(BOTTOM) + ) + assertEquals(BOTTOM, BOTTOM.meet(BOTTOM)) + + // Without BOTTOM + assertEquals(BOTTOM, Bounded(5, 10).meet(Bounded(NEGATIVE_INFINITE, -5))) + assertEquals(BOTTOM, Bounded(-10, -5).meet(Bounded(5, INFINITE))) + assertEquals(BOTTOM, Bounded(9, 10).meet(Bounded(-10, -9))) + assertEquals(Bounded(0, 0), Bounded(0, 0).meet(Bounded(NEGATIVE_INFINITE, INFINITE))) + assertEquals(Bounded(-9, 9), Bounded(-9, 10).meet(Bounded(-10, 9))) } @Test From dfc5b29e5eafb9974cf7d8857c801e5179c8c668 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 11:35:57 +0100 Subject: [PATCH 51/58] change default of plusAssign and minusAssign to lose all information --- .../aisec/cpg/analysis/abstracteval/value/Integer.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt index 07147c43cc..99220c4abf 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt @@ -93,11 +93,11 @@ class Integer : Value { val valueInterval = LatticeInterval.Bounded(value, value) current + valueInterval } - // Per default set upper bound to infinite + // Per default lose all information else { val joinInterval: LatticeInterval = LatticeInterval.Bounded( - LatticeInterval.Bound.INFINITE, + LatticeInterval.Bound.NEGATIVE_INFINITE, LatticeInterval.Bound.INFINITE ) current.join(joinInterval) @@ -112,12 +112,12 @@ class Integer : Value { val valueInterval = LatticeInterval.Bounded(value, value) current - valueInterval } - // Per default set lower bound to negative infinite + // Per default lose all information else { val joinInterval: LatticeInterval = LatticeInterval.Bounded( LatticeInterval.Bound.NEGATIVE_INFINITE, - LatticeInterval.Bound.NEGATIVE_INFINITE + LatticeInterval.Bound.INFINITE ) current.join(joinInterval) } From 613457c0f752a0cce424d8c5dde902eaf83f82c3 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 11:36:15 +0100 Subject: [PATCH 52/58] add tests for the Integer Value --- .../abstracteval/value/IntegerValueTest.kt | 333 ++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt new file mode 100644 index 0000000000..0ff31a472e --- /dev/null +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt @@ -0,0 +1,333 @@ +/* + * 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.cpg.analysis.abstracteval.value + +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.Bound.* +import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference +import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator +import kotlin.test.Test +import kotlin.test.assertEquals + +class IntegerValueTest { + val name = Name("testVariable") + val current = LatticeInterval.Bounded(1, 1) + + @Test + fun applyDeclarationTest() { + val correctDeclaration = run { + val decl = VariableDeclaration() + val init = Literal() + init.value = 5 + decl.name = name + decl.initializer = init + decl + } + assertEquals( + LatticeInterval.Bounded(5, 5), + Integer().applyEffect(current, correctDeclaration, name.localName) + ) + + val wrongNameDeclaration = run { + val decl = VariableDeclaration() + val init = Literal() + init.value = 5 + decl.name = Name("otherVariable") + decl.initializer = init + decl + } + assertEquals( + LatticeInterval.Bounded(1, 1), + Integer().applyEffect(current, wrongNameDeclaration, name.localName) + ) + + val noInitializerDeclaration = run { + val decl = VariableDeclaration() + decl.name = name + decl + } + assertEquals( + LatticeInterval.Bounded(1, 1), + Integer().applyEffect(current, noInitializerDeclaration, name.localName) + ) + } + + @Test + fun applyUnaryOperator() { + val preInc = run { + val op = UnaryOperator() + op.isPrefix = true + op.operatorCode = "++" + op.input.code = name.localName + op + } + assertEquals( + LatticeInterval.Bounded(2, 2), + Integer().applyEffect(current, preInc, name.localName) + ) + + val postInc = run { + val op = UnaryOperator() + op.isPrefix = false + op.operatorCode = "++" + op.input.code = name.localName + op + } + assertEquals( + LatticeInterval.Bounded(2, 2), + Integer().applyEffect(current, postInc, name.localName) + ) + + val preDec = run { + val op = UnaryOperator() + op.isPrefix = true + op.operatorCode = "--" + op.input.code = name.localName + op + } + assertEquals( + LatticeInterval.Bounded(0, 0), + Integer().applyEffect(current, preDec, name.localName) + ) + + val postDec = run { + val op = UnaryOperator() + op.isPrefix = false + op.operatorCode = "--" + op.input.code = name.localName + op + } + assertEquals( + LatticeInterval.Bounded(0, 0), + Integer().applyEffect(current, postDec, name.localName) + ) + + val wrongName = run { + val op = UnaryOperator() + op.isPrefix = false + op.operatorCode = "--" + op.input.code = "otherVariable" + op + } + assertEquals( + LatticeInterval.Bounded(1, 1), + Integer().applyEffect(current, wrongName, name.localName) + ) + + val wrongCode = run { + val op = UnaryOperator() + op.isPrefix = false + op.operatorCode = "+-" + op.input.code = name.localName + op + } + assertEquals( + LatticeInterval.Bounded(1, 1), + Integer().applyEffect(current, wrongCode, name.localName) + ) + } + + @Test + fun applyAssignExpression() { + val assignLiteral = run { + val expr = AssignExpression() + val ref = Reference() + val lit = Literal() + lit.value = 3 + ref.code = name.localName + expr.operatorCode = "=" + expr.lhs.add(0, ref) + expr.rhs.add(0, lit) + expr + } + assertEquals( + LatticeInterval.Bounded(3, 3), + Integer().applyEffect(current, assignLiteral, name.localName) + ) + + val assignFallback = run { + val expr = AssignExpression() + val ref = Reference() + ref.code = name.localName + expr.operatorCode = "=" + expr.lhs.add(0, ref) + expr + } + assertEquals( + LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), + Integer().applyEffect(current, assignFallback, name.localName) + ) + + val assignPlusLiteral = run { + val expr = AssignExpression() + val ref = Reference() + val lit = Literal() + lit.value = 3 + ref.code = name.localName + expr.operatorCode = "+=" + expr.lhs.add(0, ref) + expr.rhs.add(0, lit) + expr + } + assertEquals( + LatticeInterval.Bounded(4, 4), + Integer().applyEffect(current, assignPlusLiteral, name.localName) + ) + + val assignPlusFallback = run { + val expr = AssignExpression() + val ref = Reference() + ref.code = name.localName + expr.operatorCode = "+=" + expr.lhs.add(0, ref) + expr + } + assertEquals( + LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), + Integer().applyEffect(current, assignPlusFallback, name.localName) + ) + + val assignMinusLiteral = run { + val expr = AssignExpression() + val ref = Reference() + val lit = Literal() + lit.value = 3 + ref.code = name.localName + expr.operatorCode = "-=" + expr.lhs.add(0, ref) + expr.rhs.add(0, lit) + expr + } + assertEquals( + LatticeInterval.Bounded(-2, -2), + Integer().applyEffect(current, assignMinusLiteral, name.localName) + ) + + val assignMinusFallback = run { + val expr = AssignExpression() + val ref = Reference() + ref.code = name.localName + expr.operatorCode = "-=" + expr.lhs.add(0, ref) + expr + } + assertEquals( + LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), + Integer().applyEffect(current, assignMinusFallback, name.localName) + ) + + val assignTimesLiteral = run { + val expr = AssignExpression() + val ref = Reference() + val lit = Literal() + lit.value = 3 + ref.code = name.localName + expr.operatorCode = "*=" + expr.lhs.add(0, ref) + expr.rhs.add(0, lit) + expr + } + assertEquals( + LatticeInterval.Bounded(3, 3), + Integer().applyEffect(current, assignTimesLiteral, name.localName) + ) + + val assignTimesFallback = run { + val expr = AssignExpression() + val ref = Reference() + ref.code = name.localName + expr.operatorCode = "*=" + expr.lhs.add(0, ref) + expr + } + assertEquals( + LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), + Integer().applyEffect(current, assignTimesFallback, name.localName) + ) + + val assignDivLiteral = run { + val expr = AssignExpression() + val ref = Reference() + val lit = Literal() + lit.value = 3 + ref.code = name.localName + expr.operatorCode = "/=" + expr.lhs.add(0, ref) + expr.rhs.add(0, lit) + expr + } + assertEquals( + LatticeInterval.Bounded(0, 0), + Integer().applyEffect(current, assignDivLiteral, name.localName) + ) + + val assignDivFallback = run { + val expr = AssignExpression() + val ref = Reference() + ref.code = name.localName + expr.operatorCode = "/=" + expr.lhs.add(0, ref) + expr + } + assertEquals( + LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), + Integer().applyEffect(current, assignDivFallback, name.localName) + ) + + val assignModLiteral = run { + val expr = AssignExpression() + val ref = Reference() + val lit = Literal() + lit.value = 3 + ref.code = name.localName + expr.operatorCode = "%=" + expr.lhs.add(0, ref) + expr.rhs.add(0, lit) + expr + } + assertEquals( + LatticeInterval.Bounded(1, 1), + Integer().applyEffect(current, assignModLiteral, name.localName) + ) + + val assignModFallback = run { + val expr = AssignExpression() + val ref = Reference() + ref.code = name.localName + expr.operatorCode = "%=" + expr.lhs.add(0, ref) + expr + } + assertEquals( + LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), + Integer().applyEffect(current, assignModFallback, name.localName) + ) + } +} From ee316eb17090858f81ab6537ff5d735d5e8840f7 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 11:45:36 +0100 Subject: [PATCH 53/58] check name of array declaration --- .../fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt index 007c3f397b..accdac8456 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt @@ -41,7 +41,9 @@ import org.apache.commons.lang3.NotImplementedException class Array : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { // (Re-)Declaration - if (node is VariableDeclaration && node.initializer != null) { + if ( + node is VariableDeclaration && node.initializer != null && node.name.localName == name + ) { val initValue = getSize(node.initializer!!) return LatticeInterval.Bounded(initValue, initValue) } From 435a6736787ea03aa5f1695c4f5fb33f5f24fe05 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 12:19:21 +0100 Subject: [PATCH 54/58] move code block for function side effects further up --- .../abstracteval/value/MutableList.kt | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt index 565aff5b5d..f77b163a99 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt @@ -46,7 +46,9 @@ import org.apache.commons.lang3.NotImplementedException @Suppress("UNUSED") class MutableList : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { - if (node is VariableDeclaration && node.initializer != null) { + if ( + node is VariableDeclaration && node.initializer != null && node.name.localName == name + ) { when (val init = node.initializer) { is MemberCallExpression -> { val size = init.arguments.size @@ -63,9 +65,36 @@ class MutableList : Value { if (node !is MemberCallExpression) { return current } - // Only consider calls that have the subject as base + // If the call does not have the subject as base, check for subject as argument if ((node.callee as? MemberExpression)?.base?.code != name) { - return current + if (node.arguments.any { it.name.localName == name }) { + // This is a function call that uses the variable as an argument. + // To find side effects we need to create a local evaluator for this function + // and return the value of the renamed variable at the last statement. + // TODO: unfinished: + // this currently does not work if the variable is given for multiple + // parameters! + val function = node.invokes.first() + val argPos = node.arguments.indexOfFirst { it.name.localName == name } + + // We cannot take the "first" as that refers to the Block which has no nextEOG + val functionStart = function.body.statements[1] + // This variable should be a PhysicalLocation but the CPG often just hands us + // "null" + val functionEnd = function.body.statements.last() + val newTargetName = function.parameters[argPos].name.localName + + val localEvaluator = AbstractEvaluator() + return localEvaluator.evaluate( + newTargetName, + functionStart, + functionEnd, + this::class, + IntervalLattice(current) + ) + } else { + return current + } } return when (node.name.localName) { "add" -> { @@ -102,37 +131,7 @@ class MutableList : Value { val zeroInterval = LatticeInterval.Bounded(0, 0) current.join(zeroInterval) } - else -> { - // This includes all functions with side effects - if (node.arguments.any { it.name.localName == name }) { - // This is a function call that uses the variable as an argument. - // To find side effects we need to create a local evaluator for this function - // and return the value of the renamed variable at the last statement. - // TODO: unfinished: - // this currently does not work if the variable is given for multiple - // parameters! - val function = node.invokes.first() - val argPos = node.arguments.indexOfFirst { it.name.localName == name } - - // We cannot take the "first" as that refers to the Block which has no nextEOG - val functionStart = function.body.statements[1] - // This variable should be a PhysicalLocation but the CPG often just hands us - // "null" - val functionEnd = function.body.statements.last() - val newTargetName = function.parameters[argPos].name.localName - - val localEvaluator = AbstractEvaluator() - return localEvaluator.evaluate( - newTargetName, - functionStart, - functionEnd, - this::class, - IntervalLattice(current) - ) - } else { - return current - } - } + else -> current } } } From 103de89720123ea1a0ab470e185a74979968c31b Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 12:19:39 +0100 Subject: [PATCH 55/58] add tests for array and mutable list --- .../abstracteval/value/ArrayValueTest.kt | 80 ++++++++ .../abstracteval/value/IntegerValueTest.kt | 4 +- .../value/MutableListValueTest.kt | 183 ++++++++++++++++++ 3 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValueTest.kt create mode 100644 cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValueTest.kt diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValueTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValueTest.kt new file mode 100644 index 0000000000..8307977706 --- /dev/null +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValueTest.kt @@ -0,0 +1,80 @@ +/* + * 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.cpg.analysis.abstracteval.value + +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval +import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewArrayExpression +import kotlin.test.Test +import kotlin.test.assertEquals + +class ArrayValueTest { + private val name = Name("testVariable") + private val current = LatticeInterval.Bounded(1, 1) + + @Test + fun applyDeclarationTest() { + val correctDeclaration = run { + val decl = VariableDeclaration() + val init = NewArrayExpression() + val lit = Literal() + lit.value = 5 + init.dimensions = mutableListOf(lit) + decl.name = name + decl.initializer = init + decl + } + assertEquals( + LatticeInterval.Bounded(5, 5), + Array().applyEffect(current, correctDeclaration, name.localName) + ) + + val wrongNameDeclaration = run { + val decl = VariableDeclaration() + val init = Literal() + init.value = 5 + decl.name = Name("otherVariable") + decl.initializer = init + decl + } + assertEquals( + LatticeInterval.Bounded(1, 1), + Array().applyEffect(current, wrongNameDeclaration, name.localName) + ) + + val noInitializerDeclaration = run { + val decl = VariableDeclaration() + decl.name = name + decl + } + assertEquals( + LatticeInterval.Bounded(1, 1), + Array().applyEffect(current, noInitializerDeclaration, name.localName) + ) + } +} diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt index 0ff31a472e..be0ccf2b9a 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt @@ -37,8 +37,8 @@ import kotlin.test.Test import kotlin.test.assertEquals class IntegerValueTest { - val name = Name("testVariable") - val current = LatticeInterval.Bounded(1, 1) + private val name = Name("testVariable") + private val current = LatticeInterval.Bounded(1, 1) @Test fun applyDeclarationTest() { diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValueTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValueTest.kt new file mode 100644 index 0000000000..1bf61c0e40 --- /dev/null +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValueTest.kt @@ -0,0 +1,183 @@ +/* + * 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.cpg.analysis.abstracteval.value + +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval +import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval.Bound.* +import de.fraunhofer.aisec.cpg.graph.Name +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.types.IntegerType +import kotlin.test.Test +import kotlin.test.assertEquals + +class MutableListValueTest { + private val name = Name("testVariable") + private val current = LatticeInterval.Bounded(1, 1) + + @Test + fun applyDeclarationTest() { + val correctDeclaration = run { + val decl = VariableDeclaration() + val init = MemberCallExpression() + val lit = Literal() + lit.value = 5 + init.arguments = mutableListOf(lit, lit) + decl.name = name + decl.initializer = init + decl + } + assertEquals( + LatticeInterval.Bounded(2, 2), + MutableList().applyEffect(current, correctDeclaration, name.localName) + ) + + val wrongNameDeclaration = run { + val decl = VariableDeclaration() + val init = Literal() + init.value = 5 + decl.name = Name("otherVariable") + decl.initializer = init + decl + } + assertEquals( + LatticeInterval.Bounded(1, 1), + MutableList().applyEffect(current, wrongNameDeclaration, name.localName) + ) + + val noInitializerDeclaration = run { + val decl = VariableDeclaration() + decl.name = name + decl + } + assertEquals( + LatticeInterval.Bounded(1, 1), + MutableList().applyEffect(current, noInitializerDeclaration, name.localName) + ) + } + + @Test + fun applyDirectCallTest() { + val add = run { + val expr = MemberCallExpression() + val member = MemberExpression() + member.base.code = name.localName + member.name = Name("add") + expr.callee = member + expr + } + assertEquals( + LatticeInterval.Bounded(2, 2), + MutableList().applyEffect(current, add, name.localName) + ) + + val addAll = run { + val expr = MemberCallExpression() + val member = MemberExpression() + member.base.code = name.localName + member.name = Name("addAll") + expr.callee = member + expr + } + assertEquals( + LatticeInterval.Bounded(1, INFINITE), + MutableList().applyEffect(current, addAll, name.localName) + ) + + val clear = run { + val expr = MemberCallExpression() + val member = MemberExpression() + member.base.code = name.localName + member.name = Name("clear") + expr.callee = member + expr + } + assertEquals( + LatticeInterval.Bounded(0, 0), + MutableList().applyEffect(current, clear, name.localName) + ) + + val removeInt = run { + val expr = MemberCallExpression() + val lit = Literal() + val member = MemberExpression() + member.base.code = name.localName + member.name = Name("remove") + lit.type = IntegerType() + expr.callee = member + expr.arguments = mutableListOf(lit) + expr + } + assertEquals( + LatticeInterval.Bounded(0, 0), + MutableList().applyEffect(current, removeInt, name.localName) + ) + + val removeObject = run { + val expr = MemberCallExpression() + val lit = Literal() + val member = MemberExpression() + member.base.code = name.localName + member.name = Name("remove") + expr.callee = member + expr.arguments = mutableListOf(lit) + expr + } + assertEquals( + LatticeInterval.Bounded(0, 1), + MutableList().applyEffect(current, removeObject, name.localName) + ) + + val removeAll = run { + val expr = MemberCallExpression() + val member = MemberExpression() + member.base.code = name.localName + member.name = Name("removeAll") + expr.callee = member + expr + } + assertEquals( + LatticeInterval.Bounded(0, 1), + MutableList().applyEffect(current, removeAll, name.localName) + ) + + val wrongName = run { + val expr = MemberCallExpression() + val member = MemberExpression() + member.name = Name("add") + expr.callee = member + expr + } + assertEquals( + LatticeInterval.Bounded(1, 1), + MutableList().applyEffect(current, wrongName, name.localName) + ) + } + + @Test fun applyIndirectCallTest() {} +} From aa1a467c56c7a12c7612d8c5dde4f1e211c086b1 Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 12:26:33 +0100 Subject: [PATCH 56/58] remove unfinished code block in MutableList --- .../abstracteval/value/MutableList.kt | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt index f77b163a99..338c3a57b1 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt @@ -68,30 +68,10 @@ class MutableList : Value { // If the call does not have the subject as base, check for subject as argument if ((node.callee as? MemberExpression)?.base?.code != name) { if (node.arguments.any { it.name.localName == name }) { + TODO() // This is a function call that uses the variable as an argument. // To find side effects we need to create a local evaluator for this function // and return the value of the renamed variable at the last statement. - // TODO: unfinished: - // this currently does not work if the variable is given for multiple - // parameters! - val function = node.invokes.first() - val argPos = node.arguments.indexOfFirst { it.name.localName == name } - - // We cannot take the "first" as that refers to the Block which has no nextEOG - val functionStart = function.body.statements[1] - // This variable should be a PhysicalLocation but the CPG often just hands us - // "null" - val functionEnd = function.body.statements.last() - val newTargetName = function.parameters[argPos].name.localName - - val localEvaluator = AbstractEvaluator() - return localEvaluator.evaluate( - newTargetName, - functionStart, - functionEnd, - this::class, - IntervalLattice(current) - ) } else { return current } From ebfafd65a0f13314bf802dd5ea59dab8ee286f8f Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 4 Nov 2024 12:26:50 +0100 Subject: [PATCH 57/58] made spotless --- .../aisec/cpg/analysis/abstracteval/value/MutableList.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt index 338c3a57b1..7441d951dc 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt @@ -25,12 +25,9 @@ */ package de.fraunhofer.aisec.cpg.analysis.abstracteval.value -import de.fraunhofer.aisec.cpg.analysis.abstracteval.AbstractEvaluator -import de.fraunhofer.aisec.cpg.analysis.abstracteval.IntervalLattice import de.fraunhofer.aisec.cpg.analysis.abstracteval.LatticeInterval import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression From 6557a397426d81533d05c152e37b6fe29e10110f Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 18 Nov 2024 12:27:47 +0100 Subject: [PATCH 58/58] changed the name of the Value classes to prevent confusion with builtin classes --- .../abstracteval/AbstractEvaluator.kt | 8 ++-- .../value/{Array.kt => ArrayValue.kt} | 2 +- .../value/{Integer.kt => IntegerValue.kt} | 2 +- .../{MutableList.kt => MutableListValue.kt} | 2 +- .../abstracteval/value/ArrayValueTest.kt | 6 +-- .../abstracteval/value/IntegerValueTest.kt | 42 +++++++++---------- .../value/MutableListValueTest.kt | 20 ++++----- 7 files changed, 40 insertions(+), 42 deletions(-) rename cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/{Array.kt => ArrayValue.kt} (99%) rename cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/{Integer.kt => IntegerValue.kt} (99%) rename cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/{MutableList.kt => MutableListValue.kt} (99%) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt index 31323736d6..7d2654dba3 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/AbstractEvaluator.kt @@ -25,9 +25,7 @@ */ package de.fraunhofer.aisec.cpg.analysis.abstracteval -import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Array -import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Integer -import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.Value +import de.fraunhofer.aisec.cpg.analysis.abstracteval.value.* import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.statements.DoStatement import de.fraunhofer.aisec.cpg.graph.statements.ForEachStatement @@ -221,8 +219,8 @@ class AbstractEvaluator { } val name = node.type.name.toString() return when { - name.endsWith("[]") -> Array::class - name == "int" -> Integer::class + name.endsWith("[]") -> ArrayValue::class + name == "int" -> IntegerValue::class else -> throw NotImplementedException() } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValue.kt similarity index 99% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValue.kt index accdac8456..1f8739de95 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Array.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValue.kt @@ -38,7 +38,7 @@ import org.apache.commons.lang3.NotImplementedException * This class implements the [Value] interface for Arrays, tracking the size of the collection. We * assume that there is no operation that changes an array's size apart from re-declaring it. */ -class Array : Value { +class ArrayValue : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { // (Re-)Declaration if ( diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValue.kt similarity index 99% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValue.kt index 99220c4abf..5752615870 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/Integer.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValue.kt @@ -34,7 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator import org.apache.commons.lang3.NotImplementedException /** This class implements the [Value] interface for Integer values. */ -class Integer : Value { +class IntegerValue : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { // (Re-)Declarations of the Variable if ( diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValue.kt similarity index 99% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValue.kt index 7441d951dc..d0a255c5fb 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableList.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValue.kt @@ -41,7 +41,7 @@ import org.apache.commons.lang3.NotImplementedException * the below TODOs and write a test file. */ @Suppress("UNUSED") -class MutableList : Value { +class MutableListValue : Value { override fun applyEffect(current: LatticeInterval, node: Node, name: String): LatticeInterval { if ( node is VariableDeclaration && node.initializer != null && node.name.localName == name diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValueTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValueTest.kt index 8307977706..99b9a34740 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValueTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/ArrayValueTest.kt @@ -51,7 +51,7 @@ class ArrayValueTest { } assertEquals( LatticeInterval.Bounded(5, 5), - Array().applyEffect(current, correctDeclaration, name.localName) + ArrayValue().applyEffect(current, correctDeclaration, name.localName) ) val wrongNameDeclaration = run { @@ -64,7 +64,7 @@ class ArrayValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - Array().applyEffect(current, wrongNameDeclaration, name.localName) + ArrayValue().applyEffect(current, wrongNameDeclaration, name.localName) ) val noInitializerDeclaration = run { @@ -74,7 +74,7 @@ class ArrayValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - Array().applyEffect(current, noInitializerDeclaration, name.localName) + ArrayValue().applyEffect(current, noInitializerDeclaration, name.localName) ) } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt index be0ccf2b9a..1e61596486 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/IntegerValueTest.kt @@ -52,7 +52,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(5, 5), - Integer().applyEffect(current, correctDeclaration, name.localName) + IntegerValue().applyEffect(current, correctDeclaration, name.localName) ) val wrongNameDeclaration = run { @@ -65,7 +65,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - Integer().applyEffect(current, wrongNameDeclaration, name.localName) + IntegerValue().applyEffect(current, wrongNameDeclaration, name.localName) ) val noInitializerDeclaration = run { @@ -75,7 +75,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - Integer().applyEffect(current, noInitializerDeclaration, name.localName) + IntegerValue().applyEffect(current, noInitializerDeclaration, name.localName) ) } @@ -90,7 +90,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(2, 2), - Integer().applyEffect(current, preInc, name.localName) + IntegerValue().applyEffect(current, preInc, name.localName) ) val postInc = run { @@ -102,7 +102,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(2, 2), - Integer().applyEffect(current, postInc, name.localName) + IntegerValue().applyEffect(current, postInc, name.localName) ) val preDec = run { @@ -114,7 +114,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(0, 0), - Integer().applyEffect(current, preDec, name.localName) + IntegerValue().applyEffect(current, preDec, name.localName) ) val postDec = run { @@ -126,7 +126,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(0, 0), - Integer().applyEffect(current, postDec, name.localName) + IntegerValue().applyEffect(current, postDec, name.localName) ) val wrongName = run { @@ -138,7 +138,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - Integer().applyEffect(current, wrongName, name.localName) + IntegerValue().applyEffect(current, wrongName, name.localName) ) val wrongCode = run { @@ -150,7 +150,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - Integer().applyEffect(current, wrongCode, name.localName) + IntegerValue().applyEffect(current, wrongCode, name.localName) ) } @@ -169,7 +169,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(3, 3), - Integer().applyEffect(current, assignLiteral, name.localName) + IntegerValue().applyEffect(current, assignLiteral, name.localName) ) val assignFallback = run { @@ -182,7 +182,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), - Integer().applyEffect(current, assignFallback, name.localName) + IntegerValue().applyEffect(current, assignFallback, name.localName) ) val assignPlusLiteral = run { @@ -198,7 +198,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(4, 4), - Integer().applyEffect(current, assignPlusLiteral, name.localName) + IntegerValue().applyEffect(current, assignPlusLiteral, name.localName) ) val assignPlusFallback = run { @@ -211,7 +211,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), - Integer().applyEffect(current, assignPlusFallback, name.localName) + IntegerValue().applyEffect(current, assignPlusFallback, name.localName) ) val assignMinusLiteral = run { @@ -227,7 +227,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(-2, -2), - Integer().applyEffect(current, assignMinusLiteral, name.localName) + IntegerValue().applyEffect(current, assignMinusLiteral, name.localName) ) val assignMinusFallback = run { @@ -240,7 +240,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), - Integer().applyEffect(current, assignMinusFallback, name.localName) + IntegerValue().applyEffect(current, assignMinusFallback, name.localName) ) val assignTimesLiteral = run { @@ -256,7 +256,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(3, 3), - Integer().applyEffect(current, assignTimesLiteral, name.localName) + IntegerValue().applyEffect(current, assignTimesLiteral, name.localName) ) val assignTimesFallback = run { @@ -269,7 +269,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), - Integer().applyEffect(current, assignTimesFallback, name.localName) + IntegerValue().applyEffect(current, assignTimesFallback, name.localName) ) val assignDivLiteral = run { @@ -285,7 +285,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(0, 0), - Integer().applyEffect(current, assignDivLiteral, name.localName) + IntegerValue().applyEffect(current, assignDivLiteral, name.localName) ) val assignDivFallback = run { @@ -298,7 +298,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), - Integer().applyEffect(current, assignDivFallback, name.localName) + IntegerValue().applyEffect(current, assignDivFallback, name.localName) ) val assignModLiteral = run { @@ -314,7 +314,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - Integer().applyEffect(current, assignModLiteral, name.localName) + IntegerValue().applyEffect(current, assignModLiteral, name.localName) ) val assignModFallback = run { @@ -327,7 +327,7 @@ class IntegerValueTest { } assertEquals( LatticeInterval.Bounded(NEGATIVE_INFINITE, INFINITE), - Integer().applyEffect(current, assignModFallback, name.localName) + IntegerValue().applyEffect(current, assignModFallback, name.localName) ) } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValueTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValueTest.kt index 1bf61c0e40..8b2179a707 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValueTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/abstracteval/value/MutableListValueTest.kt @@ -54,7 +54,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(2, 2), - MutableList().applyEffect(current, correctDeclaration, name.localName) + MutableListValue().applyEffect(current, correctDeclaration, name.localName) ) val wrongNameDeclaration = run { @@ -67,7 +67,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - MutableList().applyEffect(current, wrongNameDeclaration, name.localName) + MutableListValue().applyEffect(current, wrongNameDeclaration, name.localName) ) val noInitializerDeclaration = run { @@ -77,7 +77,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - MutableList().applyEffect(current, noInitializerDeclaration, name.localName) + MutableListValue().applyEffect(current, noInitializerDeclaration, name.localName) ) } @@ -93,7 +93,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(2, 2), - MutableList().applyEffect(current, add, name.localName) + MutableListValue().applyEffect(current, add, name.localName) ) val addAll = run { @@ -106,7 +106,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(1, INFINITE), - MutableList().applyEffect(current, addAll, name.localName) + MutableListValue().applyEffect(current, addAll, name.localName) ) val clear = run { @@ -119,7 +119,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(0, 0), - MutableList().applyEffect(current, clear, name.localName) + MutableListValue().applyEffect(current, clear, name.localName) ) val removeInt = run { @@ -135,7 +135,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(0, 0), - MutableList().applyEffect(current, removeInt, name.localName) + MutableListValue().applyEffect(current, removeInt, name.localName) ) val removeObject = run { @@ -150,7 +150,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(0, 1), - MutableList().applyEffect(current, removeObject, name.localName) + MutableListValue().applyEffect(current, removeObject, name.localName) ) val removeAll = run { @@ -163,7 +163,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(0, 1), - MutableList().applyEffect(current, removeAll, name.localName) + MutableListValue().applyEffect(current, removeAll, name.localName) ) val wrongName = run { @@ -175,7 +175,7 @@ class MutableListValueTest { } assertEquals( LatticeInterval.Bounded(1, 1), - MutableList().applyEffect(current, wrongName, name.localName) + MutableListValue().applyEffect(current, wrongName, name.localName) ) }