From f5987a47efed0e30158732226585e1534cc62578 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 20 Apr 2022 13:02:51 +0200 Subject: [PATCH 01/67] First attempt to implement bug reasoning logic --- .../aisec/cpg/analysis/AnalysisTest.kt | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index e5411859e8..db36cd6e4f 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -27,13 +27,16 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager +import de.fraunhofer.aisec.cpg.analysis.QueryEvaluation.Quantifier import de.fraunhofer.aisec.cpg.console.fancyCode +import de.fraunhofer.aisec.cpg.graph.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.body import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import java.io.File +import kotlin.test.assertFalse import kotlin.test.assertNotNull import org.junit.jupiter.api.Test @@ -53,6 +56,93 @@ class AnalysisTest { OutOfBoundsCheck().run(result) } + @Test + fun testOutOfBoundsQuery() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/array.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + // Query: forall (n: ArraySubscriptionExpression): |max(n.subscriptExpression)| < + // |min(n.arrayExpression.refersTo.initializer.dimensions[0])| + // && |min(n.subscriptExpression)| >= 0 + val nodesN = + QueryEvaluation.NodesExpression( + "(n: ArraySubscriptionExpression)", + "n", + "ArraySubscriptionExpression", + result + ) + val index = + QueryEvaluation.FieldAccessExpr( + "n.subscriptExpression", + "n", + "subscriptExpression", + ValueEvaluator() + ) + val maxIndex = + QueryEvaluation.UnaryExpr( + "|max(n.subscriptExpression)|", + index, + QueryEvaluation.QueryOp.MAX + ) + val minIndex = + QueryEvaluation.UnaryExpr( + "|min(n.subscriptExpression)|", + index, + QueryEvaluation.QueryOp.MIN + ) + val capacity = + QueryEvaluation.FieldAccessExpr( + "n.arrayExpression.refersTo.initializer.dimensions[0]", + "n", + "arrayExpression.refersTo.initializer.dimensions[0]", + ValueEvaluator() + ) + val minCapacity = + QueryEvaluation.UnaryExpr( + "|min(n.arrayExpression.refersTo.initializer.dimensions[0])|", + capacity, + QueryEvaluation.QueryOp.MIN + ) + val maxUp = + QueryEvaluation.BinaryExpr( + "|max(n.subscriptExpression)| < |min(n.arrayExpression.refersTo.initializer.dimensions[0])|", + maxIndex, + minCapacity, + QueryEvaluation.QueryOp.LT + ) + val min0 = + QueryEvaluation.BinaryExpr( + "|min(n.subscriptExpression)| >= 0", + minIndex, + QueryEvaluation.ConstExpr("0", 0), + QueryEvaluation.QueryOp.GE + ) + val checks = + QueryEvaluation.BinaryExpr( + "|max(n.subscriptExpression)| < |min(n.arrayExpression.refersTo.initializer.dimensions[0])| && |min(n.subscriptExpression)| >= 0", + maxUp, + min0, + QueryEvaluation.QueryOp.AND + ) + val forall = + QueryEvaluation.QuantifierExpr( + "forall (n: ArraySubscriptionExpression): |max(n.subscriptExpression)| < |min(n.arrayExpression.refersTo.initializer.dimensions[0])| && |min(n.subscriptExpression)| >= 0", + Quantifier.FORALL, + nodesN, + "n", + checks + ) + + assertFalse(forall.evaluate() as Boolean) + } + @Test fun testNullPointer() { val config = From 07fcbf32c03c18b1d17f31e7fc14b1aba1e54b2e Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 20 Apr 2022 14:13:33 +0200 Subject: [PATCH 02/67] Add file --- .../aisec/cpg/analysis/QueryEvaluation.kt | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt new file mode 100644 index 0000000000..b283a20ab9 --- /dev/null +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2022, 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.TranslationResult +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.ValueEvaluator +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import kotlin.reflect.KProperty1 +import kotlin.reflect.jvm.isAccessible + +class QueryEvaluation { + + enum class QueryOp { + GT, + GE, + LT, + LE, + EQ, + NE, + IMPLIES, + MAX, + MIN, + NOT, + AND, + OR, + IS + } + + enum class Quantifier { + FORALL, + EXISTS + } + + abstract class NumberSet { + abstract fun min(): Long + abstract fun max(): Long + abstract fun addValue(value: Long) + abstract fun clear() + } + + class Interval : NumberSet() { + private var min: Long = Long.MAX_VALUE + private var max: Long = Long.MIN_VALUE + + override fun addValue(value: Long) { + if (value < min) { + min = value + } + if (value > max) { + max = value + } + } + override fun min(): Long { + return min + } + override fun max(): Long { + return max + } + override fun clear() { + min = Long.MAX_VALUE + max = Long.MIN_VALUE + } + } + + class ConcreteNumberSet : NumberSet() { + private var values: MutableSet = mutableSetOf() + + override fun addValue(value: Long) { + values.add(value) + } + override fun min(): Long { + return values.minOrNull()!! + } + override fun max(): Long { + return values.maxOrNull()!! + } + override fun clear() { + values.clear() + } + } + + abstract class QueryExpression(open val representation: String) { + abstract fun evaluate(input: Map = mutableMapOf()): Any + } + + class NodesExpression( + override val representation: String, + val name: String, + private val nodeType: String, + private val result: TranslationResult + ) : QueryExpression(representation) { + override fun evaluate(input: Map): Any { + return SubgraphWalker.flattenAST(result).filter { n -> + n.javaClass.simpleName == nodeType + } + } + } + + class QuantifierExpr( + override val representation: String, + private val quantifier: Quantifier, + private val variables: QueryExpression, + private val variableName: String, + private val inner: QueryExpression + ) : QueryExpression(representation) { + override fun evaluate(input: Map): Any { + val newInput = input.toMutableMap() + return if (quantifier == Quantifier.FORALL) { + (variables.evaluate(input) as Collection).all { v -> + newInput[variableName] = v + inner.evaluate(newInput) as Boolean + } + } else if (quantifier == Quantifier.EXISTS) { + (variables.evaluate(input) as Collection).any { v -> + newInput[variableName] = v + inner.evaluate(newInput) as Boolean + } + } else { + false + } + } + } + + class FieldAccessExpr( + override val representation: String, + private val variableName: String, + private val fieldSpecifier: String, + private val evaluator: ValueEvaluator + ) : QueryExpression(representation) { + override fun evaluate(input: Map): Any { + var currentField: Any = input[variableName]!! + for (fs in fieldSpecifier.split(".")) { + val arrayIndex = + if ("[" !in fs) { + -1 + } else { + fs.split("[")[1].dropLast(1).toInt() + } + val fieldName = if (arrayIndex > -1) fs.split("[")[0] else fs + currentField = readInstanceProperty(currentField, fieldName) + if (arrayIndex != -1 && currentField is Array<*>) { + currentField = currentField[arrayIndex]!! + } else if (arrayIndex != -1 && currentField is List<*>) { + currentField = currentField[arrayIndex]!! + // Ugly hack to get the property where the edge points to + currentField = readInstanceProperty(currentField, "end") + } + } + return evaluator.evaluate(currentField as Node)!! + } + + private fun readInstanceProperty(instance: Any, propertyName: String): Any { + val property = + instance::class.members.first { it.name == propertyName } as KProperty1 + return property.apply { isAccessible = true }.get(instance)!! + } + } + + class ConstExpr(override val representation: String, private val value: Any) : + QueryExpression(representation) { + override fun evaluate(input: Map): Any { + return value + } + } + + class UnaryExpr( + override val representation: String, + private val inner: QueryExpression, + private val operator: QueryOp + ) : QueryExpression(representation) { + override fun evaluate(input: Map): Any { + return when (operator) { + QueryOp.NOT -> !(inner.evaluate(input) as Boolean) + QueryOp.MAX -> { + val result = inner.evaluate(input) + if (result is Number) { + result.toLong() + } else { + (result as NumberSet).max() + } + } + QueryOp.MIN -> { + val result = inner.evaluate(input) + if (result is Number) { + result.toLong() + } else { + (result as NumberSet).min() + } + } + else -> throw Exception("Unknown operation $operator on expression $inner") + } + } + } + + class BinaryExpr( + override val representation: String, + private val lhs: QueryExpression, + private val rhs: QueryExpression, + private val operator: QueryOp + ) : QueryExpression(representation) { + override fun evaluate(input: Map): Boolean { + return when (operator) { + QueryOp.AND -> lhs.evaluate(input) as Boolean && rhs.evaluate(input) as Boolean + QueryOp.OR -> lhs.evaluate(input) as Boolean || rhs.evaluate(input) as Boolean + QueryOp.EQ -> lhs.evaluate(input) == rhs.evaluate(input) + QueryOp.NE -> lhs.evaluate(input) != rhs.evaluate(input) + QueryOp.GT -> lhs.evaluate(input) as Long > rhs.evaluate(input) as Long + QueryOp.GE -> lhs.evaluate(input) as Long >= rhs.evaluate(input) as Long + QueryOp.LT -> (lhs.evaluate(input) as Long) < (rhs.evaluate(input) as Long) + QueryOp.LE -> lhs.evaluate(input) as Long <= rhs.evaluate(input) as Long + QueryOp.IS -> + lhs.evaluate(input).javaClass.simpleName == rhs.evaluate(input) as String + QueryOp.IMPLIES -> + !(lhs.evaluate(input) as Boolean) || rhs.evaluate(input) as Boolean + else -> false + } + } + } +} From c92687a7fbea805c78b25048f27a3d0d6bf1b1e7 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 21 Apr 2022 16:36:38 +0200 Subject: [PATCH 03/67] Naive extension to retrieve more than a single value (in some cases) --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 335 ++++++++++++++++++ .../aisec/cpg/analysis/NumberSet.kt | 72 ++++ .../aisec/cpg/analysis/ValueEvaluator.kt | 114 +++--- .../aisec/cpg/analysis/ValueEvaluatorTest.kt | 30 +- .../aisec/cpg/analysis/QueryEvaluation.kt | 61 +--- .../aisec/cpg/analysis/AnalysisTest.kt | 75 +++- cpg-console/src/test/resources/array2.cpp | 8 + .../src/test/resources/array_correct.cpp | 8 + 8 files changed, 580 insertions(+), 123 deletions(-) create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt create mode 100644 cpg-console/src/test/resources/array2.cpp create mode 100644 cpg-console/src/test/resources/array_correct.cpp diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt new file mode 100644 index 0000000000..69cb40671b --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2022, 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.graph.Node +import de.fraunhofer.aisec.cpg.graph.ValueEvaluator +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.negate +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.passes.astParent +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class MultiValueEvaluator : ValueEvaluator() { + companion object { + const val MAX_DEPTH: Int = 10 + } + + override val log: Logger + get() = LoggerFactory.getLogger(MultiValueEvaluator::class.java) + + override fun evaluate(node: Node?): Any? { + val result = evaluateInternal(node, 0) + return if (result is List<*> && result.all { r -> r is Number? }) + ConcreteNumberSet( + result.filterNotNull().map { r -> (r as Number).toLong() }.toMutableSet() + ) + else result + } + + /** Tries to evaluate this node. Anything can happen. */ + override fun evaluateInternal(node: Node?, depth: Int): Any? { + if (depth > MAX_DEPTH) { + return cannotEvaluate(node, this) + } + // Add the expression to the current path + node?.let { this.path += it } + + when (node) { + is ArrayCreationExpression -> return evaluateInternal(node.initializer, depth + 1) + is VariableDeclaration -> return evaluateInternal(node.initializer, depth + 1) + // For a literal, we can just take its value, and we are finished + is Literal<*> -> return node.value + is DeclaredReferenceExpression -> return handleDeclaredReferenceExpression(node, depth) + is UnaryOperator -> return handleUnaryOp(node, depth) + is BinaryOperator -> return handleBinaryOperator(node, depth) + // Casts are just a wrapper in this case, we are interested in the inner expression + is CastExpression -> return this.evaluateInternal(node.expression, depth + 1) + is ArraySubscriptionExpression -> handleArraySubscriptionExpression(node, depth) + // While we are not handling different paths of variables with If statements, we can + // easily be partly path-sensitive in a conditional expression + is ConditionalExpression -> handleConditionalExpression(node, depth) + } + + // At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe + // this helps + return cannotEvaluate(node, this) + } + + /** + * We are handling some basic arithmetic binary operations and string operations that are more + * or less language-independent. + */ + override fun handleBinaryOperator(expr: BinaryOperator, depth: Int): Any? { + // Resolve lhs + val lhsValue = evaluateInternal(expr.lhs, depth + 1) + // Resolve rhs + val rhsValue = evaluateInternal(expr.rhs, depth + 1) + + if (lhsValue !is List<*> && rhsValue !is List<*>) { + return computeBinaryOpEffect(lhsValue, rhsValue, expr) + } + + val result = mutableListOf() + if (lhsValue is List<*>) { + for (lhs in lhsValue) { + if (rhsValue is List<*>) { + result.addAll(rhsValue.map { r -> computeBinaryOpEffect(lhs, r, expr) }) + } else { + result.add(computeBinaryOpEffect(lhs, rhsValue, expr)) + } + } + } else { + result.addAll( + (rhsValue as List<*>).map { r -> computeBinaryOpEffect(lhsValue, r, expr) } + ) + } + + return result + } + + override fun handleConditionalExpression(expr: ConditionalExpression, depth: Int): Any? { + // Assume that condition is a binary operator + if (expr.condition is BinaryOperator) { + val lhs = evaluateInternal((expr.condition as? BinaryOperator)?.lhs, depth + 1) + val rhs = evaluateInternal((expr.condition as? BinaryOperator)?.rhs, depth + 1) + + return if (lhs is List<*> && lhs.size > 1 && rhs is List<*> && rhs.size > 1) { + val result = mutableListOf() + val elseResult = evaluateInternal(expr.elseExpr, depth + 1) + if (elseResult is List<*>) result.addAll(elseResult) else result.add(elseResult) + if (lhs.any { l -> l in rhs }) { + val thenResult = evaluateInternal(expr.thenExpr, depth + 1) + if (thenResult is List<*>) result.addAll(thenResult) else result.add(thenResult) + } + result + } else if (lhs is List<*> && rhs is List<*> && lhs.firstOrNull() == rhs.firstOrNull() || + lhs is List<*> && lhs.firstOrNull() == rhs || + rhs is List<*> && rhs.firstOrNull() == lhs || + lhs == rhs + ) { + evaluateInternal(expr.thenExpr, depth + 1) + } else { + evaluateInternal(expr.elseExpr, depth + 1) + } + } + + return cannotEvaluate(expr, this) + } + + override fun handleUnaryOp(expr: UnaryOperator, depth: Int): Any? { + return when (expr.operatorCode) { + "-" -> { + when (val input = evaluateInternal(expr.input, depth + 1)) { + is List<*> -> input.map { n -> (n as? Number)?.negate() } + is Number -> input.negate() + else -> cannotEvaluate(expr, this) + } + } + "++" -> { + if (expr.astParent is ForStatement) { + evaluateInternal(expr.input, depth + 1) + } else { + when (val input = evaluateInternal(expr.input, depth + 1)) { + is Number -> input.toLong() + 1 + is List<*> -> input.map { n -> (n as? Number)?.toLong()?.plus(1) } + else -> cannotEvaluate(expr, this) + } + } + } + "*" -> evaluateInternal(expr.input, depth + 1) + "&" -> evaluateInternal(expr.input, depth + 1) + else -> cannotEvaluate(expr, this) + } + } + + /** + * Tries to compute the value of a reference. It therefore checks the incoming data flow edges. + * + * In contrast to the implementation of [ValueEvaluator], this one can handle more than one + * value. + */ + override fun handleDeclaredReferenceExpression( + expr: DeclaredReferenceExpression, + depth: Int + ): List { + // For a reference, we are interested in its last assignment into the reference + // denoted by the previous DFG edge + val prevDFG = expr.prevDFG + + if (prevDFG.size == 1) { + // There's only one incoming DFG edge, so we follow this one. + return mutableListOf(evaluateInternal(prevDFG.first(), depth + 1)) + } + + // We are only interested in expressions + val expressions = prevDFG.filterIsInstance() + + if (expressions.size == 2 && + expressions.all { e -> + (e.astParent?.astParent as? ForStatement)?.initializerStatement == e || + (e.astParent as? ForStatement)?.iterationStatement == e + } + ) { + return handleSimpleLoopVariable(expr, depth) + } + + val result = mutableListOf() + if (expressions.isEmpty()) { + // No previous expression?? Let's try with a variable declaration and its initialization + val decl = prevDFG.filterIsInstance() + for (declaration in decl) { + val res = evaluateInternal(declaration, depth + 1) + if (res is Collection<*>) { + result.addAll(res) + } else { + result.add(res) + } + } + } + + for (expression in expressions) { + val res = evaluateInternal(expression, depth + 1) + if (res is Collection<*>) { + result.addAll(res) + } else { + result.add(res) + } + } + return result + } + + private fun handleSimpleLoopVariable( + expr: DeclaredReferenceExpression, + depth: Int + ): List { + val loop = + expr.prevDFG.firstOrNull { e -> e.astParent is ForStatement }?.astParent as? + ForStatement + if (loop == null || loop.condition !is BinaryOperator) return listOf() + + var loopVar = + evaluateInternal(loop.initializerStatement.declarations.first(), depth) as? Number + if (loopVar == null) return listOf() + val cond = loop.condition as BinaryOperator + val result = mutableListOf() + var lhs = + if ((cond.lhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) { + loopVar + } else { + evaluateInternal(cond.lhs, depth + 1) + } + var rhs = + if ((cond.rhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) { + loopVar + } else { + evaluateInternal(cond.rhs, depth + 1) + } + + var comparisonResult = computeBinaryOpEffect(lhs, rhs, cond) + while (comparisonResult == true) { + result.add( + loopVar + ) // We skip the last iteration on purpose because that last operation will be added by + // the statement which made us end up here. + + val loopOp = loop.iterationStatement + loopVar = + when (loopOp) { + is BinaryOperator -> { + val opLhs = + if ((loopOp.lhs as? DeclaredReferenceExpression)?.refersTo == + expr.refersTo + ) { + loopVar + } else { + loopOp.lhs + } + val opRhs = + if ((loopOp.rhs as? DeclaredReferenceExpression)?.refersTo == + expr.refersTo + ) { + loopVar + } else { + loopOp.rhs + } + computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number + } + is UnaryOperator -> { + computeUnaryOpEffect( + if ((loopOp.input as? DeclaredReferenceExpression)?.refersTo == + expr.refersTo + ) { + loopVar!! + } else { + loopOp.input + }, + loopOp + ) as? + Number + } + else -> { + null + } + } + if (loopVar == null) { + return result + } + // result.add(loopVar) + + if ((cond.lhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) { + lhs = loopVar + } + if ((cond.rhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo) { + rhs = loopVar + } + comparisonResult = computeBinaryOpEffect(lhs, rhs, cond) + } + return result + } + + private fun computeUnaryOpEffect(input: Any, expr: UnaryOperator): Any? { + return when (expr.operatorCode) { + "-" -> { + when (input) { + is List<*> -> input.map { n -> (n as? Number)?.negate() } + is Number -> input.negate() + else -> cannotEvaluate(expr, this) + } + } + "++" -> { + when (input) { + is Number -> input.toLong() + 1 + is List<*> -> input.map { n -> (n as? Number)?.toLong()?.plus(1) } + else -> cannotEvaluate(expr, this) + } + } + else -> cannotEvaluate(expr, this) + } + } +} diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt new file mode 100644 index 0000000000..83ad067909 --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 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 + +abstract class NumberSet { + abstract fun min(): Long + abstract fun max(): Long + abstract fun addValue(value: Long) + abstract fun clear() +} + +class Interval : NumberSet() { + private var min: Long = Long.MAX_VALUE + private var max: Long = Long.MIN_VALUE + + override fun addValue(value: Long) { + if (value < min) { + min = value + } + if (value > max) { + max = value + } + } + override fun min(): Long { + return min + } + override fun max(): Long { + return max + } + override fun clear() { + min = Long.MAX_VALUE + max = Long.MIN_VALUE + } +} + +class ConcreteNumberSet(private var values: MutableSet = mutableSetOf()) : NumberSet() { + override fun addValue(value: Long) { + values.add(value) + } + override fun min(): Long { + return values.minOrNull()!! + } + override fun max(): Long { + return values.maxOrNull()!! + } + override fun clear() { + values.clear() + } +} diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 9e3a58150a..050a0d1249 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -39,15 +39,15 @@ class CouldNotResolve * * The result can be retrieved in two ways: * * The result of the [resolve] function is a JVM object which represents the constant value - * * Furthermore, after the execution of [evaluate], the latest evaluation path can be retrieved in - * the [path] property of the evaluator. + * * Furthermore, after the execution of [evaluateInternal], the latest evaluation path can be + * retrieved in the [path] property of the evaluator. * * It contains some advanced mechanics such as resolution of values of arrays, if they contain * literal values. Furthermore, its behaviour can be adjusted by implementing the [cannotEvaluate] * function, which is called when the default behaviour would not be able to resolve the value. This * way, language specific features such as string formatting can be modelled. */ -class ValueEvaluator( +open class ValueEvaluator( /** * Contains a reference to a function that gets called if the value cannot be resolved by the * standard behaviour. @@ -61,31 +61,35 @@ class ValueEvaluator( } } ) { - private val log: Logger + protected open val log: Logger get() = LoggerFactory.getLogger(ValueEvaluator::class.java) - /** This property contains the path of the latest execution of [evaluate]. */ + /** This property contains the path of the latest execution of [evaluateInternal]. */ val path: MutableList = mutableListOf() + open fun evaluate(node: Node?): Any? { + return evaluateInternal(node, 0) + } + /** Tries to evaluate this node. Anything can happen. */ - fun evaluate(node: Node?): Any? { + protected open fun evaluateInternal(node: Node?, depth: Int): Any? { // Add the expression to the current path node?.let { this.path += it } when (node) { - is ArrayCreationExpression -> return evaluate(node.initializer) - is VariableDeclaration -> return evaluate(node.initializer) + is ArrayCreationExpression -> return evaluateInternal(node.initializer, depth + 1) + is VariableDeclaration -> return evaluateInternal(node.initializer, depth + 1) // For a literal, we can just take its value, and we are finished is Literal<*> -> return node.value - is DeclaredReferenceExpression -> return handleDeclaredReferenceExpression(node) - is UnaryOperator -> return handleUnaryOp(node) - is BinaryOperator -> return handleBinaryOperator(node) + is DeclaredReferenceExpression -> return handleDeclaredReferenceExpression(node, depth) + is UnaryOperator -> return handleUnaryOp(node, depth) + is BinaryOperator -> return handleBinaryOperator(node, depth) // Casts are just a wrapper in this case, we are interested in the inner expression - is CastExpression -> return this.evaluate(node.expression) - is ArraySubscriptionExpression -> handleArraySubscriptionExpression(node) + is CastExpression -> return this.evaluateInternal(node.expression, depth + 1) + is ArraySubscriptionExpression -> handleArraySubscriptionExpression(node, depth) // While we are not handling different paths of variables with If statements, we can // easily be partly path-sensitive in a conditional expression - is ConditionalExpression -> handleConditionalExpression(node) + is ConditionalExpression -> handleConditionalExpression(node, depth) } // At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe @@ -97,12 +101,20 @@ class ValueEvaluator( * We are handling some basic arithmetic binary operations and string operations that are more * or less language-independent. */ - private fun handleBinaryOperator(expr: BinaryOperator): Any? { + protected open fun handleBinaryOperator(expr: BinaryOperator, depth: Int): Any? { // Resolve lhs - val lhsValue = evaluate(expr.lhs) + val lhsValue = evaluateInternal(expr.lhs, depth + 1) // Resolve rhs - val rhsValue = evaluate(expr.rhs) + val rhsValue = evaluateInternal(expr.rhs, depth + 1) + return computeBinaryOpEffect(lhsValue, rhsValue, expr) + } + + protected fun computeBinaryOpEffect( + lhsValue: Any?, + rhsValue: Any?, + expr: BinaryOperator + ): Any? { return when (expr.operatorCode) { "+" -> handlePlus(lhsValue, rhsValue, expr) "-" -> handleMinus(lhsValue, rhsValue, expr) @@ -242,21 +254,22 @@ class ValueEvaluator( * We handle some basic unary operators. These also affect pointers and dereferences for * languages that support them. */ - private fun handleUnaryOp(expr: UnaryOperator): Any? { + protected open fun handleUnaryOp(expr: UnaryOperator, depth: Int): Any? { return when (expr.operatorCode) { "-" -> { - when (val input = evaluate(expr.input)) { - is Int -> -input - is Long -> -input - is Short -> -input - is Byte -> -input - is Double -> -input - is Float -> -input + when (val input = evaluateInternal(expr.input, depth + 1)) { + is Number -> input.negate() + else -> cannotEvaluate(expr, this) + } + } + "++" -> { + when (val input = evaluateInternal(expr.input, depth + 1)) { + is Number -> input.toLong() + 1 else -> cannotEvaluate(expr, this) } } - "*" -> evaluate(expr.input) - "&" -> evaluate(expr.input) + "*" -> evaluateInternal(expr.input, depth + 1) + "&" -> evaluateInternal(expr.input, depth + 1) else -> cannotEvaluate(expr, this) } } @@ -266,20 +279,24 @@ class ValueEvaluator( * basically the case if the base of the subscript expression is a list of [KeyValueExpression] * s. */ - private fun handleArraySubscriptionExpression(expr: ArraySubscriptionExpression): Any? { + protected fun handleArraySubscriptionExpression( + expr: ArraySubscriptionExpression, + depth: Int + ): Any? { val array = (expr.arrayExpression as? DeclaredReferenceExpression)?.refersTo as? VariableDeclaration val ile = array?.initializer as? InitializerListExpression ile?.let { - return evaluate( + return evaluateInternal( it.initializers .filterIsInstance(KeyValueExpression::class.java) .firstOrNull { kve -> (kve.key as? Literal<*>)?.value == (expr.subscriptExpression as? Literal<*>)?.value } - ?.value + ?.value, + depth + 1 ) } if (array?.initializer is Literal<*>) { @@ -287,22 +304,22 @@ class ValueEvaluator( } if (expr.arrayExpression is ArraySubscriptionExpression) { - return evaluate(expr.arrayExpression) + return evaluateInternal(expr.arrayExpression, depth + 1) } return cannotEvaluate(expr, this) } - private fun handleConditionalExpression(expr: ConditionalExpression): Any? { + protected open fun handleConditionalExpression(expr: ConditionalExpression, depth: Int): Any? { // Assume that condition is a binary operator if (expr.condition is BinaryOperator) { - val lhs = evaluate((expr.condition as? BinaryOperator)?.lhs) - val rhs = evaluate((expr.condition as? BinaryOperator)?.rhs) + val lhs = evaluateInternal((expr.condition as? BinaryOperator)?.lhs, depth) + val rhs = evaluateInternal((expr.condition as? BinaryOperator)?.rhs, depth) return if (lhs == rhs) { - evaluate(expr.thenExpr) + evaluateInternal(expr.thenExpr, depth + 1) } else { - evaluate(expr.elseExpr) + evaluateInternal(expr.elseExpr, depth + 1) } } @@ -313,14 +330,17 @@ class ValueEvaluator( * Tries to compute the constant value of a reference. It therefore checks the incoming data * flow edges. */ - private fun handleDeclaredReferenceExpression(expr: DeclaredReferenceExpression): Any? { + protected open fun handleDeclaredReferenceExpression( + expr: DeclaredReferenceExpression, + depth: Int + ): Any? { // For a reference, we are interested into its last assignment into the reference // denoted by the previous DFG edge val prevDFG = expr.prevDFG if (prevDFG.size == 1) // There's only one incoming DFG edge, so we follow this one. - return evaluate(prevDFG.first()) + return evaluateInternal(prevDFG.first(), depth + 1) // We are only interested in expressions val expressions = prevDFG.filterIsInstance() @@ -345,10 +365,22 @@ class ValueEvaluator( ) return cannotEvaluate(expr, this) } - return evaluate(decl.firstOrNull()) + return evaluateInternal(decl.firstOrNull(), depth + 1) } - return evaluate(expressions.firstOrNull()) + return evaluateInternal(expressions.firstOrNull(), depth + 1) + } +} + +internal fun Number.negate(): Number { + return when (this) { + is Int -> -this + is Long -> -this + is Short -> -this + is Byte -> -this + is Double -> -this + is Float -> -this + else -> 0 } } @@ -357,7 +389,7 @@ class ValueEvaluator( * and compares an arbitrary [Number] with another [Number] using the dedicated compareTo functions * for the individual implementations of [Number], such as [Int.compareTo]. */ -private fun Number.compareTo(other: T): Int { +fun Number.compareTo(other: T): Int { return when { this is Byte && other is Double -> this.compareTo(other) this is Byte && other is Float -> this.compareTo(other) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt index 63efc9075b..f84fe352e4 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt @@ -56,14 +56,14 @@ class ValueEvaluatorTest { val b = main.bodyOrNull()?.singleDeclaration assertNotNull(b) - var value = b.evaluate() + var value = b.evaluateInternal() assertEquals(2L, value) val printB = main.bodyOrNull() assertNotNull(printB) val evaluator = ValueEvaluator() - value = evaluator.evaluate(printB.arguments.firstOrNull()) + value = evaluator.evaluateInternal(printB.arguments.firstOrNull()) assertEquals(2L, value) val path = evaluator.path @@ -72,70 +72,70 @@ class ValueEvaluatorTest { val printA = main.bodyOrNull(1) assertNotNull(printA) - value = printA.arguments.firstOrNull()?.evaluate() + value = printA.arguments.firstOrNull()?.evaluateInternal() assertEquals(2, value) val c = main.bodyOrNull(2)?.singleDeclaration assertNotNull(c) - value = c.evaluate() + value = c.evaluateInternal() assertEquals(3L, value) val d = main.bodyOrNull(3)?.singleDeclaration assertNotNull(d) - value = d.evaluate() + value = d.evaluateInternal() assertEquals(2L, value) val e = main.bodyOrNull(4)?.singleDeclaration assertNotNull(e) - value = e.evaluate() + value = e.evaluateInternal() assertEquals(3.5, value) val f = main.bodyOrNull(5)?.singleDeclaration assertNotNull(f) - value = f.evaluate() + value = f.evaluateInternal() assertEquals(10L, value) val printHelloWorld = main.bodyOrNull(2) assertNotNull(printHelloWorld) - value = printHelloWorld.arguments.firstOrNull()?.evaluate() + value = printHelloWorld.arguments.firstOrNull()?.evaluateInternal() assertEquals("Hello world", value) val g = main.bodyOrNull(6)?.singleDeclaration assertNotNull(g) - value = g.evaluate() + value = g.evaluateInternal() assertEquals(-3L, value) val h = main.bodyOrNull(7)?.singleDeclaration assertNotNull(h) - value = h.evaluate() + value = h.evaluateInternal() assertFalse(value as Boolean) val i = main.bodyOrNull(8)?.singleDeclaration assertNotNull(i) - value = i.evaluate() + value = i.evaluateInternal() assertFalse(value as Boolean) val j = main.bodyOrNull(9)?.singleDeclaration assertNotNull(j) - value = j.evaluate() + value = j.evaluateInternal() assertFalse(value as Boolean) val k = main.bodyOrNull(10)?.singleDeclaration assertNotNull(k) - value = k.evaluate() + value = k.evaluateInternal() assertFalse(value as Boolean) val l = main.bodyOrNull(11)?.singleDeclaration assertNotNull(l) - value = l.evaluate() + value = l.evaluateInternal() assertFalse(value as Boolean) val m = main.bodyOrNull(12)?.singleDeclaration assertNotNull(m) - value = m.evaluate() + value = m.evaluateInternal() assertFalse(value as Boolean) } } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt index b283a20ab9..1a83eba8c2 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.ValueEvaluator +import de.fraunhofer.aisec.cpg.graph.compareTo import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.isAccessible @@ -55,54 +56,6 @@ class QueryEvaluation { EXISTS } - abstract class NumberSet { - abstract fun min(): Long - abstract fun max(): Long - abstract fun addValue(value: Long) - abstract fun clear() - } - - class Interval : NumberSet() { - private var min: Long = Long.MAX_VALUE - private var max: Long = Long.MIN_VALUE - - override fun addValue(value: Long) { - if (value < min) { - min = value - } - if (value > max) { - max = value - } - } - override fun min(): Long { - return min - } - override fun max(): Long { - return max - } - override fun clear() { - min = Long.MAX_VALUE - max = Long.MIN_VALUE - } - } - - class ConcreteNumberSet : NumberSet() { - private var values: MutableSet = mutableSetOf() - - override fun addValue(value: Long) { - values.add(value) - } - override fun min(): Long { - return values.minOrNull()!! - } - override fun max(): Long { - return values.maxOrNull()!! - } - override fun clear() { - values.clear() - } - } - abstract class QueryExpression(open val representation: String) { abstract fun evaluate(input: Map = mutableMapOf()): Any } @@ -228,10 +181,14 @@ class QueryEvaluation { QueryOp.OR -> lhs.evaluate(input) as Boolean || rhs.evaluate(input) as Boolean QueryOp.EQ -> lhs.evaluate(input) == rhs.evaluate(input) QueryOp.NE -> lhs.evaluate(input) != rhs.evaluate(input) - QueryOp.GT -> lhs.evaluate(input) as Long > rhs.evaluate(input) as Long - QueryOp.GE -> lhs.evaluate(input) as Long >= rhs.evaluate(input) as Long - QueryOp.LT -> (lhs.evaluate(input) as Long) < (rhs.evaluate(input) as Long) - QueryOp.LE -> lhs.evaluate(input) as Long <= rhs.evaluate(input) as Long + QueryOp.GT -> + (lhs.evaluate(input) as Number).compareTo(rhs.evaluate(input) as Number) > 0 + QueryOp.GE -> + (lhs.evaluate(input) as Number).compareTo(rhs.evaluate(input) as Number) >= 0 + QueryOp.LT -> + (lhs.evaluate(input) as Number).compareTo(rhs.evaluate(input) as Number) < 0 + QueryOp.LE -> + (lhs.evaluate(input) as Number).compareTo(rhs.evaluate(input) as Number) <= 0 QueryOp.IS -> lhs.evaluate(input).javaClass.simpleName == rhs.evaluate(input) as String QueryOp.IMPLIES -> diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index db36cd6e4f..f1fe2bb4b8 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager +import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.analysis.QueryEvaluation.Quantifier import de.fraunhofer.aisec.cpg.console.fancyCode import de.fraunhofer.aisec.cpg.graph.ValueEvaluator @@ -35,9 +36,11 @@ import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import java.io.File import kotlin.test.assertFalse import kotlin.test.assertNotNull +import kotlin.test.assertTrue import org.junit.jupiter.api.Test class AnalysisTest { @@ -56,18 +59,10 @@ class AnalysisTest { OutOfBoundsCheck().run(result) } - @Test - fun testOutOfBoundsQuery() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array.cpp")) - .defaultPasses() - .defaultLanguages() - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - + fun createArrayOutOfBoundsQuery( + result: TranslationResult, + normalEvaluator: Boolean + ): QueryEvaluation.QueryExpression { // Query: forall (n: ArraySubscriptionExpression): |max(n.subscriptExpression)| < // |min(n.arrayExpression.refersTo.initializer.dimensions[0])| // && |min(n.subscriptExpression)| >= 0 @@ -83,7 +78,7 @@ class AnalysisTest { "n.subscriptExpression", "n", "subscriptExpression", - ValueEvaluator() + if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() ) val maxIndex = QueryEvaluation.UnaryExpr( @@ -102,7 +97,7 @@ class AnalysisTest { "n.arrayExpression.refersTo.initializer.dimensions[0]", "n", "arrayExpression.refersTo.initializer.dimensions[0]", - ValueEvaluator() + if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() ) val minCapacity = QueryEvaluation.UnaryExpr( @@ -140,7 +135,57 @@ class AnalysisTest { checks ) - assertFalse(forall.evaluate() as Boolean) + return forall + } + + @Test + fun testOutOfBoundsQuery() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/array.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val query = createArrayOutOfBoundsQuery(result, true) + assertFalse(query.evaluate() as Boolean) + } + + @Test + fun testOutOfBoundsQuery2() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/array2.cpp")) + .defaultPasses() + .defaultLanguages() + .registerPass(EdgeCachePass()) + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val query = createArrayOutOfBoundsQuery(result, false) + assertFalse(query.evaluate() as Boolean) + } + + @Test + fun testOutOfBoundsQueryCorrect() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/array_correct.cpp")) + .defaultPasses() + .defaultLanguages() + .registerPass(EdgeCachePass()) + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val query = createArrayOutOfBoundsQuery(result, false) + assertTrue(query.evaluate() as Boolean) } @Test diff --git a/cpg-console/src/test/resources/array2.cpp b/cpg-console/src/test/resources/array2.cpp new file mode 100644 index 0000000000..940a178f4f --- /dev/null +++ b/cpg-console/src/test/resources/array2.cpp @@ -0,0 +1,8 @@ +int main() { + char* c = new char[4]; + int a = 0; + for(int i = 0; i <= 4; i++) { + a = a + c[i]; + } + return a; +} diff --git a/cpg-console/src/test/resources/array_correct.cpp b/cpg-console/src/test/resources/array_correct.cpp new file mode 100644 index 0000000000..735b7324e2 --- /dev/null +++ b/cpg-console/src/test/resources/array_correct.cpp @@ -0,0 +1,8 @@ +int main() { + char* c = new char[4]; + int a = 0; + for(int i = 0; i < 4; i++) { + a = a + c[i]; + } + return a; +} From 22dd8b5b3ee020461f921c5e5ace7e570aa0e33b Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 21 Apr 2022 18:21:09 +0200 Subject: [PATCH 04/67] Fix error of refactoring --- .../aisec/cpg/analysis/ValueEvaluatorTest.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt index f84fe352e4..63efc9075b 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluatorTest.kt @@ -56,14 +56,14 @@ class ValueEvaluatorTest { val b = main.bodyOrNull()?.singleDeclaration assertNotNull(b) - var value = b.evaluateInternal() + var value = b.evaluate() assertEquals(2L, value) val printB = main.bodyOrNull() assertNotNull(printB) val evaluator = ValueEvaluator() - value = evaluator.evaluateInternal(printB.arguments.firstOrNull()) + value = evaluator.evaluate(printB.arguments.firstOrNull()) assertEquals(2L, value) val path = evaluator.path @@ -72,70 +72,70 @@ class ValueEvaluatorTest { val printA = main.bodyOrNull(1) assertNotNull(printA) - value = printA.arguments.firstOrNull()?.evaluateInternal() + value = printA.arguments.firstOrNull()?.evaluate() assertEquals(2, value) val c = main.bodyOrNull(2)?.singleDeclaration assertNotNull(c) - value = c.evaluateInternal() + value = c.evaluate() assertEquals(3L, value) val d = main.bodyOrNull(3)?.singleDeclaration assertNotNull(d) - value = d.evaluateInternal() + value = d.evaluate() assertEquals(2L, value) val e = main.bodyOrNull(4)?.singleDeclaration assertNotNull(e) - value = e.evaluateInternal() + value = e.evaluate() assertEquals(3.5, value) val f = main.bodyOrNull(5)?.singleDeclaration assertNotNull(f) - value = f.evaluateInternal() + value = f.evaluate() assertEquals(10L, value) val printHelloWorld = main.bodyOrNull(2) assertNotNull(printHelloWorld) - value = printHelloWorld.arguments.firstOrNull()?.evaluateInternal() + value = printHelloWorld.arguments.firstOrNull()?.evaluate() assertEquals("Hello world", value) val g = main.bodyOrNull(6)?.singleDeclaration assertNotNull(g) - value = g.evaluateInternal() + value = g.evaluate() assertEquals(-3L, value) val h = main.bodyOrNull(7)?.singleDeclaration assertNotNull(h) - value = h.evaluateInternal() + value = h.evaluate() assertFalse(value as Boolean) val i = main.bodyOrNull(8)?.singleDeclaration assertNotNull(i) - value = i.evaluateInternal() + value = i.evaluate() assertFalse(value as Boolean) val j = main.bodyOrNull(9)?.singleDeclaration assertNotNull(j) - value = j.evaluateInternal() + value = j.evaluate() assertFalse(value as Boolean) val k = main.bodyOrNull(10)?.singleDeclaration assertNotNull(k) - value = k.evaluateInternal() + value = k.evaluate() assertFalse(value as Boolean) val l = main.bodyOrNull(11)?.singleDeclaration assertNotNull(l) - value = l.evaluateInternal() + value = l.evaluate() assertFalse(value as Boolean) val m = main.bodyOrNull(12)?.singleDeclaration assertNotNull(m) - value = m.evaluateInternal() + value = m.evaluate() assertFalse(value as Boolean) } } From 598f0e3f4703d32d1b99975a48bdf8a86ef3674c Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 22 Apr 2022 11:49:53 +0200 Subject: [PATCH 05/67] Builder for Queries --- .../aisec/cpg/analysis/QueryBuilder.kt | 516 ++++++++++++++++++ .../aisec/cpg/analysis/QueryEvaluation.kt | 187 +++++-- .../aisec/cpg/analysis/AnalysisTest.kt | 106 ++-- 3 files changed, 693 insertions(+), 116 deletions(-) create mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt new file mode 100644 index 0000000000..a1e96ab631 --- /dev/null +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2022, 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.TranslationResult + +class QueryBuilder { + // Quantifiers + fun forall(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr = + QueryEvaluation.QuantifierExpr(QueryEvaluation.Quantifier.FORALL).apply(block) + fun exists(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr = + QueryEvaluation.QuantifierExpr(QueryEvaluation.Quantifier.EXISTS).apply(block) + + // Binary Operators + fun and(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.AND).apply(block) + fun or(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.OR).apply(block) + fun eq(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.EQ).apply(block) + fun ne(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.NE).apply(block) + fun gt(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.GT).apply(block) + fun ge(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.GE).apply(block) + fun lt(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.LT).apply(block) + fun le(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.LE).apply(block) + fun IS(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.IS).apply(block) + fun implies(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.IMPLIES).apply(block) + fun IN(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = + QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.IN).apply(block) + + // Unary Operators + fun not(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = + QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.NOT).apply(block) + fun min(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = + QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.MIN).apply(block) + fun max(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = + QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.MAX).apply(block) + + // Constant expression + fun const(block: QueryEvaluation.ConstExpr.() -> Unit): QueryEvaluation.ConstExpr = + QueryEvaluation.ConstExpr().apply(block) + + // Nodes expression + fun queryNodes( + result: TranslationResult, + block: QueryEvaluation.NodesExpression.() -> Unit + ): QueryEvaluation.NodesExpression = QueryEvaluation.NodesExpression(result).apply(block) + + // access a field + fun fieldAccess( + block: QueryEvaluation.FieldAccessExpr.() -> Unit + ): QueryEvaluation.FieldAccessExpr = QueryEvaluation.FieldAccessExpr().apply(block) +} + +// forall | exists (queryNodes): +fun QueryEvaluation.QuantifierExpr.queryNodes( + result: TranslationResult, + block: QueryEvaluation.NodesExpression.() -> Unit +): QueryEvaluation.QuantifierExpr { + variables = QueryBuilder().queryNodes(result, block) + return this +} + +fun QueryEvaluation.QuantifierExpr.fieldAccess( + block: QueryEvaluation.FieldAccessExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().fieldAccess(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.not( + block: QueryEvaluation.UnaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().not(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.and( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().and(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.or( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().or(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.eq( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().eq(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.ne( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().ne(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.gt( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().gt(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.ge( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().ge(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.lt( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().lt(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.le( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().le(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.IS( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().IS(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.implies( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().implies(block) + return this +} + +fun QueryEvaluation.QuantifierExpr.IN( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + inner = QueryBuilder().IN(block) + return this +} + +// +// and | or | eq | ne | gt | lt | ge | le | implies | is | in +// +fun QueryEvaluation.BinaryExpr.const( + block: QueryEvaluation.ConstExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + // Automagically pick lhs or rhs + if (lhs == null) { + lhs = QueryBuilder().const(block) + } else { + rhs = QueryBuilder().const(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.fieldAccess( + block: QueryEvaluation.FieldAccessExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().fieldAccess(block) + } else { + rhs = QueryBuilder().fieldAccess(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.not( + block: QueryEvaluation.UnaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().not(block) + } else { + rhs = QueryBuilder().not(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.max( + block: QueryEvaluation.UnaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().max(block) + } else { + rhs = QueryBuilder().max(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.min( + block: QueryEvaluation.UnaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().min(block) + } else { + rhs = QueryBuilder().min(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.and( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().and(block) + } else { + rhs = QueryBuilder().and(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.or( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().or(block) + } else { + rhs = QueryBuilder().or(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.eq( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().eq(block) + } else { + rhs = QueryBuilder().eq(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.ne( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().ne(block) + } else { + rhs = QueryBuilder().ne(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.gt( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().gt(block) + } else { + rhs = QueryBuilder().gt(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.ge( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().ge(block) + } else { + rhs = QueryBuilder().ge(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.lt( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().lt(block) + } else { + rhs = QueryBuilder().lt(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.le( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().le(block) + } else { + rhs = QueryBuilder().le(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.IS( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().IS(block) + } else { + rhs = QueryBuilder().IS(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.implies( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().implies(block) + } else { + rhs = QueryBuilder().implies(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.IN( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().IN(block) + } else { + rhs = QueryBuilder().IN(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.forall( + block: QueryEvaluation.QuantifierExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().forall(block) + } else { + rhs = QueryBuilder().forall(block) + } + return this +} + +fun QueryEvaluation.BinaryExpr.exists( + block: QueryEvaluation.QuantifierExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().exists(block) + } else { + rhs = QueryBuilder().exists(block) + } + return this +} + +// not | min | max +// +fun QueryEvaluation.UnaryExpr.const( + block: QueryEvaluation.ConstExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().const(block) + return this +} + +fun QueryEvaluation.UnaryExpr.fieldAccess( + block: QueryEvaluation.FieldAccessExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().fieldAccess(block) + return this +} + +fun QueryEvaluation.UnaryExpr.not( + block: QueryEvaluation.UnaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().not(block) + return this +} + +fun QueryEvaluation.UnaryExpr.max( + block: QueryEvaluation.UnaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().max(block) + return this +} + +fun QueryEvaluation.UnaryExpr.min( + block: QueryEvaluation.UnaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().min(block) + return this +} + +fun QueryEvaluation.UnaryExpr.and( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().and(block) + return this +} + +fun QueryEvaluation.UnaryExpr.or( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().or(block) + return this +} + +fun QueryEvaluation.UnaryExpr.eq( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().eq(block) + return this +} + +fun QueryEvaluation.UnaryExpr.ne( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().ne(block) + return this +} + +fun QueryEvaluation.UnaryExpr.gt( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().gt(block) + return this +} + +fun QueryEvaluation.UnaryExpr.ge( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().ge(block) + return this +} + +fun QueryEvaluation.UnaryExpr.lt( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().lt(block) + return this +} + +fun QueryEvaluation.UnaryExpr.le( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().le(block) + return this +} + +fun QueryEvaluation.UnaryExpr.IS( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().IS(block) + return this +} + +fun QueryEvaluation.UnaryExpr.implies( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().implies(block) + return this +} + +fun QueryEvaluation.UnaryExpr.IN( + block: QueryEvaluation.BinaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().IN(block) + return this +} + +fun QueryEvaluation.UnaryExpr.forall( + block: QueryEvaluation.QuantifierExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().forall(block) + return this +} + +fun QueryEvaluation.UnaryExpr.exists( + block: QueryEvaluation.QuantifierExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().exists(block) + return this +} diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt index 1a83eba8c2..79ea3bf6b9 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt @@ -48,7 +48,8 @@ class QueryEvaluation { NOT, AND, OR, - IS + IS, + IN } enum class Quantifier { @@ -56,30 +57,76 @@ class QueryEvaluation { EXISTS } - abstract class QueryExpression(open val representation: String) { + abstract class QueryExpression(open val representation: String?) { abstract fun evaluate(input: Map = mutableMapOf()): Any } - class NodesExpression( - override val representation: String, - val name: String, - private val nodeType: String, - private val result: TranslationResult - ) : QueryExpression(representation) { + class NodesExpression(override val representation: String? = "") : + QueryExpression(representation) { + lateinit var nodeType: String + lateinit var result: TranslationResult + var kClass: Class? = null + + constructor( + nodeType: String, + result: TranslationResult, + representation: String? = "" + ) : this(representation) { + this.nodeType = nodeType + this.result = result + } + + constructor( + result: TranslationResult, + representation: String? = "" + ) : this(representation) { + this.result = result + } + + constructor( + kClass: Class, + result: TranslationResult, + representation: String? = "" + ) : this(representation) { + this.result = result + this.kClass = kClass + this.nodeType = kClass.simpleName + } + override fun evaluate(input: Map): Any { + if (kClass != null) { + return SubgraphWalker.flattenAST(result).filter { n -> n.javaClass == kClass } + } return SubgraphWalker.flattenAST(result).filter { n -> n.javaClass.simpleName == nodeType } } } - class QuantifierExpr( - override val representation: String, - private val quantifier: Quantifier, - private val variables: QueryExpression, - private val variableName: String, - private val inner: QueryExpression - ) : QueryExpression(representation) { + class QuantifierExpr(override val representation: String? = "") : + QueryExpression(representation) { + lateinit var quantifier: Quantifier + lateinit var variables: QueryExpression + lateinit var variableName: String + lateinit var inner: QueryExpression + + constructor( + quantifier: Quantifier, + variables: QueryExpression, + variableName: String, + inner: QueryExpression, + representation: String? = "" + ) : this(representation) { + this.quantifier = quantifier + this.variables = variables + this.variableName = variableName + this.inner = inner + } + + constructor(quantifier: Quantifier, representation: String? = "") : this(representation) { + this.quantifier = quantifier + } + override fun evaluate(input: Map): Any { val newInput = input.toMutableMap() return if (quantifier == Quantifier.FORALL) { @@ -98,12 +145,24 @@ class QueryEvaluation { } } - class FieldAccessExpr( - override val representation: String, - private val variableName: String, - private val fieldSpecifier: String, - private val evaluator: ValueEvaluator - ) : QueryExpression(representation) { + class FieldAccessExpr(override val representation: String? = "") : + QueryExpression(representation) { + + lateinit var variableName: String + lateinit var fieldSpecifier: String + lateinit var evaluator: ValueEvaluator + + constructor( + variableName: String, + fieldSpecifier: String, + evaluator: ValueEvaluator, + representation: String? = "" + ) : this(representation) { + this.variableName = variableName + this.fieldSpecifier = fieldSpecifier + this.evaluator = evaluator + } + override fun evaluate(input: Map): Any { var currentField: Any = input[variableName]!! for (fs in fieldSpecifier.split(".")) { @@ -133,18 +192,35 @@ class QueryEvaluation { } } - class ConstExpr(override val representation: String, private val value: Any) : - QueryExpression(representation) { + class ConstExpr(override val representation: String? = "") : QueryExpression(representation) { + lateinit var value: Any + + constructor(value: Any, representation: String? = "") : this(representation) { + this.value = value + } + override fun evaluate(input: Map): Any { return value } } - class UnaryExpr( - override val representation: String, - private val inner: QueryExpression, - private val operator: QueryOp - ) : QueryExpression(representation) { + class UnaryExpr(override val representation: String? = "") : QueryExpression(representation) { + lateinit var inner: QueryExpression + lateinit var operator: QueryOp + + constructor( + inner: QueryExpression, + operator: QueryOp, + representation: String? = "" + ) : this(representation) { + this.inner = inner + this.operator = operator + } + + constructor(operator: QueryOp, representation: String? = "") : this(representation) { + this.operator = operator + } + override fun evaluate(input: Map): Any { return when (operator) { QueryOp.NOT -> !(inner.evaluate(input) as Boolean) @@ -169,30 +245,51 @@ class QueryEvaluation { } } - class BinaryExpr( - override val representation: String, - private val lhs: QueryExpression, - private val rhs: QueryExpression, - private val operator: QueryOp - ) : QueryExpression(representation) { + class BinaryExpr(override var representation: String? = "") : QueryExpression(representation) { + var lhs: QueryExpression? = null + var rhs: QueryExpression? = null + lateinit var operator: QueryOp + + constructor( + lhs: QueryExpression, + rhs: QueryExpression, + operator: QueryOp, + representation: String? = "" + ) : this(representation) { + this.lhs = lhs + this.rhs = rhs + this.operator = operator + } + + constructor(operator: QueryOp, representation: String? = "") : this(representation) { + this.operator = operator + } + override fun evaluate(input: Map): Boolean { return when (operator) { - QueryOp.AND -> lhs.evaluate(input) as Boolean && rhs.evaluate(input) as Boolean - QueryOp.OR -> lhs.evaluate(input) as Boolean || rhs.evaluate(input) as Boolean - QueryOp.EQ -> lhs.evaluate(input) == rhs.evaluate(input) - QueryOp.NE -> lhs.evaluate(input) != rhs.evaluate(input) + QueryOp.AND -> lhs?.evaluate(input) as Boolean && rhs?.evaluate(input) as Boolean + QueryOp.OR -> lhs?.evaluate(input) as Boolean || rhs?.evaluate(input) as Boolean + QueryOp.EQ -> lhs?.evaluate(input) == rhs?.evaluate(input) + QueryOp.NE -> lhs?.evaluate(input) != rhs?.evaluate(input) QueryOp.GT -> - (lhs.evaluate(input) as Number).compareTo(rhs.evaluate(input) as Number) > 0 + (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) > 0 QueryOp.GE -> - (lhs.evaluate(input) as Number).compareTo(rhs.evaluate(input) as Number) >= 0 + (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) >= 0 QueryOp.LT -> - (lhs.evaluate(input) as Number).compareTo(rhs.evaluate(input) as Number) < 0 + (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) < 0 QueryOp.LE -> - (lhs.evaluate(input) as Number).compareTo(rhs.evaluate(input) as Number) <= 0 - QueryOp.IS -> - lhs.evaluate(input).javaClass.simpleName == rhs.evaluate(input) as String + (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) <= 0 + QueryOp.IS -> { + val rhsVal = rhs?.evaluate(input) + if (rhsVal is String) { + lhs?.evaluate(input)?.javaClass?.simpleName == rhsVal + } else { + lhs?.evaluate(input)?.javaClass == rhsVal + } + } QueryOp.IMPLIES -> - !(lhs.evaluate(input) as Boolean) || rhs.evaluate(input) as Boolean + !(lhs?.evaluate(input) as Boolean) || rhs?.evaluate(input) as Boolean + QueryOp.IN -> lhs?.evaluate(input) in (rhs?.evaluate(input) as Collection<*>) else -> false } } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index f1fe2bb4b8..6b0e1139fa 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.analysis.QueryEvaluation.Quantifier import de.fraunhofer.aisec.cpg.console.fancyCode import de.fraunhofer.aisec.cpg.graph.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.body @@ -66,76 +65,41 @@ class AnalysisTest { // Query: forall (n: ArraySubscriptionExpression): |max(n.subscriptExpression)| < // |min(n.arrayExpression.refersTo.initializer.dimensions[0])| // && |min(n.subscriptExpression)| >= 0 - val nodesN = - QueryEvaluation.NodesExpression( - "(n: ArraySubscriptionExpression)", - "n", - "ArraySubscriptionExpression", - result - ) - val index = - QueryEvaluation.FieldAccessExpr( - "n.subscriptExpression", - "n", - "subscriptExpression", - if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() - ) - val maxIndex = - QueryEvaluation.UnaryExpr( - "|max(n.subscriptExpression)|", - index, - QueryEvaluation.QueryOp.MAX - ) - val minIndex = - QueryEvaluation.UnaryExpr( - "|min(n.subscriptExpression)|", - index, - QueryEvaluation.QueryOp.MIN - ) - val capacity = - QueryEvaluation.FieldAccessExpr( - "n.arrayExpression.refersTo.initializer.dimensions[0]", - "n", - "arrayExpression.refersTo.initializer.dimensions[0]", - if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() - ) - val minCapacity = - QueryEvaluation.UnaryExpr( - "|min(n.arrayExpression.refersTo.initializer.dimensions[0])|", - capacity, - QueryEvaluation.QueryOp.MIN - ) - val maxUp = - QueryEvaluation.BinaryExpr( - "|max(n.subscriptExpression)| < |min(n.arrayExpression.refersTo.initializer.dimensions[0])|", - maxIndex, - minCapacity, - QueryEvaluation.QueryOp.LT - ) - val min0 = - QueryEvaluation.BinaryExpr( - "|min(n.subscriptExpression)| >= 0", - minIndex, - QueryEvaluation.ConstExpr("0", 0), - QueryEvaluation.QueryOp.GE - ) - val checks = - QueryEvaluation.BinaryExpr( - "|max(n.subscriptExpression)| < |min(n.arrayExpression.refersTo.initializer.dimensions[0])| && |min(n.subscriptExpression)| >= 0", - maxUp, - min0, - QueryEvaluation.QueryOp.AND - ) - val forall = - QueryEvaluation.QuantifierExpr( - "forall (n: ArraySubscriptionExpression): |max(n.subscriptExpression)| < |min(n.arrayExpression.refersTo.initializer.dimensions[0])| && |min(n.subscriptExpression)| >= 0", - Quantifier.FORALL, - nodesN, - "n", - checks - ) - - return forall + return QueryBuilder().forall { + variableName = "n" + queryNodes(result) { nodeType = "ArraySubscriptionExpression" } + and { + lt { + max { + fieldAccess { + variableName = "n" + fieldSpecifier = "subscriptExpression" + evaluator = + if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() + } + } + min { + fieldAccess { + variableName = "n" + fieldSpecifier = "arrayExpression.refersTo.initializer.dimensions[0]" + evaluator = + if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() + } + } + } + ge { + min { + fieldAccess { + variableName = "n" + fieldSpecifier = "subscriptExpression" + evaluator = + if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() + } + } + const { value = 0 } + } + } + } } @Test From 8e6b338e8c7a549d1e2a41749c2b4c1063eb8f69 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 22 Apr 2022 14:23:44 +0200 Subject: [PATCH 06/67] Slightly simplify the Query API --- .../aisec/cpg/analysis/QueryBuilder.kt | 43 ++++++++++++++----- .../aisec/cpg/analysis/QueryEvaluation.kt | 30 ++++++++++--- .../aisec/cpg/analysis/AnalysisTest.kt | 32 ++++++-------- 3 files changed, 69 insertions(+), 36 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt index a1e96ab631..bdc42093c0 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt @@ -31,6 +31,13 @@ class QueryBuilder { // Quantifiers fun forall(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr = QueryEvaluation.QuantifierExpr(QueryEvaluation.Quantifier.FORALL).apply(block) + + fun forall( + tr: TranslationResult, + block: QueryEvaluation.QuantifierExpr.() -> Unit + ): QueryEvaluation.QuantifierExpr = + QueryEvaluation.QuantifierExpr(QueryEvaluation.Quantifier.FORALL, tr).apply(block) + fun exists(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr = QueryEvaluation.QuantifierExpr(QueryEvaluation.Quantifier.EXISTS).apply(block) @@ -67,8 +74,11 @@ class QueryBuilder { QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.MAX).apply(block) // Constant expression - fun const(block: QueryEvaluation.ConstExpr.() -> Unit): QueryEvaluation.ConstExpr = - QueryEvaluation.ConstExpr().apply(block) + fun const(value: Any): QueryEvaluation.ConstExpr { + val constExpr = QueryEvaluation.ConstExpr() + constExpr.value = value + return constExpr + } // Nodes expression fun queryNodes( @@ -82,6 +92,21 @@ class QueryBuilder { ): QueryEvaluation.FieldAccessExpr = QueryEvaluation.FieldAccessExpr().apply(block) } +fun forall( + translationResult: TranslationResult, + block: QueryEvaluation.QuantifierExpr.() -> Unit +): QueryEvaluation.QuantifierExpr { + return QueryBuilder().forall(translationResult, block) +} + +fun forall(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr { + return QueryBuilder().forall(block) +} + +fun exists(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr { + return QueryBuilder().exists(block) +} + // forall | exists (queryNodes): fun QueryEvaluation.QuantifierExpr.queryNodes( result: TranslationResult, @@ -187,14 +212,12 @@ fun QueryEvaluation.QuantifierExpr.IN( // and | or | eq | ne | gt | lt | ge | le | implies | is | in // -fun QueryEvaluation.BinaryExpr.const( - block: QueryEvaluation.ConstExpr.() -> Unit -): QueryEvaluation.BinaryExpr { +fun QueryEvaluation.BinaryExpr.const(value: Any): QueryEvaluation.BinaryExpr { // Automagically pick lhs or rhs if (lhs == null) { - lhs = QueryBuilder().const(block) + lhs = QueryBuilder().const(value) } else { - rhs = QueryBuilder().const(block) + rhs = QueryBuilder().const(value) } return this } @@ -389,10 +412,8 @@ fun QueryEvaluation.BinaryExpr.exists( // not | min | max // -fun QueryEvaluation.UnaryExpr.const( - block: QueryEvaluation.ConstExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().const(block) +fun QueryEvaluation.UnaryExpr.const(value: Any): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().const(value) return this } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt index 79ea3bf6b9..9a783d1c24 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt @@ -103,8 +103,17 @@ class QueryEvaluation { } } - class QuantifierExpr(override val representation: String? = "") : - QueryExpression(representation) { + class QuantifierExpr( + var result: TranslationResult? = null, + override val representation: String? = "" + ) : QueryExpression(representation) { + var str: String? = null + set(value) { + variableName = value?.split(":")?.get(0)?.strip() ?: "" + val varClass = value?.split(":")?.get(1)?.strip() ?: "" + variables = NodesExpression(varClass, result!!) + field = value + } lateinit var quantifier: Quantifier lateinit var variables: QueryExpression lateinit var variableName: String @@ -115,15 +124,20 @@ class QueryEvaluation { variables: QueryExpression, variableName: String, inner: QueryExpression, + result: TranslationResult? = null, representation: String? = "" - ) : this(representation) { + ) : this(result, representation) { this.quantifier = quantifier this.variables = variables this.variableName = variableName this.inner = inner } - constructor(quantifier: Quantifier, representation: String? = "") : this(representation) { + constructor( + quantifier: Quantifier, + result: TranslationResult? = null, + representation: String? = "" + ) : this(result, representation) { this.quantifier = quantifier } @@ -148,9 +162,15 @@ class QueryEvaluation { class FieldAccessExpr(override val representation: String? = "") : QueryExpression(representation) { + var str: String? = null + set(value) { + variableName = value?.split(".", limit = 2)?.get(0) ?: "" + fieldSpecifier = value?.split(".", limit = 2)?.get(1) ?: "" + field = value + } lateinit var variableName: String lateinit var fieldSpecifier: String - lateinit var evaluator: ValueEvaluator + var evaluator: ValueEvaluator = ValueEvaluator() constructor( variableName: String, diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index 6b0e1139fa..a19278a7f3 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -29,7 +29,6 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.console.fancyCode -import de.fraunhofer.aisec.cpg.graph.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.body import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration @@ -62,41 +61,34 @@ class AnalysisTest { result: TranslationResult, normalEvaluator: Boolean ): QueryEvaluation.QueryExpression { - // Query: forall (n: ArraySubscriptionExpression): |max(n.subscriptExpression)| < - // |min(n.arrayExpression.refersTo.initializer.dimensions[0])| - // && |min(n.subscriptExpression)| >= 0 - return QueryBuilder().forall { - variableName = "n" - queryNodes(result) { nodeType = "ArraySubscriptionExpression" } + // Query: forall (n: ArraySubscriptionExpression): max(n.subscriptExpression) < + // min(n.arrayExpression.refersTo.initializer.dimensions[0]) + // && min(n.subscriptExpression) >= 0 + return forall(result) { + str = "n: ArraySubscriptionExpression" and { lt { max { fieldAccess { - variableName = "n" - fieldSpecifier = "subscriptExpression" - evaluator = - if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() + str = "n.subscriptExpression" + if (!normalEvaluator) evaluator = MultiValueEvaluator() } } min { fieldAccess { - variableName = "n" - fieldSpecifier = "arrayExpression.refersTo.initializer.dimensions[0]" - evaluator = - if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() + str = "n.arrayExpression.refersTo.initializer.dimensions[0]" + if (!normalEvaluator) evaluator = MultiValueEvaluator() } } } ge { min { fieldAccess { - variableName = "n" - fieldSpecifier = "subscriptExpression" - evaluator = - if (normalEvaluator) ValueEvaluator() else MultiValueEvaluator() + str = "n.subscriptExpression" + if (!normalEvaluator) evaluator = MultiValueEvaluator() } } - const { value = 0 } + const(0) } } } From da9eed84e0bace3ba1dac1b4419f6648fae6893b Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 25 Apr 2022 09:24:52 +0200 Subject: [PATCH 07/67] Test null pointer check with query --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 14 ++++-- .../aisec/cpg/analysis/QueryBuilder.kt | 15 +++++- .../aisec/cpg/analysis/QueryEvaluation.kt | 48 +++++++++++++++---- .../aisec/cpg/analysis/AnalysisTest.kt | 30 +++++++++++- 4 files changed, 91 insertions(+), 16 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 69cb40671b..eb66565add 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.ValueEvaluator +import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.negate import de.fraunhofer.aisec.cpg.graph.statements.ForStatement @@ -45,15 +46,17 @@ class MultiValueEvaluator : ValueEvaluator() { override fun evaluate(node: Node?): Any? { val result = evaluateInternal(node, 0) - return if (result is List<*> && result.all { r -> r is Number? }) - ConcreteNumberSet( - result.filterNotNull().map { r -> (r as Number).toLong() }.toMutableSet() - ) + return if (result is List<*> && result.all { r -> r is Number }) + ConcreteNumberSet(result.map { r -> (r as Number).toLong() }.toMutableSet()) else result } /** Tries to evaluate this node. Anything can happen. */ override fun evaluateInternal(node: Node?, depth: Int): Any? { + if (node == null) { + return null + } + if (depth > MAX_DEPTH) { return cannotEvaluate(node, this) } @@ -61,6 +64,9 @@ class MultiValueEvaluator : ValueEvaluator() { node?.let { this.path += it } when (node) { + is FieldDeclaration -> { + return evaluateInternal(node.initializer, depth + 1) + } is ArrayCreationExpression -> return evaluateInternal(node.initializer, depth + 1) is VariableDeclaration -> return evaluateInternal(node.initializer, depth + 1) // For a literal, we can just take its value, and we are finished diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt index bdc42093c0..cd3cbe7107 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt @@ -74,7 +74,7 @@ class QueryBuilder { QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.MAX).apply(block) // Constant expression - fun const(value: Any): QueryEvaluation.ConstExpr { + fun const(value: Any?): QueryEvaluation.ConstExpr { val constExpr = QueryEvaluation.ConstExpr() constExpr.value = value return constExpr @@ -212,7 +212,7 @@ fun QueryEvaluation.QuantifierExpr.IN( // and | or | eq | ne | gt | lt | ge | le | implies | is | in // -fun QueryEvaluation.BinaryExpr.const(value: Any): QueryEvaluation.BinaryExpr { +fun QueryEvaluation.BinaryExpr.const(value: Any?): QueryEvaluation.BinaryExpr { // Automagically pick lhs or rhs if (lhs == null) { lhs = QueryBuilder().const(value) @@ -222,6 +222,17 @@ fun QueryEvaluation.BinaryExpr.const(value: Any): QueryEvaluation.BinaryExpr { return this } +fun QueryEvaluation.BinaryExpr.fieldAccess(str: String): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryEvaluation.FieldAccessExpr() + (lhs as QueryEvaluation.FieldAccessExpr).str = str + } else { + rhs = QueryEvaluation.FieldAccessExpr() + (rhs as QueryEvaluation.FieldAccessExpr).str = str + } + return this +} + fun QueryEvaluation.BinaryExpr.fieldAccess( block: QueryEvaluation.FieldAccessExpr.() -> Unit ): QueryEvaluation.BinaryExpr { diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt index 9a783d1c24..82d3e03d03 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.compareTo import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import kotlin.reflect.KProperty1 +import kotlin.reflect.full.allSupertypes import kotlin.reflect.jvm.isAccessible class QueryEvaluation { @@ -58,7 +59,7 @@ class QueryEvaluation { } abstract class QueryExpression(open val representation: String?) { - abstract fun evaluate(input: Map = mutableMapOf()): Any + abstract fun evaluate(input: Map = mutableMapOf()): Any? } class NodesExpression(override val representation: String? = "") : @@ -95,11 +96,26 @@ class QueryEvaluation { override fun evaluate(input: Map): Any { if (kClass != null) { - return SubgraphWalker.flattenAST(result).filter { n -> n.javaClass == kClass } + return SubgraphWalker.flattenAST(result).filter { n -> + n::class.allSupertypes.any { t -> t.javaClass == kClass } || + n.javaClass == kClass || + n.javaClass.interfaces.any { i -> i == kClass } + } } return SubgraphWalker.flattenAST(result).filter { n -> - n.javaClass.simpleName == nodeType + n.javaClass.simpleName == nodeType || + classNamesOfNode(n.javaClass).any { c -> c == nodeType } + } + } + + fun classNamesOfNode(jClass: Class<*>): Collection { + val result = mutableListOf() + if (jClass.superclass != null) { + result.add(jClass.superclass.simpleName) + result.addAll(classNamesOfNode(jClass.superclass)) } + result.addAll(jClass.interfaces.map { i -> i.simpleName }) + return result } } @@ -144,10 +160,16 @@ class QueryEvaluation { override fun evaluate(input: Map): Any { val newInput = input.toMutableMap() return if (quantifier == Quantifier.FORALL) { - (variables.evaluate(input) as Collection).all { v -> + var result = true + for (v in variables.evaluate(input) as Collection) { newInput[variableName] = v - inner.evaluate(newInput) as Boolean + val temp = (inner.evaluate(newInput) as Boolean) + if (!temp) { + // TODO: Collect potential problems here + result = false + } } + result } else if (quantifier == Quantifier.EXISTS) { (variables.evaluate(input) as Collection).any { v -> newInput[variableName] = v @@ -213,13 +235,13 @@ class QueryEvaluation { } class ConstExpr(override val representation: String? = "") : QueryExpression(representation) { - lateinit var value: Any + var value: Any? = null - constructor(value: Any, representation: String? = "") : this(representation) { + constructor(value: Any?, representation: String? = "") : this(representation) { this.value = value } - override fun evaluate(input: Map): Any { + override fun evaluate(input: Map): Any? { return value } } @@ -290,7 +312,15 @@ class QueryEvaluation { QueryOp.AND -> lhs?.evaluate(input) as Boolean && rhs?.evaluate(input) as Boolean QueryOp.OR -> lhs?.evaluate(input) as Boolean || rhs?.evaluate(input) as Boolean QueryOp.EQ -> lhs?.evaluate(input) == rhs?.evaluate(input) - QueryOp.NE -> lhs?.evaluate(input) != rhs?.evaluate(input) + QueryOp.NE -> { + val lhsVal = lhs?.evaluate(input) + val rhsVal = rhs?.evaluate(input) + if (lhsVal is Collection<*>) { + lhsVal.all { l -> l != rhsVal } + } else { + lhsVal != rhsVal + } + } QueryOp.GT -> (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) > 0 QueryOp.GE -> diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index a19278a7f3..135307a949 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -57,7 +57,7 @@ class AnalysisTest { OutOfBoundsCheck().run(result) } - fun createArrayOutOfBoundsQuery( + private fun createArrayOutOfBoundsQuery( result: TranslationResult, normalEvaluator: Boolean ): QueryEvaluation.QueryExpression { @@ -195,4 +195,32 @@ class AnalysisTest { code = call.fancyCode(3, true) println(code) } + + @Test + fun testNullPointerQuery() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/Array.java")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + // forall (n: Node): n.has_base() => n.base != null + val query = + forall(result) { + str = "n: HasBase" + ne { + fieldAccess { + str = "n.base" + evaluator = MultiValueEvaluator() + } + const(null) + } + } + + assertFalse(query.evaluate() as Boolean) + } } From 81793cd2b4750e9d41198db1449399539855a88f Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 25 Apr 2022 15:57:02 +0200 Subject: [PATCH 08/67] Test for memcpy query and sizeof --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 4 +- .../aisec/cpg/analysis/SizeEvaluator.kt | 61 +++++++++++++++++++ .../aisec/cpg/analysis/ValueEvaluator.kt | 6 +- .../aisec/cpg/analysis/QueryBuilder.kt | 22 ++++++- .../aisec/cpg/analysis/QueryEvaluation.kt | 16 ++++- .../aisec/cpg/analysis/AnalysisTest.kt | 32 ++++++++++ cpg-console/src/test/resources/vulnerable.cpp | 5 ++ 7 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt create mode 100644 cpg-console/src/test/resources/vulnerable.cpp diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index eb66565add..07e975065b 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -44,8 +44,8 @@ class MultiValueEvaluator : ValueEvaluator() { override val log: Logger get() = LoggerFactory.getLogger(MultiValueEvaluator::class.java) - override fun evaluate(node: Node?): Any? { - val result = evaluateInternal(node, 0) + override fun evaluate(node: Any?): Any? { + val result = evaluateInternal(node as? Node, 0) return if (result is List<*> && result.all { r -> r is Number }) ConcreteNumberSet(result.map { r -> (r as Number).toLong() }.toMutableSet()) else result diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt new file mode 100644 index 0000000000..3b888cf16e --- /dev/null +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, 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.graph.Node +import de.fraunhofer.aisec.cpg.graph.ValueEvaluator +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class SizeEvaluator : ValueEvaluator() { + override val log: Logger + get() = LoggerFactory.getLogger(MultiValueEvaluator::class.java) + + override fun evaluate(node: Any?): Any? { + if (node is String) { + return node.length + } + val result = evaluateInternal(node as? Node, 0) + return result + } + + override fun evaluateInternal(node: Node?, depth: Int): Any? { + // Add the expression to the current path + node?.let { this.path += it } + + return when (node) { + is ArrayCreationExpression -> evaluateInternal(node.initializer, depth + 1) + is VariableDeclaration -> evaluateInternal(node.initializer, depth + 1) + is DeclaredReferenceExpression -> evaluateInternal(node.refersTo, depth + 1) + // For a literal, we can just take its value, and we are finished + is Literal<*> -> if (node.value is String) (node.value as String).length else node.value + is ArraySubscriptionExpression -> evaluate(node.arrayExpression) + else -> cannotEvaluate(node, this) + } + } +} diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 050a0d1249..a383124e1a 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -67,8 +67,10 @@ open class ValueEvaluator( /** This property contains the path of the latest execution of [evaluateInternal]. */ val path: MutableList = mutableListOf() - open fun evaluate(node: Node?): Any? { - return evaluateInternal(node, 0) + open fun evaluate(node: Any?): Any? { + if (node !is Node) return node + + return evaluateInternal(node as? Node, 0) } /** Tries to evaluate this node. Anything can happen. */ diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt index cd3cbe7107..ec0727c6f1 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt @@ -70,6 +70,8 @@ class QueryBuilder { QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.NOT).apply(block) fun min(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.MIN).apply(block) + fun sizeof(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = + QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.SIZEOF).apply(block) fun max(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.MAX).apply(block) @@ -277,6 +279,17 @@ fun QueryEvaluation.BinaryExpr.min( return this } +fun QueryEvaluation.BinaryExpr.sizeof( + block: QueryEvaluation.UnaryExpr.() -> Unit +): QueryEvaluation.BinaryExpr { + if (lhs == null) { + lhs = QueryBuilder().sizeof(block) + } else { + rhs = QueryBuilder().sizeof(block) + } + return this +} + fun QueryEvaluation.BinaryExpr.and( block: QueryEvaluation.BinaryExpr.() -> Unit ): QueryEvaluation.BinaryExpr { @@ -420,7 +433,7 @@ fun QueryEvaluation.BinaryExpr.exists( return this } -// not | min | max +// not | min | max | sizeof // fun QueryEvaluation.UnaryExpr.const(value: Any): QueryEvaluation.UnaryExpr { @@ -456,6 +469,13 @@ fun QueryEvaluation.UnaryExpr.min( return this } +fun QueryEvaluation.UnaryExpr.sizeof( + block: QueryEvaluation.UnaryExpr.() -> Unit +): QueryEvaluation.UnaryExpr { + inner = QueryBuilder().sizeof(block) + return this +} + fun QueryEvaluation.UnaryExpr.and( block: QueryEvaluation.BinaryExpr.() -> Unit ): QueryEvaluation.UnaryExpr { diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt index 82d3e03d03..b2710736ea 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt @@ -46,6 +46,7 @@ class QueryEvaluation { IMPLIES, MAX, MIN, + SIZEOF, NOT, AND, OR, @@ -222,9 +223,16 @@ class QueryEvaluation { currentField = currentField[arrayIndex]!! // Ugly hack to get the property where the edge points to currentField = readInstanceProperty(currentField, "end") + } else if (currentField is List<*>) { + // The query assumes a single value instead of a list. We just return the first + // element. + currentField = currentField[0]!! + // Ugly hack to get the property where the edge points to + currentField = readInstanceProperty(currentField, "end") } } - return evaluator.evaluate(currentField as Node)!! + + return evaluator.evaluate(currentField)!! } private fun readInstanceProperty(instance: Any, propertyName: String): Any { @@ -263,7 +271,7 @@ class QueryEvaluation { this.operator = operator } - override fun evaluate(input: Map): Any { + override fun evaluate(input: Map): Any? { return when (operator) { QueryOp.NOT -> !(inner.evaluate(input) as Boolean) QueryOp.MAX -> { @@ -282,6 +290,10 @@ class QueryEvaluation { (result as NumberSet).min() } } + QueryOp.SIZEOF -> { + (inner as? FieldAccessExpr)?.evaluator = SizeEvaluator() + inner.evaluate(input) + } else -> throw Exception("Unknown operation $operator on expression $inner") } } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index 135307a949..57b270119c 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -223,4 +223,36 @@ class AnalysisTest { assertFalse(query.evaluate() as Boolean) } + + @Test + fun testMemcpyTooLargeQuery() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + // forall (n: CallExpression): n.invokes.name == "memcpy" => |sizeof(n.arguments[0])| < + // |sizeof(n.arguments[2])| + val query = + forall(result) { + str = "n: CallExpression" + implies { + eq { + fieldAccess { str = "n.invokes.name" } + const("memcpy") + } + ge { + sizeof { fieldAccess { str = "n.arguments[0]" } } + sizeof { fieldAccess { str = "n.arguments[1]" } } + } + } + } + + assertFalse(query.evaluate() as Boolean) + } } diff --git a/cpg-console/src/test/resources/vulnerable.cpp b/cpg-console/src/test/resources/vulnerable.cpp new file mode 100644 index 0000000000..5889ba5bc3 --- /dev/null +++ b/cpg-console/src/test/resources/vulnerable.cpp @@ -0,0 +1,5 @@ +int main() { + char array[6] = "hello"; + memcpy(array, "Hello world", 11); + printf(array); +} \ No newline at end of file From c3e21b55cc8ac24a9559557cf20baaf24f507260 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 26 Apr 2022 16:21:10 +0200 Subject: [PATCH 09/67] Try to compress the notation a bit --- .../aisec/cpg/analysis/QueryBuilder.kt | 56 +++++++++++++++++-- .../aisec/cpg/analysis/QueryEvaluation.kt | 39 +++++++++++++ .../aisec/cpg/analysis/AnalysisTest.kt | 36 ++++-------- 3 files changed, 100 insertions(+), 31 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt index ec0727c6f1..c17b4385ef 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.ValueEvaluator class QueryBuilder { // Quantifiers @@ -82,6 +83,20 @@ class QueryBuilder { return constExpr } + // Unary expressions + fun sizeof(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { + return QueryEvaluation.UnaryExpr(inner, QueryEvaluation.QueryOp.SIZEOF) + } + fun min(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { + return QueryEvaluation.UnaryExpr(inner, QueryEvaluation.QueryOp.MIN) + } + fun max(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { + return QueryEvaluation.UnaryExpr(inner, QueryEvaluation.QueryOp.MAX) + } + fun not(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { + return QueryEvaluation.UnaryExpr(inner, QueryEvaluation.QueryOp.NOT) + } + // Nodes expression fun queryNodes( result: TranslationResult, @@ -118,7 +133,7 @@ fun QueryEvaluation.QuantifierExpr.queryNodes( return this } -fun QueryEvaluation.QuantifierExpr.fieldAccess( +fun QueryEvaluation.QuantifierExpr.field( block: QueryEvaluation.FieldAccessExpr.() -> Unit ): QueryEvaluation.QuantifierExpr { inner = QueryBuilder().fieldAccess(block) @@ -209,6 +224,37 @@ fun QueryEvaluation.QuantifierExpr.IN( return this } +fun const(value: Any?): QueryEvaluation.ConstExpr { + return QueryBuilder().const(value) +} + +fun sizeof(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { + return QueryBuilder().sizeof(inner) +} + +fun field(str: String, valueEvaluator: ValueEvaluator): QueryEvaluation.FieldAccessExpr { + return QueryEvaluation.FieldAccessExpr(str, valueEvaluator) +} + +fun field(str: String): QueryEvaluation.FieldAccessExpr { + val res = QueryEvaluation.FieldAccessExpr() + res.str = str + return res +} + +fun forall( + str: String, + inner: QueryEvaluation.QueryExpression, + result: TranslationResult +): QueryEvaluation.QuantifierExpr { + val res = QueryEvaluation.QuantifierExpr() + res.result = result + res.quantifier = QueryEvaluation.Quantifier.FORALL + res.str = str + res.inner = inner + return res +} + // // and | or | eq | ne | gt | lt | ge | le | implies | is | in @@ -224,7 +270,7 @@ fun QueryEvaluation.BinaryExpr.const(value: Any?): QueryEvaluation.BinaryExpr { return this } -fun QueryEvaluation.BinaryExpr.fieldAccess(str: String): QueryEvaluation.BinaryExpr { +fun QueryEvaluation.BinaryExpr.field(str: String): QueryEvaluation.BinaryExpr { if (lhs == null) { lhs = QueryEvaluation.FieldAccessExpr() (lhs as QueryEvaluation.FieldAccessExpr).str = str @@ -235,7 +281,7 @@ fun QueryEvaluation.BinaryExpr.fieldAccess(str: String): QueryEvaluation.BinaryE return this } -fun QueryEvaluation.BinaryExpr.fieldAccess( +fun QueryEvaluation.BinaryExpr.field( block: QueryEvaluation.FieldAccessExpr.() -> Unit ): QueryEvaluation.BinaryExpr { if (lhs == null) { @@ -389,7 +435,7 @@ fun QueryEvaluation.BinaryExpr.IS( return this } -fun QueryEvaluation.BinaryExpr.implies( +infix fun QueryEvaluation.BinaryExpr.implies( block: QueryEvaluation.BinaryExpr.() -> Unit ): QueryEvaluation.BinaryExpr { if (lhs == null) { @@ -441,7 +487,7 @@ fun QueryEvaluation.UnaryExpr.const(value: Any): QueryEvaluation.UnaryExpr { return this } -fun QueryEvaluation.UnaryExpr.fieldAccess( +fun QueryEvaluation.UnaryExpr.field( block: QueryEvaluation.FieldAccessExpr.() -> Unit ): QueryEvaluation.UnaryExpr { inner = QueryBuilder().fieldAccess(block) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt index b2710736ea..a1597f2e6e 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt @@ -61,6 +61,34 @@ class QueryEvaluation { abstract class QueryExpression(open val representation: String?) { abstract fun evaluate(input: Map = mutableMapOf()): Any? + + infix fun `==`(other: QueryExpression): BinaryExpr { + return BinaryExpr(this, other, QueryOp.EQ) + } + + infix fun `!=`(other: QueryExpression): BinaryExpr { + return BinaryExpr(this, other, QueryOp.NE) + } + + infix fun implies(other: QueryExpression): BinaryExpr { + return BinaryExpr(this, other, QueryOp.IMPLIES) + } + + infix fun ge(other: QueryExpression): BinaryExpr { + return BinaryExpr(this, other, QueryOp.GE) + } + + infix fun gt(other: QueryExpression): BinaryExpr { + return BinaryExpr(this, other, QueryOp.GT) + } + + infix fun le(other: QueryExpression): BinaryExpr { + return BinaryExpr(this, other, QueryOp.LE) + } + + infix fun lt(other: QueryExpression): BinaryExpr { + return BinaryExpr(this, other, QueryOp.LT) + } } class NodesExpression(override val representation: String? = "") : @@ -195,6 +223,17 @@ class QueryEvaluation { lateinit var fieldSpecifier: String var evaluator: ValueEvaluator = ValueEvaluator() + constructor( + str: String, + evaluator: ValueEvaluator, + representation: String? = "" + ) : this(representation) { + this.str = str + variableName = str.split(".", limit = 2).get(0) + fieldSpecifier = str.split(".", limit = 2).get(1) + this.evaluator = evaluator + } + constructor( variableName: String, fieldSpecifier: String, diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index 57b270119c..cd8c4cf868 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -69,13 +69,13 @@ class AnalysisTest { and { lt { max { - fieldAccess { + field { str = "n.subscriptExpression" if (!normalEvaluator) evaluator = MultiValueEvaluator() } } min { - fieldAccess { + field { str = "n.arrayExpression.refersTo.initializer.dimensions[0]" if (!normalEvaluator) evaluator = MultiValueEvaluator() } @@ -83,7 +83,7 @@ class AnalysisTest { } ge { min { - fieldAccess { + field { str = "n.subscriptExpression" if (!normalEvaluator) evaluator = MultiValueEvaluator() } @@ -210,16 +210,7 @@ class AnalysisTest { // forall (n: Node): n.has_base() => n.base != null val query = - forall(result) { - str = "n: HasBase" - ne { - fieldAccess { - str = "n.base" - evaluator = MultiValueEvaluator() - } - const(null) - } - } + forall("n: HasBase", field("n.base", MultiValueEvaluator()) `!=` const(null), result) assertFalse(query.evaluate() as Boolean) } @@ -239,19 +230,12 @@ class AnalysisTest { // forall (n: CallExpression): n.invokes.name == "memcpy" => |sizeof(n.arguments[0])| < // |sizeof(n.arguments[2])| val query = - forall(result) { - str = "n: CallExpression" - implies { - eq { - fieldAccess { str = "n.invokes.name" } - const("memcpy") - } - ge { - sizeof { fieldAccess { str = "n.arguments[0]" } } - sizeof { fieldAccess { str = "n.arguments[1]" } } - } - } - } + forall( + "n: CallExpression", + ((field("n.invokes.name") `==` const("memcpy")) implies + (sizeof(field("n.arguments[0]")) ge sizeof(field("n.arguments[1]")))), + result + ) assertFalse(query.evaluate() as Boolean) } From b0ea44be9230d92d724376249e1b6a46aaf276a1 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 26 Apr 2022 19:51:24 +0200 Subject: [PATCH 10/67] Quick alternative using reified function --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 52 ++++++++++++++++ .../aisec/cpg/analysis2/Analysis2Test.kt | 59 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt create mode 100644 cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt new file mode 100644 index 0000000000..55a69e98d6 --- /dev/null +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022, 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.query + +import de.fraunhofer.aisec.cpg.ExperimentalGraph +import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.graph + +@ExperimentalGraph +inline fun TranslationResult.all( + sel: (T) -> Boolean, + mustSatisfy: (T) -> Boolean +): Boolean { + var nodes = this.graph.nodes.filterIsInstance() + + // filter the nodes according to the selector + nodes = nodes.filter(sel) + + return nodes.all(mustSatisfy) +} + +fun sizeof(n: Node?): Int { + val eval = SizeEvaluator() + // TODO(oxisto): This cast could potentially go wrong, but if its not an int, its not really a + // size + return eval.evaluate(n) as? Int ?: -1 +} diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt new file mode 100644 index 0000000000..dc330f44a3 --- /dev/null +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022, 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.analysis2 + +import de.fraunhofer.aisec.cpg.ExperimentalGraph +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationManager +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.query.all +import de.fraunhofer.aisec.cpg.query.sizeof +import java.io.File +import kotlin.test.assertFalse +import org.junit.jupiter.api.Test + +class Analysis2Test { + @OptIn(ExperimentalGraph::class) + @Test + fun testMemcpyTooLargeQuery2() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val ok = + result.all({ it.name == "memcpy" }) { + sizeof(it.arguments[0]) > sizeof(it.arguments[1]) + } + + assertFalse(ok) + } +} From 1260fb341a31910c833577edfad3f248b5796452 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 26 Apr 2022 20:06:09 +0200 Subject: [PATCH 11/67] Even more fancy --- .../aisec/cpg/analysis/QueryEvaluation.kt | 4 +++ .../de/fraunhofer/aisec/cpg/query/Query.kt | 14 +++++++++++ .../aisec/cpg/analysis/AnalysisTest.kt | 25 +++++++++++++++++++ .../aisec/cpg/analysis2/Analysis2Test.kt | 22 ++++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt index a1597f2e6e..f235bc37b1 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt @@ -66,6 +66,10 @@ class QueryEvaluation { return BinaryExpr(this, other, QueryOp.EQ) } + infix fun eq(other: QueryExpression): BinaryExpr { + return BinaryExpr(this, other, QueryOp.EQ) + } + infix fun `!=`(other: QueryExpression): BinaryExpr { return BinaryExpr(this, other, QueryOp.NE) } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 55a69e98d6..90ff7680b2 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -29,7 +29,9 @@ import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.evaluate import de.fraunhofer.aisec.cpg.graph.graph +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @ExperimentalGraph inline fun TranslationResult.all( @@ -50,3 +52,15 @@ fun sizeof(n: Node?): Int { // size return eval.evaluate(n) as? Int ?: -1 } + +class QueryResult(val inner: Any? = null) { + operator fun compareTo(o: QueryResult): Int { + // for now assume that its also an int (which is not always the case of course) + return this.inner as Int - o.inner as Int + } +} + +val Expression.size: QueryResult + get() { + return QueryResult(sizeof(this)) + } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index cd8c4cf868..a2c5645acd 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -239,4 +239,29 @@ class AnalysisTest { assertFalse(query.evaluate() as Boolean) } + + @Test + fun testMemcpyTooLargeQuery2() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + // forall (n: CallExpression): n.invokes.name == "memcpy" => |sizeof(n.arguments[0])| < + // |sizeof(n.arguments[2])| + val query = + forall( + "n: CallExpression", + ((field("n.invokes.name") eq const("memcpy")) implies + (sizeof(field("n.arguments[0]")) ge sizeof(field("n.arguments[1]")))), + result + ) + + assertFalse(query.evaluate() as Boolean) + } } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index dc330f44a3..0df9af037d 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.query.all +import de.fraunhofer.aisec.cpg.query.size import de.fraunhofer.aisec.cpg.query.sizeof import java.io.File import kotlin.test.assertFalse @@ -56,4 +57,25 @@ class Analysis2Test { assertFalse(ok) } + + @OptIn(ExperimentalGraph::class) + @Test + fun testMemcpyTooLargeQuery() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val ok = + result.all({ it.name == "memcpy" }) { + it.arguments[0].size > it.arguments[1].size + } + + assertFalse(ok) + } } From 8dbf785a3c685fef89f92c627459c4e0ad6ee842 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Tue, 26 Apr 2022 20:51:25 +0200 Subject: [PATCH 12/67] more possibilites --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 26 ++++++++++++++++ .../aisec/cpg/analysis2/Analysis2Test.kt | 30 +++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 90ff7680b2..07c4924ea4 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -53,14 +53,40 @@ fun sizeof(n: Node?): Int { return eval.evaluate(n) as? Int ?: -1 } +fun const(n: Int): QueryResult { + return QueryResult(n) +} + class QueryResult(val inner: Any? = null) { operator fun compareTo(o: QueryResult): Int { // for now assume that its also an int (which is not always the case of course) return this.inner as Int - o.inner as Int } + + override fun equals(other: Any?): Boolean { + if (other is Int && this.inner is Int) { + return other == this.inner + } + + if (other is QueryResult) { + return this.inner?.equals(other.inner) ?: false + } + + return super.equals(other) + } } val Expression.size: QueryResult get() { return QueryResult(sizeof(this)) } + +val Expression.value: QueryResult + get() { + return QueryResult(evaluate()) + } + +val Expression.intValue: Int? + get() { + return evaluate() as? Int + } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index 0df9af037d..5ccd6a0ee3 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -29,11 +29,10 @@ import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.query.all -import de.fraunhofer.aisec.cpg.query.size -import de.fraunhofer.aisec.cpg.query.sizeof +import de.fraunhofer.aisec.cpg.query.* import java.io.File import kotlin.test.assertFalse +import kotlin.test.assertTrue import org.junit.jupiter.api.Test class Analysis2Test { @@ -78,4 +77,29 @@ class Analysis2Test { assertFalse(ok) } + + @OptIn(ExperimentalGraph::class) + @Test + fun testParameterEqualsConst() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + var ok = + result.all({ it.name == "memcpy" }) { + it.arguments[2].value == const(11) + } + + assertTrue(ok) + + ok = result.all({ it.name == "memcpy" }) { it.arguments[2].intValue == 11 } + + assertTrue(ok) + } } From 0cc817e62b4271b743e1cd0cae8a82717da110aa Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 27 Apr 2022 11:02:52 +0200 Subject: [PATCH 13/67] added Assignment interface --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 26 +++++++--- .../aisec/cpg/analysis2/Analysis2Test.kt | 19 ++++++++ .../fraunhofer/aisec/cpg/graph/Assignment.kt | 47 +++++++++++++++++++ .../declarations/VariableDeclaration.java | 21 ++++++--- .../expressions/BinaryOperator.java | 32 ++++++++++--- .../DeclaredReferenceExpression.java | 3 +- 6 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 07c4924ea4..ac3a18a1fa 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -34,14 +34,20 @@ import de.fraunhofer.aisec.cpg.graph.graph import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @ExperimentalGraph -inline fun TranslationResult.all( - sel: (T) -> Boolean, - mustSatisfy: (T) -> Boolean +inline fun TranslationResult.all( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean ): Boolean { + println("${this.graph.nodes.size} before filter") + var nodes = this.graph.nodes.filterIsInstance() + println("${nodes.size} nodes after filter") + // filter the nodes according to the selector - nodes = nodes.filter(sel) + if (sel != null) { + nodes = nodes.filter(sel) + } return nodes.all(mustSatisfy) } @@ -57,10 +63,18 @@ fun const(n: Int): QueryResult { return QueryResult(n) } +operator fun Expression?.invoke(): QueryResult { + return QueryResult(this?.evaluate()) +} + class QueryResult(val inner: Any? = null) { operator fun compareTo(o: QueryResult): Int { - // for now assume that its also an int (which is not always the case of course) - return this.inner as Int - o.inner as Int + if (this.inner is Int && o.inner is Int) { + // for now assume that its also an int (which is not always the case of course) + return this.inner as Int - o.inner as Int + } else { + return -1 + } } override fun equals(other: Any?): Boolean { diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index 5ccd6a0ee3..5213875d38 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.analysis2 import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager +import de.fraunhofer.aisec.cpg.graph.Assignment import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.query.* import java.io.File @@ -102,4 +103,22 @@ class Analysis2Test { assertTrue(ok) } + + @OptIn(ExperimentalGraph::class) + @Test + fun testAssign() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/assign.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val ok = result.all { it.value() < const(5) } + + assertTrue(ok) + } } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt new file mode 100644 index 0000000000..bff48f67e2 --- /dev/null +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, 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.graph + +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression + +/** An assignment assigns a certain value (usually an [Expression]) to a certain target. */ +interface Assignment { + /** + * The target of this assignment. Note that this is intentionally nullable, because while + * [BinaryOperator] implements [Assignment], not all binary operations are assignments. Thus, + * the target is only non-null for operations that have a == operator. + */ + val target: AssignmentTarget? + val value: Expression? +} + +/** + * The target of an assignment. The target is usually either a [Declaration] or a + * [DeclaredReferenceExpression]. + */ +interface AssignmentTarget { + val name: String +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.java index 1e44c4a26c..45abddc657 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/declarations/VariableDeclaration.java @@ -25,12 +25,8 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations; -import de.fraunhofer.aisec.cpg.graph.HasInitializer; -import de.fraunhofer.aisec.cpg.graph.HasType; +import de.fraunhofer.aisec.cpg.graph.*; import de.fraunhofer.aisec.cpg.graph.HasType.TypeListener; -import de.fraunhofer.aisec.cpg.graph.Node; -import de.fraunhofer.aisec.cpg.graph.SubGraph; -import de.fraunhofer.aisec.cpg.graph.TypeManager; import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression; import de.fraunhofer.aisec.cpg.graph.statements.expressions.InitializerListExpression; import de.fraunhofer.aisec.cpg.graph.types.Type; @@ -40,7 +36,8 @@ import org.neo4j.ogm.annotation.Relationship; /** Represents the declaration of a variable. */ -public class VariableDeclaration extends ValueDeclaration implements TypeListener, HasInitializer { +public class VariableDeclaration extends ValueDeclaration + implements TypeListener, HasInitializer, Assignment, AssignmentTarget { /** The (optional) initializer of the declaration. */ @SubGraph("AST") @@ -197,4 +194,16 @@ public boolean equals(Object o) { public int hashCode() { return super.hashCode(); } + + @Nullable + @Override + public AssignmentTarget getTarget() { + return this; + } + + @Nullable + @Override + public Expression getValue() { + return initializer; + } } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.java index e40a2635ae..6466d7007d 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.java @@ -25,23 +25,20 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions; -import de.fraunhofer.aisec.cpg.graph.AccessValues; -import de.fraunhofer.aisec.cpg.graph.HasType; +import de.fraunhofer.aisec.cpg.graph.*; import de.fraunhofer.aisec.cpg.graph.HasType.TypeListener; -import de.fraunhofer.aisec.cpg.graph.Node; -import de.fraunhofer.aisec.cpg.graph.SubGraph; -import de.fraunhofer.aisec.cpg.graph.TypeManager; import de.fraunhofer.aisec.cpg.graph.types.Type; import de.fraunhofer.aisec.cpg.graph.types.TypeParser; import java.util.*; import org.apache.commons.lang3.builder.ToStringBuilder; +import org.jetbrains.annotations.Nullable; import org.neo4j.ogm.annotation.Transient; /** * A binary operation expression, such as "a + b". It consists of a left hand expression (lhs), a * right hand expression (rhs) and an operatorCode. */ -public class BinaryOperator extends Expression implements TypeListener { +public class BinaryOperator extends Expression implements TypeListener, Assignment { /** The left hand expression. */ @SubGraph("AST") @@ -245,4 +242,27 @@ public boolean equals(Object o) { public int hashCode() { return super.hashCode(); } + + @Nullable + @Override + public AssignmentTarget getTarget() { + // We only want to supply a target if this is an assignment + return isAssignment() + ? (lhs instanceof AssignmentTarget ? (AssignmentTarget) lhs : null) + : null; + } + + @Nullable + @Override + public Expression getValue() { + return isAssignment() ? rhs : null; + } + + public boolean isAssignment() { + // TODO(oxisto): We need to discuss, if the other operators are also assignments and if we + // really want them + return this.operatorCode.equals( + "==") /*||this.operatorCode.equals("+=") ||this.operatorCode.equals("-=") + ||this.operatorCode.equals("/=") ||this.operatorCode.equals("*=")*/; + } } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.java index 225f16ab68..eced02c451 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/DeclaredReferenceExpression.java @@ -45,7 +45,8 @@ * DeclaredReferenceExpression}s, one for the variable a and one for variable b * , which have been previously been declared. */ -public class DeclaredReferenceExpression extends Expression implements TypeListener { +public class DeclaredReferenceExpression extends Expression + implements TypeListener, AssignmentTarget { /** The {@link Declaration}s this expression might refer to. */ @Relationship(value = "REFERS_TO") From 65b20e0bf98e6afa120928785b82f84b542592c6 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 27 Apr 2022 11:11:28 +0200 Subject: [PATCH 14/67] Added forgotten stuff --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 22 +++++++++++++------ .../aisec/cpg/analysis2/Analysis2Test.kt | 5 +---- cpg-console/src/test/resources/assign.cpp | 6 +++++ .../fraunhofer/aisec/cpg/graph/Assignment.kt | 10 ++++++++- 4 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 cpg-console/src/test/resources/assign.cpp diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index ac3a18a1fa..849690f947 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -59,6 +59,10 @@ fun sizeof(n: Node?): Int { return eval.evaluate(n) as? Int ?: -1 } +/** + * This is a small wrapper to create a [QueryResult] containing a constant value, so that it can be + * used to in comparison with other [QueryResult] objects. + */ fun const(n: Int): QueryResult { return QueryResult(n) } @@ -69,25 +73,29 @@ operator fun Expression?.invoke(): QueryResult { class QueryResult(val inner: Any? = null) { operator fun compareTo(o: QueryResult): Int { - if (this.inner is Int && o.inner is Int) { + return if (this.inner is Int && o.inner is Int) { // for now assume that its also an int (which is not always the case of course) - return this.inner as Int - o.inner as Int + this.inner - o.inner } else { - return -1 + -1 } } override fun equals(other: Any?): Boolean { - if (other is Int && this.inner is Int) { - return other == this.inner - } - if (other is QueryResult) { return this.inner?.equals(other.inner) ?: false } return super.equals(other) } + + override fun toString(): String { + return inner.toString() + } + + override fun hashCode(): Int { + return inner?.hashCode() ?: 0 + } } val Expression.size: QueryResult diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index 5213875d38..8353fdd485 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -36,8 +36,8 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue import org.junit.jupiter.api.Test +@ExperimentalGraph class Analysis2Test { - @OptIn(ExperimentalGraph::class) @Test fun testMemcpyTooLargeQuery2() { val config = @@ -58,7 +58,6 @@ class Analysis2Test { assertFalse(ok) } - @OptIn(ExperimentalGraph::class) @Test fun testMemcpyTooLargeQuery() { val config = @@ -79,7 +78,6 @@ class Analysis2Test { assertFalse(ok) } - @OptIn(ExperimentalGraph::class) @Test fun testParameterEqualsConst() { val config = @@ -104,7 +102,6 @@ class Analysis2Test { assertTrue(ok) } - @OptIn(ExperimentalGraph::class) @Test fun testAssign() { val config = diff --git a/cpg-console/src/test/resources/assign.cpp b/cpg-console/src/test/resources/assign.cpp new file mode 100644 index 0000000000..2b4588416a --- /dev/null +++ b/cpg-console/src/test/resources/assign.cpp @@ -0,0 +1,6 @@ +int main() { + int a = 4; + // int a, b = 4; // this is broken, a is missing an initializer + + a = 3; +} \ No newline at end of file diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt index bff48f67e2..819aa35c91 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt @@ -25,6 +25,9 @@ */ package de.fraunhofer.aisec.cpg.graph +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression /** An assignment assigns a certain value (usually an [Expression]) to a certain target. */ @@ -35,11 +38,16 @@ interface Assignment { * the target is only non-null for operations that have a == operator. */ val target: AssignmentTarget? + + /** + * The value expression that is assigned to the target. This is intentionally nullable for the same + * reason as [target]. + */ val value: Expression? } /** - * The target of an assignment. The target is usually either a [Declaration] or a + * The target of an assignment. The target is usually either a [VariableDeclaration] or a * [DeclaredReferenceExpression]. */ interface AssignmentTarget { From 8f9f21ddbf628b295ca3cdd9e7442b76a9fb0846 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 27 Apr 2022 13:42:50 +0200 Subject: [PATCH 15/67] Formatting --- .../src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt index 819aa35c91..b2cab1aa49 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt @@ -40,8 +40,8 @@ interface Assignment { val target: AssignmentTarget? /** - * The value expression that is assigned to the target. This is intentionally nullable for the same - * reason as [target]. + * The value expression that is assigned to the target. This is intentionally nullable for the + * same reason as [target]. */ val value: Expression? } From c16de2712ea9093973bd8ca8ce5eaa1145a96779 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 10 May 2022 17:42:15 +0200 Subject: [PATCH 16/67] Include intermediate steps and results --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 2 +- .../aisec/cpg/analysis/QueryEvaluation.kt | 155 +++++++++++------- .../aisec/cpg/analysis/AnalysisTest.kt | 1 + 3 files changed, 96 insertions(+), 62 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 07e975065b..e4d06c1605 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -61,7 +61,7 @@ class MultiValueEvaluator : ValueEvaluator() { return cannotEvaluate(node, this) } // Add the expression to the current path - node?.let { this.path += it } + this.path += node when (node) { is FieldDeclaration -> { diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt index f235bc37b1..6722d4f0d4 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt @@ -60,6 +60,7 @@ class QueryEvaluation { } abstract class QueryExpression(open val representation: String?) { + open var paths: Pair = Pair(null, null) abstract fun evaluate(input: Map = mutableMapOf()): Any? infix fun `==`(other: QueryExpression): BinaryExpr { @@ -135,10 +136,13 @@ class QueryEvaluation { n.javaClass.interfaces.any { i -> i == kClass } } } - return SubgraphWalker.flattenAST(result).filter { n -> - n.javaClass.simpleName == nodeType || - classNamesOfNode(n.javaClass).any { c -> c == nodeType } - } + val result = + SubgraphWalker.flattenAST(result).filter { n -> + n.javaClass.simpleName == nodeType || + classNamesOfNode(n.javaClass).any { c -> c == nodeType } + } + this.paths = Pair(result, result) + return result } fun classNamesOfNode(jClass: Class<*>): Collection { @@ -191,24 +195,35 @@ class QueryEvaluation { } override fun evaluate(input: Map): Any { + val paths = mutableListOf>() val newInput = input.toMutableMap() + val evaluationResult = variables.evaluate(input) as Collection return if (quantifier == Quantifier.FORALL) { var result = true - for (v in variables.evaluate(input) as Collection) { + for (v in evaluationResult) { newInput[variableName] = v val temp = (inner.evaluate(newInput) as Boolean) + paths.add(Pair(inner.paths, temp)) if (!temp) { - // TODO: Collect potential problems here result = false } } + this.paths = Pair(paths, result) result } else if (quantifier == Quantifier.EXISTS) { - (variables.evaluate(input) as Collection).any { v -> + var result = false + for (v in evaluationResult) { newInput[variableName] = v - inner.evaluate(newInput) as Boolean + val temp = inner.evaluate(newInput) as Boolean + paths.add(Pair(inner.paths, temp)) + if (temp) { + result = true + } } + this.paths = Pair(paths, result) + result } else { + this.paths = Pair("Unknown Quantifier", result) false } } @@ -275,7 +290,9 @@ class QueryEvaluation { } } - return evaluator.evaluate(currentField)!! + val returnValue = evaluator.evaluate(currentField)!! + this.paths = Pair(evaluator.path, returnValue) + return returnValue } private fun readInstanceProperty(instance: Any, propertyName: String): Any { @@ -293,6 +310,7 @@ class QueryEvaluation { } override fun evaluate(input: Map): Any? { + this.paths = Pair(value, value) return value } } @@ -315,30 +333,35 @@ class QueryEvaluation { } override fun evaluate(input: Map): Any? { - return when (operator) { - QueryOp.NOT -> !(inner.evaluate(input) as Boolean) - QueryOp.MAX -> { - val result = inner.evaluate(input) - if (result is Number) { - result.toLong() - } else { - (result as NumberSet).max() + val returnValue = + when (operator) { + QueryOp.NOT -> !(inner.evaluate(input) as Boolean) + QueryOp.MAX -> { + val result = inner.evaluate(input) + if (result is Number) { + result.toLong() + } else { + (result as NumberSet).max() + } } - } - QueryOp.MIN -> { - val result = inner.evaluate(input) - if (result is Number) { - result.toLong() - } else { - (result as NumberSet).min() + QueryOp.MIN -> { + val result = inner.evaluate(input) + if (result is Number) { + result.toLong() + } else { + (result as NumberSet).min() + } } + QueryOp.SIZEOF -> { + if ((inner as? FieldAccessExpr)?.evaluator !is SizeEvaluator) { + (inner as? FieldAccessExpr)?.evaluator = SizeEvaluator() + } + inner.evaluate(input) + } + else -> throw Exception("Unknown operation $operator on expression $inner") } - QueryOp.SIZEOF -> { - (inner as? FieldAccessExpr)?.evaluator = SizeEvaluator() - inner.evaluate(input) - } - else -> throw Exception("Unknown operation $operator on expression $inner") - } + this.paths = Pair(inner.paths, returnValue) + return returnValue } } @@ -363,40 +386,50 @@ class QueryEvaluation { } override fun evaluate(input: Map): Boolean { - return when (operator) { - QueryOp.AND -> lhs?.evaluate(input) as Boolean && rhs?.evaluate(input) as Boolean - QueryOp.OR -> lhs?.evaluate(input) as Boolean || rhs?.evaluate(input) as Boolean - QueryOp.EQ -> lhs?.evaluate(input) == rhs?.evaluate(input) - QueryOp.NE -> { - val lhsVal = lhs?.evaluate(input) - val rhsVal = rhs?.evaluate(input) - if (lhsVal is Collection<*>) { - lhsVal.all { l -> l != rhsVal } - } else { - lhsVal != rhsVal + val returnValue = + when (operator) { + QueryOp.AND -> + lhs?.evaluate(input) as Boolean && rhs?.evaluate(input) as Boolean + QueryOp.OR -> lhs?.evaluate(input) as Boolean || rhs?.evaluate(input) as Boolean + QueryOp.EQ -> lhs?.evaluate(input) == rhs?.evaluate(input) + QueryOp.NE -> { + val lhsVal = lhs?.evaluate(input) + val rhsVal = rhs?.evaluate(input) + if (lhsVal is Collection<*>) { + lhsVal.all { l -> l != rhsVal } + } else { + lhsVal != rhsVal + } } - } - QueryOp.GT -> - (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) > 0 - QueryOp.GE -> - (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) >= 0 - QueryOp.LT -> - (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) < 0 - QueryOp.LE -> - (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) <= 0 - QueryOp.IS -> { - val rhsVal = rhs?.evaluate(input) - if (rhsVal is String) { - lhs?.evaluate(input)?.javaClass?.simpleName == rhsVal - } else { - lhs?.evaluate(input)?.javaClass == rhsVal + QueryOp.GT -> + (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) > + 0 + QueryOp.GE -> + (lhs?.evaluate(input) as Number).compareTo( + rhs?.evaluate(input) as Number + ) >= 0 + QueryOp.LT -> + (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) < + 0 + QueryOp.LE -> + (lhs?.evaluate(input) as Number).compareTo( + rhs?.evaluate(input) as Number + ) <= 0 + QueryOp.IS -> { + val rhsVal = rhs?.evaluate(input) + if (rhsVal is String) { + lhs?.evaluate(input)?.javaClass?.simpleName == rhsVal + } else { + lhs?.evaluate(input)?.javaClass == rhsVal + } } + QueryOp.IMPLIES -> + !(lhs?.evaluate(input) as Boolean) || rhs?.evaluate(input) as Boolean + QueryOp.IN -> lhs?.evaluate(input) in (rhs?.evaluate(input) as Collection<*>) + else -> false } - QueryOp.IMPLIES -> - !(lhs?.evaluate(input) as Boolean) || rhs?.evaluate(input) as Boolean - QueryOp.IN -> lhs?.evaluate(input) in (rhs?.evaluate(input) as Collection<*>) - else -> false - } + this.paths = Pair(Pair(lhs?.paths, rhs?.paths), returnValue) + return returnValue } } } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index a2c5645acd..2b5773f018 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -263,5 +263,6 @@ class AnalysisTest { ) assertFalse(query.evaluate() as Boolean) + println(query.paths) } } From 4236b0845101c1eda0e09c9c1a049e5af600ad36 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 19 May 2022 11:18:43 +0200 Subject: [PATCH 17/67] ++ --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 25 +++++++++++++++++-- .../aisec/cpg/analysis2/Analysis2Test.kt | 23 +++++++++++------ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 849690f947..2b9608a656 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -37,7 +37,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression inline fun TranslationResult.all( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> Boolean -): Boolean { +): Pair> { println("${this.graph.nodes.size} before filter") var nodes = this.graph.nodes.filterIsInstance() @@ -49,7 +49,28 @@ inline fun TranslationResult.all( nodes = nodes.filter(sel) } - return nodes.all(mustSatisfy) + return Pair(nodes.all(mustSatisfy), listOf()) +} + +@ExperimentalGraph +inline fun Node.all( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean +): Pair> { + val children = this.astChildren + + println("${children.size} before filter") + + var nodes = children.filterIsInstance() + + println("${nodes.size} nodes after filter") + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + return Pair(nodes.all(mustSatisfy), listOf()) } fun sizeof(n: Node?): Int { diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index 8353fdd485..1b53ee95b7 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -30,6 +30,8 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.graph.Assignment import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.passes.followNextEOG import de.fraunhofer.aisec.cpg.query.* import java.io.File import kotlin.test.assertFalse @@ -50,12 +52,13 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val ok = + val (ok, fails) = result.all({ it.name == "memcpy" }) { - sizeof(it.arguments[0]) > sizeof(it.arguments[1]) + sizeof(it.arguments[0]) > sizeof(it.arguments[1]) && 1 > 25 } assertFalse(ok) + println(fails) } @Test @@ -70,7 +73,7 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val ok = + val (ok, _) = result.all({ it.name == "memcpy" }) { it.arguments[0].size > it.arguments[1].size } @@ -90,14 +93,20 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - var ok = - result.all({ it.name == "memcpy" }) { - it.arguments[2].value == const(11) + val (ok, _) = + result.all({ it.name == "free" }) { outer -> + val path = + outer.followNextEOG { + (it.end as? DeclaredReferenceExpression)?.refersTo == + (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + } + return@all path?.isEmpty() == true } assertTrue(ok) - ok = result.all({ it.name == "memcpy" }) { it.arguments[2].intValue == 11 } + // (ok, _) = result.all({ it.name == "memcpy" }) { it.arguments[2].intValue + // == 11 } assertTrue(ok) } From 4121fda3d3ff8434a866d1df774cd93dc28dc6ef Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 24 May 2022 14:39:30 +0200 Subject: [PATCH 18/67] Add min and max operators --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 2b9608a656..2599dcf236 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -27,8 +27,11 @@ package de.fraunhofer.aisec.cpg.query import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.analysis.ConcreteNumberSet +import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.evaluate import de.fraunhofer.aisec.cpg.graph.graph import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @@ -38,18 +41,16 @@ inline fun TranslationResult.all( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> Boolean ): Pair> { - println("${this.graph.nodes.size} before filter") - var nodes = this.graph.nodes.filterIsInstance() - println("${nodes.size} nodes after filter") - // filter the nodes according to the selector if (sel != null) { nodes = nodes.filter(sel) } - return Pair(nodes.all(mustSatisfy), listOf()) + val failed = nodes.filterNot(mustSatisfy) + + return Pair(nodes.all(mustSatisfy), failed as List) } @ExperimentalGraph @@ -59,18 +60,16 @@ inline fun Node.all( ): Pair> { val children = this.astChildren - println("${children.size} before filter") - var nodes = children.filterIsInstance() - println("${nodes.size} nodes after filter") - // filter the nodes according to the selector if (sel != null) { nodes = nodes.filter(sel) } - return Pair(nodes.all(mustSatisfy), listOf()) + val failed = nodes.filterNot(mustSatisfy) + + return Pair(nodes.all(mustSatisfy), failed as List) } fun sizeof(n: Node?): Int { @@ -80,6 +79,24 @@ fun sizeof(n: Node?): Int { return eval.evaluate(n) as? Int ?: -1 } +fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { + val evalRes = eval.evaluate(n) + if (evalRes is Number) { + return evalRes.toLong() + } + // TODO: Extend this when we have other evaluators. + return (evalRes as? ConcreteNumberSet)?.min() ?: -1 +} + +fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { + val evalRes = eval.evaluate(n) + if (evalRes is Number) { + return evalRes.toLong() + } + // TODO: Extend this when we have other evaluators. + return (evalRes as? ConcreteNumberSet)?.max() ?: -1 +} + /** * This is a small wrapper to create a [QueryResult] containing a constant value, so that it can be * used to in comparison with other [QueryResult] objects. From a0563c2b2b71adb264866702dceca6e15fe69890 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 24 May 2022 14:39:50 +0200 Subject: [PATCH 19/67] Fix compiler error, add tests --- .../aisec/cpg/analysis2/Analysis2Test.kt | 118 ++++++++++++++++-- cpg-console/src/test/resources/vulnerable.cpp | 2 + 2 files changed, 113 insertions(+), 7 deletions(-) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index 1b53ee95b7..006b90d073 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -29,8 +29,12 @@ import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.graph.Assignment +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import de.fraunhofer.aisec.cpg.passes.followNextEOG import de.fraunhofer.aisec.cpg.query.* import java.io.File @@ -54,7 +58,7 @@ class Analysis2Test { val (ok, fails) = result.all({ it.name == "memcpy" }) { - sizeof(it.arguments[0]) > sizeof(it.arguments[1]) && 1 > 25 + sizeof(it.arguments[0]) > sizeof(it.arguments[1]) } assertFalse(ok) @@ -82,7 +86,7 @@ class Analysis2Test { } @Test - fun testParameterEqualsConst() { + fun testDoubleFree() { val config = TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/vulnerable.cpp")) @@ -93,7 +97,7 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, _) = + val (ok, fails) = result.all({ it.name == "free" }) { outer -> val path = outer.followNextEOG { @@ -103,10 +107,24 @@ class Analysis2Test { return@all path?.isEmpty() == true } - assertTrue(ok) + assertFalse(ok) + println(fails) + } + + @Test + fun testParameterEqualsConst() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() - // (ok, _) = result.all({ it.name == "memcpy" }) { it.arguments[2].intValue - // == 11 } + val (ok, _) = + result.all({ it.name == "memcpy" }) { it.arguments[2].intValue == 11 } assertTrue(ok) } @@ -123,8 +141,94 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val ok = result.all { it.value() < const(5) } + val (ok, _) = result.all { it.value() < const(5) } + + assertTrue(ok) + } + + @Test + fun testOutOfBoundsQuery() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/array.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val (ok, fails) = + result.all { + max(it.subscriptExpression) < + min( + (((it.arrayExpression as DeclaredReferenceExpression).refersTo as + VariableDeclaration) + .initializer as + ArrayCreationExpression) + .dimensions[0] + ) && min(it.subscriptExpression) >= 0 + } + assertFalse(ok) + println(fails) + } + + @Test + fun testOutOfBoundsQuery2() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/array2.cpp")) + .defaultPasses() + .defaultLanguages() + .registerPass(EdgeCachePass()) + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val (ok, fails) = + result.all { + max(it.subscriptExpression) < + min( + (((it.arrayExpression as DeclaredReferenceExpression).refersTo as + VariableDeclaration) + .initializer as + ArrayCreationExpression) + .dimensions[0] + ) && min(it.subscriptExpression) >= 0 + } + assertFalse(ok) + println(fails) + } + @Test + fun testOutOfBoundsQueryCorrect() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/array_correct.cpp")) + .defaultPasses() + .defaultLanguages() + .registerPass(EdgeCachePass()) + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val (ok, fails) = + result.all { + val max_sub = max(it.subscriptExpression) + val min_dim = + min( + (((it.arrayExpression as DeclaredReferenceExpression).refersTo as + VariableDeclaration) + .initializer as + ArrayCreationExpression) + .dimensions[0] + ) + val min_sub = min(it.subscriptExpression) + return@all max_sub < min_dim && min_sub >= 0 + } assertTrue(ok) + println(fails) } } diff --git a/cpg-console/src/test/resources/vulnerable.cpp b/cpg-console/src/test/resources/vulnerable.cpp index 5889ba5bc3..01eecb057b 100644 --- a/cpg-console/src/test/resources/vulnerable.cpp +++ b/cpg-console/src/test/resources/vulnerable.cpp @@ -2,4 +2,6 @@ int main() { char array[6] = "hello"; memcpy(array, "Hello world", 11); printf(array); + free(array); + free(array); } \ No newline at end of file From a8132cd4757ccfca8c6f7ee42cc03d1760263420 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 24 May 2022 15:13:38 +0200 Subject: [PATCH 20/67] Performance, doc --- .../aisec/cpg/analysis/SizeEvaluator.kt | 4 +++ .../de/fraunhofer/aisec/cpg/query/Query.kt | 25 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt index 3b888cf16e..32659305b9 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt @@ -32,6 +32,10 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import org.slf4j.Logger import org.slf4j.LoggerFactory +/** + * Simple evaluation of the size of an object. Right now, it can only support a statically given + * size of arrays and strings. + */ class SizeEvaluator : ValueEvaluator() { override val log: Logger get() = LoggerFactory.getLogger(MultiValueEvaluator::class.java) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 2599dcf236..436b02220d 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -50,7 +50,7 @@ inline fun TranslationResult.all( val failed = nodes.filterNot(mustSatisfy) - return Pair(nodes.all(mustSatisfy), failed as List) + return Pair(failed.isEmpty(), failed as List) } @ExperimentalGraph @@ -69,9 +69,10 @@ inline fun Node.all( val failed = nodes.filterNot(mustSatisfy) - return Pair(nodes.all(mustSatisfy), failed as List) + return Pair(failed.isEmpty(), failed as List) } +/** Evaluates the size of a node. The implementation is very very basic! */ fun sizeof(n: Node?): Int { val eval = SizeEvaluator() // TODO(oxisto): This cast could potentially go wrong, but if its not an int, its not really a @@ -79,6 +80,11 @@ fun sizeof(n: Node?): Int { return eval.evaluate(n) as? Int ?: -1 } +/** + * Retrieves the minimal value of the node. + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { val evalRes = eval.evaluate(n) if (evalRes is Number) { @@ -88,6 +94,11 @@ fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { return (evalRes as? ConcreteNumberSet)?.min() ?: -1 } +/** + * Retrieves the minimal value of the node. + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { val evalRes = eval.evaluate(n) if (evalRes is Number) { @@ -141,6 +152,16 @@ val Expression.size: QueryResult return QueryResult(sizeof(this)) } +val Expression.min: QueryResult + get() { + return QueryResult(min(this)) + } + +val Expression.max: QueryResult + get() { + return QueryResult(max(this)) + } + val Expression.value: QueryResult get() { return QueryResult(evaluate()) From 4c57e25bb52e572201a9cc710bdd09751817a84a Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 10 Jun 2022 14:07:53 +0200 Subject: [PATCH 21/67] Additional testcase and second min/max function --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 40 +++++++++++++++++++ .../aisec/cpg/analysis2/Analysis2Test.kt | 28 +++++++++++++ cpg-console/src/test/resources/array3.cpp | 12 ++++++ .../fraunhofer/aisec/cpg/graph/Extensions.kt | 22 ++++++++++ 4 files changed, 102 insertions(+) create mode 100644 cpg-console/src/test/resources/array3.cpp diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 436b02220d..56a61d07ca 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -94,6 +94,46 @@ fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { return (evalRes as? ConcreteNumberSet)?.min() ?: -1 } +/** + * Retrieves the minimal value of the nodes in the list. + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ +fun min(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { + var result = Long.MAX_VALUE + if (n == null) return result + + for (node in n) { + val evalRes = eval.evaluate(node) + if (evalRes is Number && evalRes.toLong() < result) { + result = evalRes.toLong() + } else if (evalRes is ConcreteNumberSet && evalRes.min() < result) { + result = evalRes.min() + } + } + return result +} + +/** + * Retrieves the maximal value of the nodes in the list. + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ +fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { + var result = Long.MIN_VALUE + if (n == null) return result + + for (node in n) { + val evalRes = eval.evaluate(node) + if (evalRes is Number && evalRes.toLong() > result) { + result = evalRes.toLong() + } else if (evalRes is ConcreteNumberSet && evalRes.max() > result) { + result = evalRes.max() + } + } + return result +} + /** * Retrieves the minimal value of the node. * diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index 006b90d073..f4abb5267e 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.graph.Assignment import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.followPrevDFG import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression @@ -201,6 +202,33 @@ class Analysis2Test { println(fails) } + @Test + fun testOutOfBoundsQuery3() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/array3.cpp")) + .defaultPasses() + .defaultLanguages() + .registerPass(EdgeCachePass()) + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val (ok, fails) = + result.all { + max(it.subscriptExpression) < + min( + ((it.arrayExpression as DeclaredReferenceExpression).refersTo as + VariableDeclaration) + .followPrevDFG { node -> node is ArrayCreationExpression } + .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } + ) && min(it.subscriptExpression) >= 0 + } + assertFalse(ok) + println(fails) + } + @Test fun testOutOfBoundsQueryCorrect() { val config = diff --git a/cpg-console/src/test/resources/array3.cpp b/cpg-console/src/test/resources/array3.cpp new file mode 100644 index 0000000000..df8cae0295 --- /dev/null +++ b/cpg-console/src/test/resources/array3.cpp @@ -0,0 +1,12 @@ +int main() { + char* c; + if(5 > 4) + c = new char[4]; + else + c = new char[5]; + int a = 0; + for(int i = 0; i <= 4; i++) { + a = a + c[i]; + } + return a; +} diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 4cf7574c0d..bbc3eea45b 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -136,6 +136,28 @@ class StatementNotFound : Exception() class DeclarationNotFound(message: String) : Exception(message) +fun Node.followPrevDFG(predicate: (Node) -> Boolean): List { + val result = mutableListOf() + val alreadySeen = mutableListOf() + val worklist = mutableListOf() + worklist.add(this) + + while (worklist.isNotEmpty()) { + val currentNode = worklist.removeFirst() + alreadySeen.add(currentNode) + for (prev in currentNode.prevDFG) { + if (predicate(prev)) { + result.add(prev) + } + if (!alreadySeen.contains(prev)) { + worklist.add(prev) + } + } + } + + return result +} + fun Node.followPrevEOG(predicate: (PropertyEdge<*>) -> Boolean): List>? { val path = mutableListOf>() From 17686363fba16f7ec0de697a9adaf09c4666f127 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 10 Jun 2022 14:28:32 +0200 Subject: [PATCH 22/67] Try to update MultiValueEvaluator --- .../de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index e4d06c1605..5f2e36ab92 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -241,7 +241,9 @@ class MultiValueEvaluator : ValueEvaluator() { var loopVar = evaluateInternal(loop.initializerStatement.declarations.first(), depth) as? Number + if (loopVar == null) return listOf() + val cond = loop.condition as BinaryOperator val result = mutableListOf() var lhs = From 2b94fbc3f007a99911039508e8254a218a5b54c2 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 10 Jun 2022 14:29:55 +0200 Subject: [PATCH 23/67] Updated formatting --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 8 +++--- .../aisec/cpg/analysis2/Analysis2Test.kt | 25 ++++++++----------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 5f2e36ab92..a816008062 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -235,8 +235,8 @@ class MultiValueEvaluator : ValueEvaluator() { depth: Int ): List { val loop = - expr.prevDFG.firstOrNull { e -> e.astParent is ForStatement }?.astParent as? - ForStatement + expr.prevDFG.firstOrNull { e -> e.astParent is ForStatement }?.astParent + as? ForStatement if (loop == null || loop.condition !is BinaryOperator) return listOf() var loopVar = @@ -298,8 +298,8 @@ class MultiValueEvaluator : ValueEvaluator() { loopOp.input }, loopOp - ) as? - Number + ) + as? Number } else -> { null diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index f4abb5267e..928d6f0da1 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -163,10 +163,9 @@ class Analysis2Test { result.all { max(it.subscriptExpression) < min( - (((it.arrayExpression as DeclaredReferenceExpression).refersTo as - VariableDeclaration) - .initializer as - ArrayCreationExpression) + (((it.arrayExpression as DeclaredReferenceExpression).refersTo + as VariableDeclaration) + .initializer as ArrayCreationExpression) .dimensions[0] ) && min(it.subscriptExpression) >= 0 } @@ -191,10 +190,9 @@ class Analysis2Test { result.all { max(it.subscriptExpression) < min( - (((it.arrayExpression as DeclaredReferenceExpression).refersTo as - VariableDeclaration) - .initializer as - ArrayCreationExpression) + (((it.arrayExpression as DeclaredReferenceExpression).refersTo + as VariableDeclaration) + .initializer as ArrayCreationExpression) .dimensions[0] ) && min(it.subscriptExpression) >= 0 } @@ -219,8 +217,8 @@ class Analysis2Test { result.all { max(it.subscriptExpression) < min( - ((it.arrayExpression as DeclaredReferenceExpression).refersTo as - VariableDeclaration) + ((it.arrayExpression as DeclaredReferenceExpression).refersTo + as VariableDeclaration) .followPrevDFG { node -> node is ArrayCreationExpression } .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } ) && min(it.subscriptExpression) >= 0 @@ -247,10 +245,9 @@ class Analysis2Test { val max_sub = max(it.subscriptExpression) val min_dim = min( - (((it.arrayExpression as DeclaredReferenceExpression).refersTo as - VariableDeclaration) - .initializer as - ArrayCreationExpression) + (((it.arrayExpression as DeclaredReferenceExpression).refersTo + as VariableDeclaration) + .initializer as ArrayCreationExpression) .dimensions[0] ) val min_sub = min(it.subscriptExpression) From 2cf68d40d6ade36d4ac591c116e9324b8e0b1587 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 10 Jun 2022 14:48:12 +0200 Subject: [PATCH 24/67] Fix --- .../kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt | 4 ++-- .../src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index 928d6f0da1..dd112c4e62 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -30,7 +30,7 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.graph.Assignment import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.followPrevDFG +import de.fraunhofer.aisec.cpg.graph.followPrevDFGEdgesUntilHit import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression @@ -219,7 +219,7 @@ class Analysis2Test { min( ((it.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) - .followPrevDFG { node -> node is ArrayCreationExpression } + .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } ) && min(it.subscriptExpression) >= 0 } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 09be798187..b48cb778d0 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -136,7 +136,7 @@ class StatementNotFound : Exception() class DeclarationNotFound(message: String) : Exception(message) -fun Node.followPrevDFG(predicate: (Node) -> Boolean): List { +fun Node.followPrevDFGEdgesUntilHit(predicate: (Node) -> Boolean): List { val result = mutableListOf() val alreadySeen = mutableListOf() val worklist = mutableListOf() From ff3773c166e23e9a26463e6908f9d4de0cafc70b Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 10 Jun 2022 15:21:54 +0200 Subject: [PATCH 25/67] Replace ConcreteNumberSet with NumberSet, Fix typo --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 16 +++++++++------- .../statements/expressions/BinaryOperator.java | 6 +++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 56a61d07ca..6e3b239ffd 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -27,8 +27,8 @@ package de.fraunhofer.aisec.cpg.query import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.analysis.ConcreteNumberSet import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator +import de.fraunhofer.aisec.cpg.analysis.NumberSet import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.ValueEvaluator @@ -90,8 +90,8 @@ fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { if (evalRes is Number) { return evalRes.toLong() } - // TODO: Extend this when we have other evaluators. - return (evalRes as? ConcreteNumberSet)?.min() ?: -1 + // Extend this when we have other evaluators. + return (evalRes as? NumberSet)?.min() ?: -1 } /** @@ -107,9 +107,10 @@ fun min(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { val evalRes = eval.evaluate(node) if (evalRes is Number && evalRes.toLong() < result) { result = evalRes.toLong() - } else if (evalRes is ConcreteNumberSet && evalRes.min() < result) { + } else if (evalRes is NumberSet && evalRes.min() < result) { result = evalRes.min() } + // Extend this when we have other evaluators. } return result } @@ -127,9 +128,10 @@ fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { val evalRes = eval.evaluate(node) if (evalRes is Number && evalRes.toLong() > result) { result = evalRes.toLong() - } else if (evalRes is ConcreteNumberSet && evalRes.max() > result) { + } else if (evalRes is NumberSet && evalRes.max() > result) { result = evalRes.max() } + // Extend this when we have other evaluators. } return result } @@ -144,8 +146,8 @@ fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { if (evalRes is Number) { return evalRes.toLong() } - // TODO: Extend this when we have other evaluators. - return (evalRes as? ConcreteNumberSet)?.max() ?: -1 + // Extend this when we have other evaluators. + return (evalRes as? NumberSet)?.max() ?: -1 } /** diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.java b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.java index 6466d7007d..c560c4fdd0 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.java +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.java @@ -261,8 +261,8 @@ public Expression getValue() { public boolean isAssignment() { // TODO(oxisto): We need to discuss, if the other operators are also assignments and if we // really want them - return this.operatorCode.equals( - "==") /*||this.operatorCode.equals("+=") ||this.operatorCode.equals("-=") - ||this.operatorCode.equals("/=") ||this.operatorCode.equals("*=")*/; + return this.operatorCode.equals("=") + /*||this.operatorCode.equals("+=") ||this.operatorCode.equals("-=") + ||this.operatorCode.equals("/=") ||this.operatorCode.equals("*=")*/ ; } } From 91e14c6cb4f85030ba0df65f139ac51787762864 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 21 Jul 2022 11:26:42 +0200 Subject: [PATCH 26/67] Fix formatting --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index a816008062..c60145851a 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -134,7 +134,8 @@ class MultiValueEvaluator : ValueEvaluator() { if (thenResult is List<*>) result.addAll(thenResult) else result.add(thenResult) } result - } else if (lhs is List<*> && rhs is List<*> && lhs.firstOrNull() == rhs.firstOrNull() || + } else if ( + lhs is List<*> && rhs is List<*> && lhs.firstOrNull() == rhs.firstOrNull() || lhs is List<*> && lhs.firstOrNull() == rhs || rhs is List<*> && rhs.firstOrNull() == lhs || lhs == rhs @@ -196,7 +197,8 @@ class MultiValueEvaluator : ValueEvaluator() { // We are only interested in expressions val expressions = prevDFG.filterIsInstance() - if (expressions.size == 2 && + if ( + expressions.size == 2 && expressions.all { e -> (e.astParent?.astParent as? ForStatement)?.initializerStatement == e || (e.astParent as? ForStatement)?.iterationStatement == e @@ -271,7 +273,8 @@ class MultiValueEvaluator : ValueEvaluator() { when (loopOp) { is BinaryOperator -> { val opLhs = - if ((loopOp.lhs as? DeclaredReferenceExpression)?.refersTo == + if ( + (loopOp.lhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo ) { loopVar @@ -279,7 +282,8 @@ class MultiValueEvaluator : ValueEvaluator() { loopOp.lhs } val opRhs = - if ((loopOp.rhs as? DeclaredReferenceExpression)?.refersTo == + if ( + (loopOp.rhs as? DeclaredReferenceExpression)?.refersTo == expr.refersTo ) { loopVar @@ -290,7 +294,8 @@ class MultiValueEvaluator : ValueEvaluator() { } is UnaryOperator -> { computeUnaryOpEffect( - if ((loopOp.input as? DeclaredReferenceExpression)?.refersTo == + if ( + (loopOp.input as? DeclaredReferenceExpression)?.refersTo == expr.refersTo ) { loopVar!! From be1a6ac9b43b92a454754b98daf5828a0180382c Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 21 Jul 2022 14:14:52 +0200 Subject: [PATCH 27/67] Fix errors in test --- .../kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index fc98e61d6e..d6a5b7effe 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -37,7 +37,9 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import java.io.File import kotlin.test.Test +import kotlin.test.assertFalse import kotlin.test.assertNotNull +import kotlin.test.assertTrue class AnalysisTest { @Test From 93ace6322cc7f2d0575843297dc8ef695c689eab Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 21 Jul 2022 16:52:18 +0200 Subject: [PATCH 28/67] Fix broken tests due to changed edge names --- .../de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index d6a5b7effe..e267d01ca3 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -232,8 +232,8 @@ class AnalysisTest { val query = forall( "n: CallExpression", - ((field("n.invokes.name") `==` const("memcpy")) implies - (sizeof(field("n.arguments[0]")) ge sizeof(field("n.arguments[1]")))), + ((field("n.invokesRelationship.name") `==` const("memcpy")) implies + (sizeof(field("n.argumentsEdges[0]")) ge sizeof(field("n.argumentsEdges[1]")))), result ) @@ -257,8 +257,8 @@ class AnalysisTest { val query = forall( "n: CallExpression", - ((field("n.invokes.name") eq const("memcpy")) implies - (sizeof(field("n.arguments[0]")) ge sizeof(field("n.arguments[1]")))), + ((field("n.invokesRelationship.name") eq const("memcpy")) implies + (sizeof(field("n.argumentsEdges")) ge sizeof(field("n.argumentsEdges[1]")))), result ) From 2d367aa569d551ce22fa6676e02cd5f27b45ffaf Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 22 Jul 2022 17:56:32 +0200 Subject: [PATCH 29/67] Keep track of intermediate steps of evaluation --- .../aisec/cpg/analysis/QueryBuilder.kt | 19 +++ .../de/fraunhofer/aisec/cpg/query/Query.kt | 132 ++++++++---------- .../fraunhofer/aisec/cpg/query/QueryTree.kt | 127 +++++++++++++++++ .../aisec/cpg/analysis/AnalysisTest.kt | 6 + .../aisec/cpg/analysis2/Analysis2Test.kt | 75 +++++----- 5 files changed, 249 insertions(+), 110 deletions(-) create mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt index c17b4385ef..7edf252a6e 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.ValueEvaluator class QueryBuilder { @@ -242,6 +243,12 @@ fun field(str: String): QueryEvaluation.FieldAccessExpr { return res } +fun field(node: Node): QueryEvaluation.FieldAccessExpr { + // TODO!! + val res = QueryEvaluation.FieldAccessExpr() + return res +} + fun forall( str: String, inner: QueryEvaluation.QueryExpression, @@ -255,6 +262,18 @@ fun forall( return res } +inline fun TranslationResult.forall2( + noinline mustSatisfy: (T) -> Unit +): QueryEvaluation.QuantifierExpr { + val res = QueryEvaluation.QuantifierExpr() + res.result = this + res.quantifier = QueryEvaluation.Quantifier.FORALL + res.str = T::class.simpleName + // res.inner = inner + // TODO!! + return res +} + // // and | or | eq | ne | gt | lt | ge | le | implies | is | in diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 6e3b239ffd..f72405504d 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -30,17 +30,15 @@ import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator import de.fraunhofer.aisec.cpg.analysis.NumberSet import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator -import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.ValueEvaluator -import de.fraunhofer.aisec.cpg.graph.evaluate -import de.fraunhofer.aisec.cpg.graph.graph +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @ExperimentalGraph inline fun TranslationResult.all( noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> Boolean -): Pair> { + noinline mustSatisfy: (T) -> QueryTree +): QueryTree { var nodes = this.graph.nodes.filterIsInstance() // filter the nodes according to the selector @@ -48,16 +46,15 @@ inline fun TranslationResult.all( nodes = nodes.filter(sel) } - val failed = nodes.filterNot(mustSatisfy) - - return Pair(failed.isEmpty(), failed as List) + val queryChildren = nodes.map(mustSatisfy) + return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) } @ExperimentalGraph inline fun Node.all( noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> Boolean -): Pair> { + noinline mustSatisfy: (T) -> QueryTree +): QueryTree { val children = this.astChildren var nodes = children.filterIsInstance() @@ -67,17 +64,36 @@ inline fun Node.all( nodes = nodes.filter(sel) } - val failed = nodes.filterNot(mustSatisfy) - - return Pair(failed.isEmpty(), failed as List) + val queryChildren = nodes.map(mustSatisfy) + return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) } /** Evaluates the size of a node. The implementation is very very basic! */ -fun sizeof(n: Node?): Int { +fun sizeof(n: Node?): QueryTree { val eval = SizeEvaluator() // TODO(oxisto): This cast could potentially go wrong, but if its not an int, its not really a // size - return eval.evaluate(n) as? Int ?: -1 + return QueryTree(eval.evaluate(n) as? Int ?: -1, mutableListOf(QueryTree(n))) +} + +/* +forall (n: CallExpression): n.invokes.name == "memcpy" => |sizeof(n.arguments[0])| < |sizeof(n.arguments[1])| + + False + -------------------- + 5 < 3 (=> False) + ---------------------------------------------------------------- + sizeof( var1 ) (=> 5) < sizeof( var2 ) (=> 3) + ------------------------------------------------ + n.arguments[0] (=> var1) n.arguments[1] (=> var2) + */ + +@OptIn(ExperimentalGraph::class) +val TranslationResult.calls: List + get() = this.graph.nodes.filterIsInstance() + +fun List.name(name: String): List { + return this.filter { n -> n.invokes.any { it.name == name } } } /** @@ -85,13 +101,13 @@ fun sizeof(n: Node?): Int { * * @eval can be used to specify the evaluator but this method has to interpret the result correctly! */ -fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { +fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { val evalRes = eval.evaluate(n) if (evalRes is Number) { - return evalRes.toLong() + return QueryTree(evalRes, mutableListOf(QueryTree(n))) } // Extend this when we have other evaluators. - return (evalRes as? NumberSet)?.min() ?: -1 + return QueryTree((evalRes as? NumberSet)?.min() ?: -1, mutableListOf(QueryTree(n))) } /** @@ -99,9 +115,9 @@ fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { * * @eval can be used to specify the evaluator but this method has to interpret the result correctly! */ -fun min(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { +fun min(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { var result = Long.MAX_VALUE - if (n == null) return result + if (n == null) return QueryTree(result, mutableListOf(QueryTree(null))) for (node in n) { val evalRes = eval.evaluate(node) @@ -112,7 +128,7 @@ fun min(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { } // Extend this when we have other evaluators. } - return result + return QueryTree(result, mutableListOf(QueryTree(n))) } /** @@ -120,9 +136,9 @@ fun min(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { * * @eval can be used to specify the evaluator but this method has to interpret the result correctly! */ -fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { +fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { var result = Long.MIN_VALUE - if (n == null) return result + if (n == null) return QueryTree(result, mutableListOf(QueryTree(null))) for (node in n) { val evalRes = eval.evaluate(node) @@ -133,7 +149,7 @@ fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { } // Extend this when we have other evaluators. } - return result + return QueryTree(result, mutableListOf(QueryTree(n))) } /** @@ -141,75 +157,41 @@ fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Long { * * @eval can be used to specify the evaluator but this method has to interpret the result correctly! */ -fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Long { +fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { val evalRes = eval.evaluate(n) if (evalRes is Number) { - return evalRes.toLong() + return QueryTree(evalRes, mutableListOf(QueryTree(n))) } // Extend this when we have other evaluators. - return (evalRes as? NumberSet)?.max() ?: -1 -} - -/** - * This is a small wrapper to create a [QueryResult] containing a constant value, so that it can be - * used to in comparison with other [QueryResult] objects. - */ -fun const(n: Int): QueryResult { - return QueryResult(n) + return QueryTree((evalRes as? NumberSet)?.max() ?: -1, mutableListOf(QueryTree(n))) } -operator fun Expression?.invoke(): QueryResult { - return QueryResult(this?.evaluate()) -} - -class QueryResult(val inner: Any? = null) { - operator fun compareTo(o: QueryResult): Int { - return if (this.inner is Int && o.inner is Int) { - // for now assume that its also an int (which is not always the case of course) - this.inner - o.inner - } else { - -1 - } - } - - override fun equals(other: Any?): Boolean { - if (other is QueryResult) { - return this.inner?.equals(other.inner) ?: false - } - - return super.equals(other) - } - - override fun toString(): String { - return inner.toString() - } - - override fun hashCode(): Int { - return inner?.hashCode() ?: 0 - } +operator fun Expression?.invoke(): QueryTree { + return QueryTree(this?.evaluate(), mutableListOf(QueryTree(this))) } -val Expression.size: QueryResult +val Expression.size: QueryTree get() { - return QueryResult(sizeof(this)) + return sizeof(this) } -val Expression.min: QueryResult +val Expression.min: QueryTree get() { - return QueryResult(min(this)) + return min(this) } -val Expression.max: QueryResult +val Expression.max: QueryTree get() { - return QueryResult(max(this)) + return max(this) } -val Expression.value: QueryResult +val Expression.value: QueryTree get() { - return QueryResult(evaluate()) + return QueryTree(evaluate(), mutableListOf(QueryTree(this))) } -val Expression.intValue: Int? +val Expression.intValue: QueryTree? get() { - return evaluate() as? Int + val evalRes = evaluate() as? Int ?: return null + return QueryTree(evalRes, mutableListOf(QueryTree(this))) } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt new file mode 100644 index 0000000000..a3e5b0cf5d --- /dev/null +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2022, 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.query + +import de.fraunhofer.aisec.cpg.graph.compareTo + +open class QueryTree( + open var value: T, + open val children: MutableList> = mutableListOf(), + open val stringRepresentation: String = "" +) { + fun printNicely(depth: Int = 0): String { + // TODO: Make this output awesome + var res = + " ".repeat(depth) + + "$stringRepresentation (==> $value)\n" + + "--------".repeat(depth) + + "\n" + children.forEach { res += it.toString() + "\n" + "--------".repeat(depth + 1) + "\n" } + return res + } + + infix fun eq(other: QueryTree): QueryTree { + val result = this.value == other.value + return QueryTree(result, mutableListOf(this, other), "$this == $other") + } + + infix fun neq(other: QueryTree): QueryTree { + val result = this.value != other.value + return QueryTree(result, mutableListOf(this, other), "$this == $other") + } + + override fun hashCode(): Int { + return value?.hashCode() ?: 0 + } + + override fun equals(other: Any?): Boolean { + if (other is QueryTree<*>) { + return this.value?.equals(other.value) ?: false + } + + return super.equals(other) + } +} + +infix fun QueryTree.and(other: QueryTree): QueryTree { + return QueryTree( + this.value && other.value, + mutableListOf(this, other), + stringRepresentation = "${this.value} && ${other.value}" + ) +} + +infix fun QueryTree.or(other: QueryTree): QueryTree { + return QueryTree( + this.value || other.value, + mutableListOf(this, other), + stringRepresentation = "${this.value} || ${other.value}" + ) +} + +infix fun QueryTree.xor(other: QueryTree): QueryTree { + return QueryTree( + this.value xor other.value, + mutableListOf(this, other), + stringRepresentation = "${this.value} xor ${other.value}" + ) +} + +infix fun QueryTree.implies(other: QueryTree): QueryTree { + return QueryTree( + !this.value || other.value, + mutableListOf(this, other), + stringRepresentation = "${this.value} => ${other.value}" + ) +} + +infix fun QueryTree.gt(other: QueryTree): QueryTree { + val result = this.value.compareTo(other.value) > 0 + return QueryTree(result, mutableListOf(this, other), "${this.value} > ${other.value}") +} + +infix fun QueryTree.ge(other: QueryTree): QueryTree { + val result = this.value.compareTo(other.value) >= 0 + return QueryTree(result, mutableListOf(this, other), "${this.value} >= ${other.value}") +} + +infix fun QueryTree.lt(other: QueryTree): QueryTree { + val result = this.value.compareTo(other.value) < 0 + return QueryTree(result, mutableListOf(this, other), "${this.value} < ${other.value}") +} + +infix fun QueryTree.le(other: QueryTree): QueryTree { + val result = this.value.compareTo(other.value) <= 0 + return QueryTree(result, mutableListOf(this, other), "${this.value} <= ${other.value}") +} + +/** + * This is a small wrapper to create a [QueryTreeHolder] containing a constant value, so that it can + * be used to in comparison with other [QueryTreeHolder] objects. + */ +fun const(n: T): QueryTree { + return QueryTree(n, stringRepresentation = "$n") +} diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index e267d01ca3..dcf5453fb4 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.analysis +import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.TranslationResult @@ -240,6 +241,7 @@ class AnalysisTest { assertFalse(query.evaluate() as Boolean) } + @OptIn(ExperimentalGraph::class) @Test fun testMemcpyTooLargeQuery2() { val config = @@ -262,6 +264,10 @@ class AnalysisTest { result ) + result.forall2 { n: CallExpression -> + (field(n.invokes[0].name) eq const("memcpy")) implies + (sizeof(field(n.arguments[0])) ge sizeof(field(n.arguments[1]))) + } assertFalse(query.evaluate() as Boolean) println(query.paths) } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index dd112c4e62..6452e1252b 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -57,13 +57,15 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, fails) = + val queryTreeResult = result.all({ it.name == "memcpy" }) { - sizeof(it.arguments[0]) > sizeof(it.arguments[1]) + sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) } - assertFalse(ok) - println(fails) + assertFalse(queryTreeResult.value) + println(queryTreeResult.printNicely()) + + // result.calls.name("memcpy").all { n -> sizeof(n.arguments[0]) >= sizeof(n.arguments[1]) } } @Test @@ -78,12 +80,12 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, _) = + val queryTreeResult = result.all({ it.name == "memcpy" }) { - it.arguments[0].size > it.arguments[1].size + it.arguments[0].size gt it.arguments[1].size } - assertFalse(ok) + assertFalse(queryTreeResult.value) } @Test @@ -98,18 +100,18 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, fails) = + val queryTreeResult = result.all({ it.name == "free" }) { outer -> val path = outer.followNextEOG { (it.end as? DeclaredReferenceExpression)?.refersTo == (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo } - return@all path?.isEmpty() == true + return@all const(path?.isEmpty()) eq const(true) } - assertFalse(ok) - println(fails) + assertFalse(queryTreeResult.value) + println(queryTreeResult.printNicely()) } @Test @@ -124,10 +126,12 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, _) = - result.all({ it.name == "memcpy" }) { it.arguments[2].intValue == 11 } + val queryTreeResult = + result.all({ it.name == "memcpy" }) { + it.arguments[2].intValue!! eq const(11) + } - assertTrue(ok) + assertTrue(queryTreeResult.value) } @Test @@ -142,9 +146,10 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, _) = result.all { it.value() < const(5) } + val queryTreeResult = + result.all { it.value.invoke() as QueryTree lt const(5) } - assertTrue(ok) + assertTrue(queryTreeResult.value) } @Test @@ -159,18 +164,18 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, fails) = + val queryTreeResult = result.all { - max(it.subscriptExpression) < + (max(it.subscriptExpression) lt min( (((it.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) .initializer as ArrayCreationExpression) .dimensions[0] - ) && min(it.subscriptExpression) >= 0 + )) and (min(it.subscriptExpression) ge const(0)) } - assertFalse(ok) - println(fails) + assertFalse(queryTreeResult.value) + println(queryTreeResult.printNicely()) } @Test @@ -186,18 +191,18 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, fails) = + val queryTreeResult = result.all { - max(it.subscriptExpression) < + (max(it.subscriptExpression) lt min( (((it.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) .initializer as ArrayCreationExpression) .dimensions[0] - ) && min(it.subscriptExpression) >= 0 + )) and (min(it.subscriptExpression) ge const(0)) } - assertFalse(ok) - println(fails) + assertFalse(queryTreeResult.value) + println(queryTreeResult.printNicely()) } @Test @@ -213,18 +218,18 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, fails) = + val queryTreeResult = result.all { - max(it.subscriptExpression) < + (max(it.subscriptExpression) lt min( ((it.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } - ) && min(it.subscriptExpression) >= 0 + )) and (min(it.subscriptExpression) ge const(0)) } - assertFalse(ok) - println(fails) + assertFalse(queryTreeResult.value) + println(queryTreeResult) } @Test @@ -240,7 +245,7 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val (ok, fails) = + val queryTreeResult = result.all { val max_sub = max(it.subscriptExpression) val min_dim = @@ -251,9 +256,9 @@ class Analysis2Test { .dimensions[0] ) val min_sub = min(it.subscriptExpression) - return@all max_sub < min_dim && min_sub >= 0 + return@all (max_sub lt min_dim) and (min_sub ge const(0)) } - assertTrue(ok) - println(fails) + assertTrue(queryTreeResult.value) + println(queryTreeResult) } } From dc64a6dba7dd60d9ab866cc44f045007f2eb8675 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 25 Jul 2022 07:25:48 +0200 Subject: [PATCH 30/67] Reverte incorrect changes --- .../de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt | 12 ------------ .../de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt | 4 ---- 2 files changed, 16 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt index 7edf252a6e..e3a20e0b88 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt @@ -262,18 +262,6 @@ fun forall( return res } -inline fun TranslationResult.forall2( - noinline mustSatisfy: (T) -> Unit -): QueryEvaluation.QuantifierExpr { - val res = QueryEvaluation.QuantifierExpr() - res.result = this - res.quantifier = QueryEvaluation.Quantifier.FORALL - res.str = T::class.simpleName - // res.inner = inner - // TODO!! - return res -} - // // and | or | eq | ne | gt | lt | ge | le | implies | is | in diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt index dcf5453fb4..df1f2abcda 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt @@ -264,10 +264,6 @@ class AnalysisTest { result ) - result.forall2 { n: CallExpression -> - (field(n.invokes[0].name) eq const("memcpy")) implies - (sizeof(field(n.arguments[0])) ge sizeof(field(n.arguments[1]))) - } assertFalse(query.evaluate() as Boolean) println(query.paths) } From b46331ae5b703765db08cf61b7cd4f21b8478c71 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 25 Jul 2022 08:35:22 +0200 Subject: [PATCH 31/67] Extend operators of the query API --- .../fraunhofer/aisec/cpg/query/QueryTree.kt | 61 ++++++++++++++++++- .../aisec/cpg/analysis2/Analysis2Test.kt | 17 +++--- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index a3e5b0cf5d..62ac94c768 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -45,12 +45,42 @@ open class QueryTree( infix fun eq(other: QueryTree): QueryTree { val result = this.value == other.value - return QueryTree(result, mutableListOf(this, other), "$this == $other") + return QueryTree(result, mutableListOf(this, other), "${this.value} == ${other.value}") } - infix fun neq(other: QueryTree): QueryTree { + infix fun eq(other: T): QueryTree { + val result = this.value == other + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} == $value") + } + + infix fun ne(other: QueryTree): QueryTree { val result = this.value != other.value - return QueryTree(result, mutableListOf(this, other), "$this == $other") + return QueryTree(result, mutableListOf(this, other), "${this.value} != ${other.value}") + } + + infix fun ne(other: T): QueryTree { + val result = this.value != other + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} != $value") + } + + infix fun IN(other: QueryTree>): QueryTree { + val result = other.value.contains(this.value) + return QueryTree(result, mutableListOf(this, other), "${this.value} in ${other.value}") + } + + infix fun IN(other: Collection<*>): QueryTree { + val result = other.contains(this.value) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} in $other") + } + + infix fun IS(other: QueryTree>): QueryTree { + val result = other.value.isInstance(this.value) + return QueryTree(result, mutableListOf(this, other), "${this.value} is ${other.value}") + } + + infix fun IS(other: Class<*>): QueryTree { + val result = other.isInstance(this.value) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} is $other") } override fun hashCode(): Int { @@ -98,26 +128,51 @@ infix fun QueryTree.implies(other: QueryTree): QueryTree): QueryTree { + val result = !arg.value + return QueryTree(result, mutableListOf(arg), "! ${arg.value}") +} + infix fun QueryTree.gt(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) > 0 return QueryTree(result, mutableListOf(this, other), "${this.value} > ${other.value}") } +infix fun QueryTree.gt(other: Number): QueryTree { + val result = this.value.compareTo(other) > 0 + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} > $other") +} + infix fun QueryTree.ge(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) >= 0 return QueryTree(result, mutableListOf(this, other), "${this.value} >= ${other.value}") } +infix fun QueryTree.ge(other: Number): QueryTree { + val result = this.value.compareTo(other) >= 0 + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} >= $other") +} + infix fun QueryTree.lt(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) < 0 return QueryTree(result, mutableListOf(this, other), "${this.value} < ${other.value}") } +infix fun QueryTree.lt(other: Number): QueryTree { + val result = this.value.compareTo(other) < 0 + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} < $other") +} + infix fun QueryTree.le(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) <= 0 return QueryTree(result, mutableListOf(this, other), "${this.value} <= ${other.value}") } +infix fun QueryTree.le(other: Number): QueryTree { + val result = this.value.compareTo(other) <= 0 + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} <= $other") +} + /** * This is a small wrapper to create a [QueryTreeHolder] containing a constant value, so that it can * be used to in comparison with other [QueryTreeHolder] objects. diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index 6452e1252b..0c3be16c3e 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -107,7 +107,7 @@ class Analysis2Test { (it.end as? DeclaredReferenceExpression)?.refersTo == (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo } - return@all const(path?.isEmpty()) eq const(true) + return@all const(path?.isEmpty()) eq true } assertFalse(queryTreeResult.value) @@ -127,9 +127,7 @@ class Analysis2Test { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { - it.arguments[2].intValue!! eq const(11) - } + result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!! eq 11 } assertTrue(queryTreeResult.value) } @@ -146,8 +144,7 @@ class Analysis2Test { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val queryTreeResult = - result.all { it.value.invoke() as QueryTree lt const(5) } + val queryTreeResult = result.all { it.value.invoke() as QueryTree lt 5 } assertTrue(queryTreeResult.value) } @@ -172,7 +169,7 @@ class Analysis2Test { as VariableDeclaration) .initializer as ArrayCreationExpression) .dimensions[0] - )) and (min(it.subscriptExpression) ge const(0)) + )) and (min(it.subscriptExpression) ge 0) } assertFalse(queryTreeResult.value) println(queryTreeResult.printNicely()) @@ -199,7 +196,7 @@ class Analysis2Test { as VariableDeclaration) .initializer as ArrayCreationExpression) .dimensions[0] - )) and (min(it.subscriptExpression) ge const(0)) + )) and (min(it.subscriptExpression) ge 0) } assertFalse(queryTreeResult.value) println(queryTreeResult.printNicely()) @@ -226,7 +223,7 @@ class Analysis2Test { as VariableDeclaration) .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } - )) and (min(it.subscriptExpression) ge const(0)) + )) and (min(it.subscriptExpression) ge 0) } assertFalse(queryTreeResult.value) println(queryTreeResult) @@ -256,7 +253,7 @@ class Analysis2Test { .dimensions[0] ) val min_sub = min(it.subscriptExpression) - return@all (max_sub lt min_dim) and (min_sub ge const(0)) + return@all (max_sub lt min_dim) and (min_sub ge 0) } assertTrue(queryTreeResult.value) println(queryTreeResult) From b1924a124088843a90dbb4bbb1a8f478ead41adf Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 25 Jul 2022 09:59:31 +0200 Subject: [PATCH 32/67] Extract shortcuts for frequently used functionality --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 21 -------- .../aisec/cpg/query/QueryShortcuts.kt | 53 +++++++++++++++++++ .../aisec/cpg/analysis2/Analysis2Test.kt | 24 ++------- 3 files changed, 56 insertions(+), 42 deletions(-) create mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index f72405504d..e56970b7ba 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -31,7 +31,6 @@ import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator import de.fraunhofer.aisec.cpg.analysis.NumberSet import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @ExperimentalGraph @@ -76,26 +75,6 @@ fun sizeof(n: Node?): QueryTree { return QueryTree(eval.evaluate(n) as? Int ?: -1, mutableListOf(QueryTree(n))) } -/* -forall (n: CallExpression): n.invokes.name == "memcpy" => |sizeof(n.arguments[0])| < |sizeof(n.arguments[1])| - - False - -------------------- - 5 < 3 (=> False) - ---------------------------------------------------------------- - sizeof( var1 ) (=> 5) < sizeof( var2 ) (=> 3) - ------------------------------------------------ - n.arguments[0] (=> var1) n.arguments[1] (=> var2) - */ - -@OptIn(ExperimentalGraph::class) -val TranslationResult.calls: List - get() = this.graph.nodes.filterIsInstance() - -fun List.name(name: String): List { - return this.filter { n -> n.invokes.any { it.name == name } } -} - /** * Retrieves the minimal value of the node. * diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt new file mode 100644 index 0000000000..eb91dce6b1 --- /dev/null +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022, 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.query + +import de.fraunhofer.aisec.cpg.ExperimentalGraph +import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.graph +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* + +@OptIn(ExperimentalGraph::class) +val TranslationResult.calls: List + get() = this.graph.nodes.filterIsInstance() + +@OptIn(ExperimentalGraph::class) +fun TranslationResult.callByName(name: String): List { + return this.graph.nodes.filter { node -> + (node as? CallExpression)?.invokes?.any { it.name == name } == true + } as List +} + +fun List.name(name: String): List { + return this.filter { n -> n.invokes.any { it.name == name } } +} + +val ArraySubscriptionExpression.size: Expression + get() = + (((this.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) + .initializer as ArrayCreationExpression) + .dimensions[0] diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt index 0c3be16c3e..bb11bb59dd 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt @@ -163,13 +163,7 @@ class Analysis2Test { val queryTreeResult = result.all { - (max(it.subscriptExpression) lt - min( - (((it.arrayExpression as DeclaredReferenceExpression).refersTo - as VariableDeclaration) - .initializer as ArrayCreationExpression) - .dimensions[0] - )) and (min(it.subscriptExpression) ge 0) + (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) ge 0) } assertFalse(queryTreeResult.value) println(queryTreeResult.printNicely()) @@ -190,13 +184,7 @@ class Analysis2Test { val queryTreeResult = result.all { - (max(it.subscriptExpression) lt - min( - (((it.arrayExpression as DeclaredReferenceExpression).refersTo - as VariableDeclaration) - .initializer as ArrayCreationExpression) - .dimensions[0] - )) and (min(it.subscriptExpression) ge 0) + (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) ge 0) } assertFalse(queryTreeResult.value) println(queryTreeResult.printNicely()) @@ -245,13 +233,7 @@ class Analysis2Test { val queryTreeResult = result.all { val max_sub = max(it.subscriptExpression) - val min_dim = - min( - (((it.arrayExpression as DeclaredReferenceExpression).refersTo - as VariableDeclaration) - .initializer as ArrayCreationExpression) - .dimensions[0] - ) + val min_dim = min(it.size) val min_sub = min(it.subscriptExpression) return@all (max_sub lt min_dim) and (min_sub ge 0) } From cec9d7533bbad1bd44b52305be0df91f72a9875c Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 25 Jul 2022 09:59:51 +0200 Subject: [PATCH 33/67] Delete old design --- .../aisec/cpg/analysis/QueryBuilder.kt | 621 ------------------ .../aisec/cpg/analysis/QueryEvaluation.kt | 435 ------------ .../aisec/cpg/analysis/AnalysisTest.kt | 270 -------- 3 files changed, 1326 deletions(-) delete mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt delete mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt delete mode 100644 cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt deleted file mode 100644 index e3a20e0b88..0000000000 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryBuilder.kt +++ /dev/null @@ -1,621 +0,0 @@ -/* - * Copyright (c) 2022, 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.TranslationResult -import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.ValueEvaluator - -class QueryBuilder { - // Quantifiers - fun forall(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr = - QueryEvaluation.QuantifierExpr(QueryEvaluation.Quantifier.FORALL).apply(block) - - fun forall( - tr: TranslationResult, - block: QueryEvaluation.QuantifierExpr.() -> Unit - ): QueryEvaluation.QuantifierExpr = - QueryEvaluation.QuantifierExpr(QueryEvaluation.Quantifier.FORALL, tr).apply(block) - - fun exists(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr = - QueryEvaluation.QuantifierExpr(QueryEvaluation.Quantifier.EXISTS).apply(block) - - // Binary Operators - fun and(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.AND).apply(block) - fun or(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.OR).apply(block) - fun eq(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.EQ).apply(block) - fun ne(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.NE).apply(block) - fun gt(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.GT).apply(block) - fun ge(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.GE).apply(block) - fun lt(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.LT).apply(block) - fun le(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.LE).apply(block) - fun IS(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.IS).apply(block) - fun implies(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.IMPLIES).apply(block) - fun IN(block: QueryEvaluation.BinaryExpr.() -> Unit): QueryEvaluation.BinaryExpr = - QueryEvaluation.BinaryExpr(QueryEvaluation.QueryOp.IN).apply(block) - - // Unary Operators - fun not(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = - QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.NOT).apply(block) - fun min(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = - QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.MIN).apply(block) - fun sizeof(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = - QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.SIZEOF).apply(block) - fun max(block: QueryEvaluation.UnaryExpr.() -> Unit): QueryEvaluation.UnaryExpr = - QueryEvaluation.UnaryExpr(QueryEvaluation.QueryOp.MAX).apply(block) - - // Constant expression - fun const(value: Any?): QueryEvaluation.ConstExpr { - val constExpr = QueryEvaluation.ConstExpr() - constExpr.value = value - return constExpr - } - - // Unary expressions - fun sizeof(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { - return QueryEvaluation.UnaryExpr(inner, QueryEvaluation.QueryOp.SIZEOF) - } - fun min(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { - return QueryEvaluation.UnaryExpr(inner, QueryEvaluation.QueryOp.MIN) - } - fun max(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { - return QueryEvaluation.UnaryExpr(inner, QueryEvaluation.QueryOp.MAX) - } - fun not(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { - return QueryEvaluation.UnaryExpr(inner, QueryEvaluation.QueryOp.NOT) - } - - // Nodes expression - fun queryNodes( - result: TranslationResult, - block: QueryEvaluation.NodesExpression.() -> Unit - ): QueryEvaluation.NodesExpression = QueryEvaluation.NodesExpression(result).apply(block) - - // access a field - fun fieldAccess( - block: QueryEvaluation.FieldAccessExpr.() -> Unit - ): QueryEvaluation.FieldAccessExpr = QueryEvaluation.FieldAccessExpr().apply(block) -} - -fun forall( - translationResult: TranslationResult, - block: QueryEvaluation.QuantifierExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - return QueryBuilder().forall(translationResult, block) -} - -fun forall(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr { - return QueryBuilder().forall(block) -} - -fun exists(block: QueryEvaluation.QuantifierExpr.() -> Unit): QueryEvaluation.QuantifierExpr { - return QueryBuilder().exists(block) -} - -// forall | exists (queryNodes): -fun QueryEvaluation.QuantifierExpr.queryNodes( - result: TranslationResult, - block: QueryEvaluation.NodesExpression.() -> Unit -): QueryEvaluation.QuantifierExpr { - variables = QueryBuilder().queryNodes(result, block) - return this -} - -fun QueryEvaluation.QuantifierExpr.field( - block: QueryEvaluation.FieldAccessExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().fieldAccess(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.not( - block: QueryEvaluation.UnaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().not(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.and( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().and(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.or( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().or(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.eq( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().eq(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.ne( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().ne(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.gt( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().gt(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.ge( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().ge(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.lt( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().lt(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.le( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().le(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.IS( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().IS(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.implies( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().implies(block) - return this -} - -fun QueryEvaluation.QuantifierExpr.IN( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.QuantifierExpr { - inner = QueryBuilder().IN(block) - return this -} - -fun const(value: Any?): QueryEvaluation.ConstExpr { - return QueryBuilder().const(value) -} - -fun sizeof(inner: QueryEvaluation.QueryExpression): QueryEvaluation.UnaryExpr { - return QueryBuilder().sizeof(inner) -} - -fun field(str: String, valueEvaluator: ValueEvaluator): QueryEvaluation.FieldAccessExpr { - return QueryEvaluation.FieldAccessExpr(str, valueEvaluator) -} - -fun field(str: String): QueryEvaluation.FieldAccessExpr { - val res = QueryEvaluation.FieldAccessExpr() - res.str = str - return res -} - -fun field(node: Node): QueryEvaluation.FieldAccessExpr { - // TODO!! - val res = QueryEvaluation.FieldAccessExpr() - return res -} - -fun forall( - str: String, - inner: QueryEvaluation.QueryExpression, - result: TranslationResult -): QueryEvaluation.QuantifierExpr { - val res = QueryEvaluation.QuantifierExpr() - res.result = result - res.quantifier = QueryEvaluation.Quantifier.FORALL - res.str = str - res.inner = inner - return res -} - -// -// and | or | eq | ne | gt | lt | ge | le | implies | is | in -// -fun QueryEvaluation.BinaryExpr.const(value: Any?): QueryEvaluation.BinaryExpr { - // Automagically pick lhs or rhs - if (lhs == null) { - lhs = QueryBuilder().const(value) - } else { - rhs = QueryBuilder().const(value) - } - return this -} - -fun QueryEvaluation.BinaryExpr.field(str: String): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryEvaluation.FieldAccessExpr() - (lhs as QueryEvaluation.FieldAccessExpr).str = str - } else { - rhs = QueryEvaluation.FieldAccessExpr() - (rhs as QueryEvaluation.FieldAccessExpr).str = str - } - return this -} - -fun QueryEvaluation.BinaryExpr.field( - block: QueryEvaluation.FieldAccessExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().fieldAccess(block) - } else { - rhs = QueryBuilder().fieldAccess(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.not( - block: QueryEvaluation.UnaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().not(block) - } else { - rhs = QueryBuilder().not(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.max( - block: QueryEvaluation.UnaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().max(block) - } else { - rhs = QueryBuilder().max(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.min( - block: QueryEvaluation.UnaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().min(block) - } else { - rhs = QueryBuilder().min(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.sizeof( - block: QueryEvaluation.UnaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().sizeof(block) - } else { - rhs = QueryBuilder().sizeof(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.and( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().and(block) - } else { - rhs = QueryBuilder().and(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.or( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().or(block) - } else { - rhs = QueryBuilder().or(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.eq( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().eq(block) - } else { - rhs = QueryBuilder().eq(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.ne( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().ne(block) - } else { - rhs = QueryBuilder().ne(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.gt( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().gt(block) - } else { - rhs = QueryBuilder().gt(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.ge( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().ge(block) - } else { - rhs = QueryBuilder().ge(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.lt( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().lt(block) - } else { - rhs = QueryBuilder().lt(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.le( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().le(block) - } else { - rhs = QueryBuilder().le(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.IS( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().IS(block) - } else { - rhs = QueryBuilder().IS(block) - } - return this -} - -infix fun QueryEvaluation.BinaryExpr.implies( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().implies(block) - } else { - rhs = QueryBuilder().implies(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.IN( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().IN(block) - } else { - rhs = QueryBuilder().IN(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.forall( - block: QueryEvaluation.QuantifierExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().forall(block) - } else { - rhs = QueryBuilder().forall(block) - } - return this -} - -fun QueryEvaluation.BinaryExpr.exists( - block: QueryEvaluation.QuantifierExpr.() -> Unit -): QueryEvaluation.BinaryExpr { - if (lhs == null) { - lhs = QueryBuilder().exists(block) - } else { - rhs = QueryBuilder().exists(block) - } - return this -} - -// not | min | max | sizeof -// -fun QueryEvaluation.UnaryExpr.const(value: Any): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().const(value) - return this -} - -fun QueryEvaluation.UnaryExpr.field( - block: QueryEvaluation.FieldAccessExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().fieldAccess(block) - return this -} - -fun QueryEvaluation.UnaryExpr.not( - block: QueryEvaluation.UnaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().not(block) - return this -} - -fun QueryEvaluation.UnaryExpr.max( - block: QueryEvaluation.UnaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().max(block) - return this -} - -fun QueryEvaluation.UnaryExpr.min( - block: QueryEvaluation.UnaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().min(block) - return this -} - -fun QueryEvaluation.UnaryExpr.sizeof( - block: QueryEvaluation.UnaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().sizeof(block) - return this -} - -fun QueryEvaluation.UnaryExpr.and( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().and(block) - return this -} - -fun QueryEvaluation.UnaryExpr.or( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().or(block) - return this -} - -fun QueryEvaluation.UnaryExpr.eq( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().eq(block) - return this -} - -fun QueryEvaluation.UnaryExpr.ne( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().ne(block) - return this -} - -fun QueryEvaluation.UnaryExpr.gt( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().gt(block) - return this -} - -fun QueryEvaluation.UnaryExpr.ge( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().ge(block) - return this -} - -fun QueryEvaluation.UnaryExpr.lt( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().lt(block) - return this -} - -fun QueryEvaluation.UnaryExpr.le( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().le(block) - return this -} - -fun QueryEvaluation.UnaryExpr.IS( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().IS(block) - return this -} - -fun QueryEvaluation.UnaryExpr.implies( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().implies(block) - return this -} - -fun QueryEvaluation.UnaryExpr.IN( - block: QueryEvaluation.BinaryExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().IN(block) - return this -} - -fun QueryEvaluation.UnaryExpr.forall( - block: QueryEvaluation.QuantifierExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().forall(block) - return this -} - -fun QueryEvaluation.UnaryExpr.exists( - block: QueryEvaluation.QuantifierExpr.() -> Unit -): QueryEvaluation.UnaryExpr { - inner = QueryBuilder().exists(block) - return this -} diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt deleted file mode 100644 index 6722d4f0d4..0000000000 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryEvaluation.kt +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright (c) 2022, 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.TranslationResult -import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.ValueEvaluator -import de.fraunhofer.aisec.cpg.graph.compareTo -import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker -import kotlin.reflect.KProperty1 -import kotlin.reflect.full.allSupertypes -import kotlin.reflect.jvm.isAccessible - -class QueryEvaluation { - - enum class QueryOp { - GT, - GE, - LT, - LE, - EQ, - NE, - IMPLIES, - MAX, - MIN, - SIZEOF, - NOT, - AND, - OR, - IS, - IN - } - - enum class Quantifier { - FORALL, - EXISTS - } - - abstract class QueryExpression(open val representation: String?) { - open var paths: Pair = Pair(null, null) - abstract fun evaluate(input: Map = mutableMapOf()): Any? - - infix fun `==`(other: QueryExpression): BinaryExpr { - return BinaryExpr(this, other, QueryOp.EQ) - } - - infix fun eq(other: QueryExpression): BinaryExpr { - return BinaryExpr(this, other, QueryOp.EQ) - } - - infix fun `!=`(other: QueryExpression): BinaryExpr { - return BinaryExpr(this, other, QueryOp.NE) - } - - infix fun implies(other: QueryExpression): BinaryExpr { - return BinaryExpr(this, other, QueryOp.IMPLIES) - } - - infix fun ge(other: QueryExpression): BinaryExpr { - return BinaryExpr(this, other, QueryOp.GE) - } - - infix fun gt(other: QueryExpression): BinaryExpr { - return BinaryExpr(this, other, QueryOp.GT) - } - - infix fun le(other: QueryExpression): BinaryExpr { - return BinaryExpr(this, other, QueryOp.LE) - } - - infix fun lt(other: QueryExpression): BinaryExpr { - return BinaryExpr(this, other, QueryOp.LT) - } - } - - class NodesExpression(override val representation: String? = "") : - QueryExpression(representation) { - lateinit var nodeType: String - lateinit var result: TranslationResult - var kClass: Class? = null - - constructor( - nodeType: String, - result: TranslationResult, - representation: String? = "" - ) : this(representation) { - this.nodeType = nodeType - this.result = result - } - - constructor( - result: TranslationResult, - representation: String? = "" - ) : this(representation) { - this.result = result - } - - constructor( - kClass: Class, - result: TranslationResult, - representation: String? = "" - ) : this(representation) { - this.result = result - this.kClass = kClass - this.nodeType = kClass.simpleName - } - - override fun evaluate(input: Map): Any { - if (kClass != null) { - return SubgraphWalker.flattenAST(result).filter { n -> - n::class.allSupertypes.any { t -> t.javaClass == kClass } || - n.javaClass == kClass || - n.javaClass.interfaces.any { i -> i == kClass } - } - } - val result = - SubgraphWalker.flattenAST(result).filter { n -> - n.javaClass.simpleName == nodeType || - classNamesOfNode(n.javaClass).any { c -> c == nodeType } - } - this.paths = Pair(result, result) - return result - } - - fun classNamesOfNode(jClass: Class<*>): Collection { - val result = mutableListOf() - if (jClass.superclass != null) { - result.add(jClass.superclass.simpleName) - result.addAll(classNamesOfNode(jClass.superclass)) - } - result.addAll(jClass.interfaces.map { i -> i.simpleName }) - return result - } - } - - class QuantifierExpr( - var result: TranslationResult? = null, - override val representation: String? = "" - ) : QueryExpression(representation) { - var str: String? = null - set(value) { - variableName = value?.split(":")?.get(0)?.strip() ?: "" - val varClass = value?.split(":")?.get(1)?.strip() ?: "" - variables = NodesExpression(varClass, result!!) - field = value - } - lateinit var quantifier: Quantifier - lateinit var variables: QueryExpression - lateinit var variableName: String - lateinit var inner: QueryExpression - - constructor( - quantifier: Quantifier, - variables: QueryExpression, - variableName: String, - inner: QueryExpression, - result: TranslationResult? = null, - representation: String? = "" - ) : this(result, representation) { - this.quantifier = quantifier - this.variables = variables - this.variableName = variableName - this.inner = inner - } - - constructor( - quantifier: Quantifier, - result: TranslationResult? = null, - representation: String? = "" - ) : this(result, representation) { - this.quantifier = quantifier - } - - override fun evaluate(input: Map): Any { - val paths = mutableListOf>() - val newInput = input.toMutableMap() - val evaluationResult = variables.evaluate(input) as Collection - return if (quantifier == Quantifier.FORALL) { - var result = true - for (v in evaluationResult) { - newInput[variableName] = v - val temp = (inner.evaluate(newInput) as Boolean) - paths.add(Pair(inner.paths, temp)) - if (!temp) { - result = false - } - } - this.paths = Pair(paths, result) - result - } else if (quantifier == Quantifier.EXISTS) { - var result = false - for (v in evaluationResult) { - newInput[variableName] = v - val temp = inner.evaluate(newInput) as Boolean - paths.add(Pair(inner.paths, temp)) - if (temp) { - result = true - } - } - this.paths = Pair(paths, result) - result - } else { - this.paths = Pair("Unknown Quantifier", result) - false - } - } - } - - class FieldAccessExpr(override val representation: String? = "") : - QueryExpression(representation) { - - var str: String? = null - set(value) { - variableName = value?.split(".", limit = 2)?.get(0) ?: "" - fieldSpecifier = value?.split(".", limit = 2)?.get(1) ?: "" - field = value - } - lateinit var variableName: String - lateinit var fieldSpecifier: String - var evaluator: ValueEvaluator = ValueEvaluator() - - constructor( - str: String, - evaluator: ValueEvaluator, - representation: String? = "" - ) : this(representation) { - this.str = str - variableName = str.split(".", limit = 2).get(0) - fieldSpecifier = str.split(".", limit = 2).get(1) - this.evaluator = evaluator - } - - constructor( - variableName: String, - fieldSpecifier: String, - evaluator: ValueEvaluator, - representation: String? = "" - ) : this(representation) { - this.variableName = variableName - this.fieldSpecifier = fieldSpecifier - this.evaluator = evaluator - } - - override fun evaluate(input: Map): Any { - var currentField: Any = input[variableName]!! - for (fs in fieldSpecifier.split(".")) { - val arrayIndex = - if ("[" !in fs) { - -1 - } else { - fs.split("[")[1].dropLast(1).toInt() - } - val fieldName = if (arrayIndex > -1) fs.split("[")[0] else fs - currentField = readInstanceProperty(currentField, fieldName) - if (arrayIndex != -1 && currentField is Array<*>) { - currentField = currentField[arrayIndex]!! - } else if (arrayIndex != -1 && currentField is List<*>) { - currentField = currentField[arrayIndex]!! - // Ugly hack to get the property where the edge points to - currentField = readInstanceProperty(currentField, "end") - } else if (currentField is List<*>) { - // The query assumes a single value instead of a list. We just return the first - // element. - currentField = currentField[0]!! - // Ugly hack to get the property where the edge points to - currentField = readInstanceProperty(currentField, "end") - } - } - - val returnValue = evaluator.evaluate(currentField)!! - this.paths = Pair(evaluator.path, returnValue) - return returnValue - } - - private fun readInstanceProperty(instance: Any, propertyName: String): Any { - val property = - instance::class.members.first { it.name == propertyName } as KProperty1 - return property.apply { isAccessible = true }.get(instance)!! - } - } - - class ConstExpr(override val representation: String? = "") : QueryExpression(representation) { - var value: Any? = null - - constructor(value: Any?, representation: String? = "") : this(representation) { - this.value = value - } - - override fun evaluate(input: Map): Any? { - this.paths = Pair(value, value) - return value - } - } - - class UnaryExpr(override val representation: String? = "") : QueryExpression(representation) { - lateinit var inner: QueryExpression - lateinit var operator: QueryOp - - constructor( - inner: QueryExpression, - operator: QueryOp, - representation: String? = "" - ) : this(representation) { - this.inner = inner - this.operator = operator - } - - constructor(operator: QueryOp, representation: String? = "") : this(representation) { - this.operator = operator - } - - override fun evaluate(input: Map): Any? { - val returnValue = - when (operator) { - QueryOp.NOT -> !(inner.evaluate(input) as Boolean) - QueryOp.MAX -> { - val result = inner.evaluate(input) - if (result is Number) { - result.toLong() - } else { - (result as NumberSet).max() - } - } - QueryOp.MIN -> { - val result = inner.evaluate(input) - if (result is Number) { - result.toLong() - } else { - (result as NumberSet).min() - } - } - QueryOp.SIZEOF -> { - if ((inner as? FieldAccessExpr)?.evaluator !is SizeEvaluator) { - (inner as? FieldAccessExpr)?.evaluator = SizeEvaluator() - } - inner.evaluate(input) - } - else -> throw Exception("Unknown operation $operator on expression $inner") - } - this.paths = Pair(inner.paths, returnValue) - return returnValue - } - } - - class BinaryExpr(override var representation: String? = "") : QueryExpression(representation) { - var lhs: QueryExpression? = null - var rhs: QueryExpression? = null - lateinit var operator: QueryOp - - constructor( - lhs: QueryExpression, - rhs: QueryExpression, - operator: QueryOp, - representation: String? = "" - ) : this(representation) { - this.lhs = lhs - this.rhs = rhs - this.operator = operator - } - - constructor(operator: QueryOp, representation: String? = "") : this(representation) { - this.operator = operator - } - - override fun evaluate(input: Map): Boolean { - val returnValue = - when (operator) { - QueryOp.AND -> - lhs?.evaluate(input) as Boolean && rhs?.evaluate(input) as Boolean - QueryOp.OR -> lhs?.evaluate(input) as Boolean || rhs?.evaluate(input) as Boolean - QueryOp.EQ -> lhs?.evaluate(input) == rhs?.evaluate(input) - QueryOp.NE -> { - val lhsVal = lhs?.evaluate(input) - val rhsVal = rhs?.evaluate(input) - if (lhsVal is Collection<*>) { - lhsVal.all { l -> l != rhsVal } - } else { - lhsVal != rhsVal - } - } - QueryOp.GT -> - (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) > - 0 - QueryOp.GE -> - (lhs?.evaluate(input) as Number).compareTo( - rhs?.evaluate(input) as Number - ) >= 0 - QueryOp.LT -> - (lhs?.evaluate(input) as Number).compareTo(rhs?.evaluate(input) as Number) < - 0 - QueryOp.LE -> - (lhs?.evaluate(input) as Number).compareTo( - rhs?.evaluate(input) as Number - ) <= 0 - QueryOp.IS -> { - val rhsVal = rhs?.evaluate(input) - if (rhsVal is String) { - lhs?.evaluate(input)?.javaClass?.simpleName == rhsVal - } else { - lhs?.evaluate(input)?.javaClass == rhsVal - } - } - QueryOp.IMPLIES -> - !(lhs?.evaluate(input) as Boolean) || rhs?.evaluate(input) as Boolean - QueryOp.IN -> lhs?.evaluate(input) in (rhs?.evaluate(input) as Collection<*>) - else -> false - } - this.paths = Pair(Pair(lhs?.paths, rhs?.paths), returnValue) - return returnValue - } - } -} diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt deleted file mode 100644 index df1f2abcda..0000000000 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/AnalysisTest.kt +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2021, 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.ExperimentalGraph -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.TranslationManager -import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.console.fancyCode -import de.fraunhofer.aisec.cpg.graph.body -import de.fraunhofer.aisec.cpg.graph.byNameOrNull -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.passes.EdgeCachePass -import java.io.File -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class AnalysisTest { - @Test - fun testOutOfBounds() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array.cpp")) - .defaultPasses() - .defaultLanguages() - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - - OutOfBoundsCheck().run(result) - } - - private fun createArrayOutOfBoundsQuery( - result: TranslationResult, - normalEvaluator: Boolean - ): QueryEvaluation.QueryExpression { - // Query: forall (n: ArraySubscriptionExpression): max(n.subscriptExpression) < - // min(n.arrayExpression.refersTo.initializer.dimensions[0]) - // && min(n.subscriptExpression) >= 0 - return forall(result) { - str = "n: ArraySubscriptionExpression" - and { - lt { - max { - field { - str = "n.subscriptExpression" - if (!normalEvaluator) evaluator = MultiValueEvaluator() - } - } - min { - field { - str = "n.arrayExpression.refersTo.initializer.dimensions[0]" - if (!normalEvaluator) evaluator = MultiValueEvaluator() - } - } - } - ge { - min { - field { - str = "n.subscriptExpression" - if (!normalEvaluator) evaluator = MultiValueEvaluator() - } - } - const(0) - } - } - } - } - - @Test - fun testOutOfBoundsQuery() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array.cpp")) - .defaultPasses() - .defaultLanguages() - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - - val query = createArrayOutOfBoundsQuery(result, true) - assertFalse(query.evaluate() as Boolean) - } - - @Test - fun testOutOfBoundsQuery2() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array2.cpp")) - .defaultPasses() - .defaultLanguages() - .registerPass(EdgeCachePass()) - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - - val query = createArrayOutOfBoundsQuery(result, false) - assertFalse(query.evaluate() as Boolean) - } - - @Test - fun testOutOfBoundsQueryCorrect() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array_correct.cpp")) - .defaultPasses() - .defaultLanguages() - .registerPass(EdgeCachePass()) - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - - val query = createArrayOutOfBoundsQuery(result, false) - assertTrue(query.evaluate() as Boolean) - } - - @Test - fun testNullPointer() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/Array.java")) - .defaultPasses() - .defaultLanguages() - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - - NullPointerCheck().run(result) - } - - @Test - fun testAttribute() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/Array.java")) - .defaultPasses() - .defaultLanguages() - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - val tu = result.translationUnits.first() - - val main = tu.byNameOrNull("Array.main", true) - assertNotNull(main) - val call = main.body(0) - - var code = call.fancyCode(showNumbers = false) - - // assertEquals("obj.\u001B[36mdoSomething\u001B[0m();", code) - println(code) - - var decl = main.body(0) - code = decl.fancyCode(showNumbers = false) - println(code) - - decl = main.body(1) - code = decl.fancyCode(showNumbers = false) - println(code) - - code = main.fancyCode(showNumbers = false) - println(code) - - code = call.fancyCode(3, true) - println(code) - } - - @Test - fun testNullPointerQuery() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/Array.java")) - .defaultPasses() - .defaultLanguages() - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - - // forall (n: Node): n.has_base() => n.base != null - val query = - forall("n: HasBase", field("n.base", MultiValueEvaluator()) `!=` const(null), result) - - assertFalse(query.evaluate() as Boolean) - } - - @Test - fun testMemcpyTooLargeQuery() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) - .defaultPasses() - .defaultLanguages() - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - - // forall (n: CallExpression): n.invokes.name == "memcpy" => |sizeof(n.arguments[0])| < - // |sizeof(n.arguments[2])| - val query = - forall( - "n: CallExpression", - ((field("n.invokesRelationship.name") `==` const("memcpy")) implies - (sizeof(field("n.argumentsEdges[0]")) ge sizeof(field("n.argumentsEdges[1]")))), - result - ) - - assertFalse(query.evaluate() as Boolean) - } - - @OptIn(ExperimentalGraph::class) - @Test - fun testMemcpyTooLargeQuery2() { - val config = - TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) - .defaultPasses() - .defaultLanguages() - .build() - - val analyzer = TranslationManager.builder().config(config).build() - val result = analyzer.analyze().get() - - // forall (n: CallExpression): n.invokes.name == "memcpy" => |sizeof(n.arguments[0])| < - // |sizeof(n.arguments[2])| - val query = - forall( - "n: CallExpression", - ((field("n.invokesRelationship.name") eq const("memcpy")) implies - (sizeof(field("n.argumentsEdges")) ge sizeof(field("n.argumentsEdges[1]")))), - result - ) - - assertFalse(query.evaluate() as Boolean) - println(query.paths) - } -} From 3a029a663e13a5965e1eac273d6f2b8b8fa7677a Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 25 Jul 2022 10:00:59 +0200 Subject: [PATCH 34/67] Rename package and test name --- .../cpg/{analysis2/Analysis2Test.kt => analysis/QueryTest.kt} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/{analysis2/Analysis2Test.kt => analysis/QueryTest.kt} (99%) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt similarity index 99% rename from cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt rename to cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index bb11bb59dd..9e77b1c4d2 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis2/Analysis2Test.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -23,7 +23,7 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis2 +package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationConfiguration @@ -44,7 +44,7 @@ import kotlin.test.assertTrue import org.junit.jupiter.api.Test @ExperimentalGraph -class Analysis2Test { +class QueryTest { @Test fun testMemcpyTooLargeQuery2() { val config = From 4dfade68178a461d1cf23df2ae8776a9d1b951b3 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 25 Jul 2022 11:42:31 +0200 Subject: [PATCH 35/67] Documentation --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 91 +++++++++++++++++-- .../aisec/cpg/query/QueryShortcuts.kt | 9 ++ .../fraunhofer/aisec/cpg/query/QueryTree.kt | 84 +++++++++++++++-- 3 files changed, 171 insertions(+), 13 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index e56970b7ba..c9c1c35b74 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -33,6 +33,15 @@ import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. + * + * The optional argument [sel] can be used to filter nodes for which the condition has to be + * fulfilled. This filter should be rather simple in most cases since its evaluation is not part of + * the resulting reasoning chain. + * + * This method can be used similar to the logical implication to test "sel => mustSatisfy". + */ @ExperimentalGraph inline fun TranslationResult.all( noinline sel: ((T) -> Boolean)? = null, @@ -49,6 +58,15 @@ inline fun TranslationResult.all( return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) } +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. + * + * The optional argument [sel] can be used to filter nodes for which the condition has to be + * fulfilled. This filter should be rather simple in most cases since its evaluation is not part of + * the resulting reasoning chain. + * + * This method can be used similar to the logical implication to test "sel => mustSatisfy". + */ @ExperimentalGraph inline fun Node.all( noinline sel: ((T) -> Boolean)? = null, @@ -67,11 +85,59 @@ inline fun Node.all( return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) } -/** Evaluates the size of a node. The implementation is very very basic! */ -fun sizeof(n: Node?): QueryTree { - val eval = SizeEvaluator() - // TODO(oxisto): This cast could potentially go wrong, but if its not an int, its not really a - // size +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. + * + * The optional argument [sel] can be used to filter nodes which are considered during the + * evaluation. This filter should be rather simple in most cases since its evaluation is not part of + * the resulting reasoning chain. + */ +@ExperimentalGraph +inline fun TranslationResult.exists( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> QueryTree +): QueryTree { + var nodes = this.graph.nodes.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val queryChildren = nodes.map(mustSatisfy) + return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) +} + +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. + * + * The optional argument [sel] can be used to filter nodes which are considered during the + * evaluation. This filter should be rather simple in most cases since its evaluation is not part of + * the resulting reasoning chain. + */ +@ExperimentalGraph +inline fun Node.exists( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> QueryTree +): QueryTree { + var nodes = this.astChildren.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val queryChildren = nodes.map(mustSatisfy) + return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) +} + +/** + * Evaluates the size of a node. The implementation is very, very basic! + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ +fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): QueryTree { + // The cast could potentially go wrong, but if its not an int, its not really a size return QueryTree(eval.evaluate(n) as? Int ?: -1, mutableListOf(QueryTree(n))) } @@ -132,7 +198,7 @@ fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree } /** - * Retrieves the minimal value of the node. + * Retrieves the maximal value of the node. * * @eval can be used to specify the evaluator but this method has to interpret the result correctly! */ @@ -145,30 +211,43 @@ fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { return QueryTree(this?.evaluate(), mutableListOf(QueryTree(this))) } +/** The size of this expression. It uses the default argument for `eval` of [size] */ val Expression.size: QueryTree get() { return sizeof(this) } +/** + * The minimal integer value of this expression. It uses the default argument for `eval` of [min] + */ val Expression.min: QueryTree get() { return min(this) } +/** + * The maximal integer value of this expression. It uses the default argument for `eval` of [max] + */ val Expression.max: QueryTree get() { return max(this) } +/** Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. */ val Expression.value: QueryTree get() { return QueryTree(evaluate(), mutableListOf(QueryTree(this))) } +/** + * Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. The + * result is interpreted as an integer. + */ val Expression.intValue: QueryTree? get() { val evalRes = evaluate() as? Int ?: return null diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt index eb91dce6b1..b16a301c78 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt @@ -31,10 +31,12 @@ import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.graph import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +/** Returns all [CallExpression]s in this graph. */ @OptIn(ExperimentalGraph::class) val TranslationResult.calls: List get() = this.graph.nodes.filterIsInstance() +/** Returns all [CallExpression]s in this graph which call a method with the given [name]. */ @OptIn(ExperimentalGraph::class) fun TranslationResult.callByName(name: String): List { return this.graph.nodes.filter { node -> @@ -42,10 +44,17 @@ fun TranslationResult.callByName(name: String): List { } as List } +/** + * Filters a list of [CallExpression]s for expressions which call a method with the given [name]. + */ fun List.name(name: String): List { return this.filter { n -> n.invokes.any { it.name == name } } } +/** + * Returns the expression specifying the dimension (i.e., size) of the array during its + * initialization. + */ val ArraySubscriptionExpression.size: Expression get() = (((this.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index 62ac94c768..e5a687955a 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -27,6 +27,32 @@ package de.fraunhofer.aisec.cpg.query import de.fraunhofer.aisec.cpg.graph.compareTo +/** + * Holds the [value] to which the statements have been evaluated. The [children] define previous + * steps of the evaluation, thus building a tree of all steps of the evaluation recursively until we + * reach the nodes of the CPG. + * + * Numerous methods allow to evaluate the queries while keeping track of all the steps. Currently, + * the following operations are supported: + * - **eq**: Equality of two values. + * - **ne**: Inequality of two values. + * - **IN**: Checks if a value is contained in a [Collection] + * - **IS**: Checks if a value implements a type ([Class]). + * + * Additionally, some functions are available only for certain types of values. + * + * For boolean values: + * - **and**: Logical and operation (&&) + * - **or**: Logical or operation (||) + * - **xor**: Logical exclusive or operation (xor) + * - **implies**: Logical implication + * + * For numeric values: + * - **gt**: Grater than (>) + * - **ge**: Grater than or equal (>=) + * - **lt**: Less than (<) + * - **le**: Less than or equal (<=) + */ open class QueryTree( open var value: T, open val children: MutableList> = mutableListOf(), @@ -43,41 +69,55 @@ open class QueryTree( return res } + /** Checks for equality of two [QueryTree]s. */ infix fun eq(other: QueryTree): QueryTree { val result = this.value == other.value return QueryTree(result, mutableListOf(this, other), "${this.value} == ${other.value}") } + /** + * Checks for equality of a [QueryTree] with a value of the same type (e.g. useful to check for + * constants). + */ infix fun eq(other: T): QueryTree { val result = this.value == other return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} == $value") } + /** Checks for inequality of two [QueryTree]s. */ infix fun ne(other: QueryTree): QueryTree { val result = this.value != other.value return QueryTree(result, mutableListOf(this, other), "${this.value} != ${other.value}") } + /** + * Checks for inequality of a [QueryTree] with a value of the same type (e.g. useful to check + * for constants). + */ infix fun ne(other: T): QueryTree { val result = this.value != other return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} != $value") } + /** Checks if the value is contained in the collection of the other [QueryTree]. */ infix fun IN(other: QueryTree>): QueryTree { val result = other.value.contains(this.value) return QueryTree(result, mutableListOf(this, other), "${this.value} in ${other.value}") } + /** Checks if the value is contained in the collection [other]. */ infix fun IN(other: Collection<*>): QueryTree { val result = other.contains(this.value) return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} in $other") } + /** Checks if the value is a member of the type of the other [QueryTree]. */ infix fun IS(other: QueryTree>): QueryTree { val result = other.value.isInstance(this.value) return QueryTree(result, mutableListOf(this, other), "${this.value} is ${other.value}") } + /** Checks if the value is a member of the type of [oter]. */ infix fun IS(other: Class<*>): QueryTree { val result = other.isInstance(this.value) return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} is $other") @@ -96,6 +136,7 @@ open class QueryTree( } } +/** Performs a logical and (&&) operation between the values of two [QueryTree]s. */ infix fun QueryTree.and(other: QueryTree): QueryTree { return QueryTree( this.value && other.value, @@ -104,6 +145,7 @@ infix fun QueryTree.and(other: QueryTree): QueryTree ) } +/** Performs a logical or (||) operation between the values of two [QueryTree]s. */ infix fun QueryTree.or(other: QueryTree): QueryTree { return QueryTree( this.value || other.value, @@ -112,6 +154,7 @@ infix fun QueryTree.or(other: QueryTree): QueryTree { ) } +/** Performs a logical xor operation between the values of two [QueryTree]s. */ infix fun QueryTree.xor(other: QueryTree): QueryTree { return QueryTree( this.value xor other.value, @@ -120,6 +163,7 @@ infix fun QueryTree.xor(other: QueryTree): QueryTree ) } +/** Evaluates a logical implication (->) operation between the values of two [QueryTree]s. */ infix fun QueryTree.implies(other: QueryTree): QueryTree { return QueryTree( !this.value || other.value, @@ -128,54 +172,80 @@ infix fun QueryTree.implies(other: QueryTree): QueryTree): QueryTree { - val result = !arg.value - return QueryTree(result, mutableListOf(arg), "! ${arg.value}") -} - +/** Compares the numeric values of two [QueryTree]s for this being "greater than" (>) [other]. */ infix fun QueryTree.gt(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) > 0 return QueryTree(result, mutableListOf(this, other), "${this.value} > ${other.value}") } +/** + * Compares the numeric values of a [QueryTree] and another number for this being "greater than" (>) + * [other]. + */ infix fun QueryTree.gt(other: Number): QueryTree { val result = this.value.compareTo(other) > 0 return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} > $other") } +/** + * Compares the numeric values of two [QueryTree]s for this being "greater than or equal" (>=) + * [other]. + */ infix fun QueryTree.ge(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) >= 0 return QueryTree(result, mutableListOf(this, other), "${this.value} >= ${other.value}") } +/** + * Compares the numeric values of a [QueryTree] and another number for this being "greater than or + * equal" (>=) [other]. + */ infix fun QueryTree.ge(other: Number): QueryTree { val result = this.value.compareTo(other) >= 0 return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} >= $other") } +/** Compares the numeric values of two [QueryTree]s for this being "less than" (<) [other]. */ infix fun QueryTree.lt(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) < 0 return QueryTree(result, mutableListOf(this, other), "${this.value} < ${other.value}") } +/** + * Compares the numeric values of a [QueryTree] and another number for this being "less than" (<) + * [other]. + */ infix fun QueryTree.lt(other: Number): QueryTree { val result = this.value.compareTo(other) < 0 return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} < $other") } +/** + * Compares the numeric values of two [QueryTree]s for this being "less than or equal" (=) [other]. + */ infix fun QueryTree.le(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) <= 0 return QueryTree(result, mutableListOf(this, other), "${this.value} <= ${other.value}") } +/** + * Compares the numeric values of a [QueryTree] and another number for this being "less than or + * equal" (<=) [other]. + */ infix fun QueryTree.le(other: Number): QueryTree { val result = this.value.compareTo(other) <= 0 return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} <= $other") } +/** Negates the value of [arg] and returns the resulting [QueryTree]. */ +fun not(arg: QueryTree): QueryTree { + val result = !arg.value + return QueryTree(result, mutableListOf(arg), "! ${arg.value}") +} + /** - * This is a small wrapper to create a [QueryTreeHolder] containing a constant value, so that it can - * be used to in comparison with other [QueryTreeHolder] objects. + * This is a small wrapper to create a [QueryTree] containing a constant value, so that it can be + * used to in comparison with other [QueryTree] objects. */ fun const(n: T): QueryTree { return QueryTree(n, stringRepresentation = "$n") From 9ed1ade4d721ceeb6f946e5419ce8034b4c85cc7 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 25 Jul 2022 15:17:26 +0200 Subject: [PATCH 36/67] Functions for dfg and eog traversals --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 18 +++++ .../aisec/cpg/query/QueryShortcuts.kt | 2 +- .../aisec/cpg/analysis/QueryTest.kt | 9 +-- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 76 +++++++++++++++++++ 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index c9c1c35b74..a38739c41d 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -211,6 +211,24 @@ fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { + val evalRes = from.followNextDFGEdgesUntilHit { it == to } + return QueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) +} + +/** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ +fun executionPath(from: Node, to: Node): QueryTree { + val evalRes = from.followNextEOGEdgesUntilHit { it == to } + return QueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) +} + +/** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ +fun executionPath(from: Node, predicate: (Node) -> Boolean): QueryTree { + val evalRes = from.followNextEOGEdgesUntilHit(predicate) + return QueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) +} + /** Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. */ operator fun Expression?.invoke(): QueryTree { return QueryTree(this?.evaluate(), mutableListOf(QueryTree(this))) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt index b16a301c78..d869a8ea58 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt @@ -47,7 +47,7 @@ fun TranslationResult.callByName(name: String): List { /** * Filters a list of [CallExpression]s for expressions which call a method with the given [name]. */ -fun List.name(name: String): List { +fun List.filterByName(name: String): List { return this.filter { n -> n.invokes.any { it.name == name } } } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 9e77b1c4d2..563dee714d 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -36,7 +36,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExp import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.passes.EdgeCachePass -import de.fraunhofer.aisec.cpg.passes.followNextEOG import de.fraunhofer.aisec.cpg.query.* import java.io.File import kotlin.test.assertFalse @@ -102,12 +101,12 @@ class QueryTest { val queryTreeResult = result.all({ it.name == "free" }) { outer -> - val path = - outer.followNextEOG { - (it.end as? DeclaredReferenceExpression)?.refersTo == + not( + executionPath(outer) { + (it as? DeclaredReferenceExpression)?.refersTo == (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo } - return@all const(path?.isEmpty()) eq true + ) } assertFalse(queryTreeResult.value) diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt index b48cb778d0..550028a944 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -158,6 +158,82 @@ fun Node.followPrevDFGEdgesUntilHit(predicate: (Node) -> Boolean): List { return result } +/** + * Returns a list of nodes which are data flow paths between the starting node [this] and the end + * node fulfilling [predicate]. Paths which do not end at such a node are not included in the + * result. Hence, if the return value is a non-empty list, a data flow from [this] to such a node is + * **possible but not mandatory**. + */ +fun Node.followNextDFGEdgesUntilHit(predicate: (Node) -> Boolean): List> { + // Looks complicated but at least it's not recursive... + // result: List of paths (between from and to) + val result = mutableListOf>() + // The list of paths where we're not done yet. + val worklist = mutableListOf>() + worklist.add(listOf(this)) // We start only with the "from" node (=this) + + while (worklist.isNotEmpty()) { + val currentPath = worklist.removeFirst() + // The last node of the path is where we continue. We get all of its outgoing DFG edges and + // follow them + for (next in currentPath.last().nextDFG) { + // Copy the path for each outgoing DFG edge and add the next node + val nextPath = mutableListOf() + nextPath.addAll(currentPath) + nextPath.add(next) + if (predicate(next)) { + // We ended up in the node "to", so we're done. Add the path to the results. + result.add(nextPath) + } + // The next node is new in the current path (i.e., there's no loop), so we add the path + // with the next step to the worklist. + if (!currentPath.contains(next)) { + worklist.add(nextPath) + } + } + } + + return result +} + +/** + * Returns a list of nodes which are evaluation paths between the starting node [this] and the end + * node fulfilling [predicate]. Paths which do not end at such a node are not included in the + * result. Hence, if the return value is a non-empty list, a path from [this] to such a node is + * **possible but not mandatory**. + */ +fun Node.followNextEOGEdgesUntilHit(predicate: (Node) -> Boolean): List> { + // Looks complicated but at least it's not recursive... + // result: List of paths (between from and to) + val result = mutableListOf>() + // The list of paths where we're not done yet. + val worklist = mutableListOf>() + worklist.add(listOf(this)) // We start only with the "from" node (=this) + + while (worklist.isNotEmpty()) { + val currentPath = worklist.removeFirst() + // The last node of the path is where we continue. We get all of its outgoing DFG edges and + // follow them + for (next in currentPath.last().nextEOG) { + // Copy the path for each outgoing DFG edge and add the next node + val nextPath = mutableListOf() + nextPath.addAll(currentPath) + nextPath.add(next) + if (predicate(next)) { + // We ended up in the node "to", so we're done. Add the path to the results. + result.add(nextPath) + } + // The next node is new in the current path (i.e., there's no loop), so we add the path + // with the next step to the worklist. + if (!currentPath.contains(next)) { + worklist.add(nextPath) + } + } + } + + return result +} + fun Node.followPrevEOG(predicate: (PropertyEdge<*>) -> Boolean): List>? { val path = mutableListOf>() From 94d7b586e0493e2a19d4991cbec65ea242bc508a Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 26 Jul 2022 08:23:07 +0200 Subject: [PATCH 37/67] Fix exists operator --- .../src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index a38739c41d..92eb016bfc 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -105,7 +105,7 @@ inline fun TranslationResult.exists( } val queryChildren = nodes.map(mustSatisfy) - return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) + return QueryTree(queryChildren.any { it.value }, queryChildren.toMutableList()) } /** @@ -128,7 +128,7 @@ inline fun Node.exists( } val queryChildren = nodes.map(mustSatisfy) - return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) + return QueryTree(queryChildren.any { it.value }, queryChildren.toMutableList()) } /** From c08cc8a67f12a4d111cdce03e2325bfce16155c0 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 5 Aug 2022 15:01:08 +0200 Subject: [PATCH 38/67] Nicer output --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 26 ++++++++++++------- .../fraunhofer/aisec/cpg/query/QueryTree.kt | 9 ++++--- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 92eb016bfc..ad6f43c92b 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -138,7 +138,7 @@ inline fun Node.exists( */ fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): QueryTree { // The cast could potentially go wrong, but if its not an int, its not really a size - return QueryTree(eval.evaluate(n) as? Int ?: -1, mutableListOf(QueryTree(n))) + return QueryTree(eval.evaluate(n) as? Int ?: -1, mutableListOf(), "sizeof($n)") } /** @@ -152,7 +152,7 @@ fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree } // Extend this when we have other evaluators. } - return QueryTree(result, mutableListOf(QueryTree(n))) + return QueryTree(result, mutableListOf(), "min($n)") } /** @@ -194,7 +194,7 @@ fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree } // Extend this when we have other evaluators. } - return QueryTree(result, mutableListOf(QueryTree(n))) + return QueryTree(result, mutableListOf(), "max($n)") } /** @@ -208,7 +208,7 @@ fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { /** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ fun executionPath(from: Node, to: Node): QueryTree { val evalRes = from.followNextEOGEdgesUntilHit { it == to } - return QueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) + return QueryTree( + evalRes.isNotEmpty(), + evalRes.map { QueryTree(it) }.toMutableList(), + "executionPath($from, $to)" + ) } /** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ fun executionPath(from: Node, predicate: (Node) -> Boolean): QueryTree { val evalRes = from.followNextEOGEdgesUntilHit(predicate) - return QueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) + return QueryTree( + evalRes.isNotEmpty(), + evalRes.map { QueryTree(it) }.toMutableList(), + "executionPath($from, $predicate)" + ) } /** Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. */ @@ -259,7 +267,7 @@ val Expression.max: QueryTree /** Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. */ val Expression.value: QueryTree get() { - return QueryTree(evaluate(), mutableListOf(QueryTree(this))) + return QueryTree(evaluate(), mutableListOf(), "$this") } /** @@ -269,5 +277,5 @@ val Expression.value: QueryTree val Expression.intValue: QueryTree? get() { val evalRes = evaluate() as? Int ?: return null - return QueryTree(evalRes, mutableListOf(QueryTree(this))) + return QueryTree(evalRes, mutableListOf(), "$this") } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index e5a687955a..c49e05e927 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -59,13 +59,16 @@ open class QueryTree( open val stringRepresentation: String = "" ) { fun printNicely(depth: Int = 0): String { - // TODO: Make this output awesome var res = " ".repeat(depth) + "$stringRepresentation (==> $value)\n" + - "--------".repeat(depth) + + "--------".repeat(depth + 1) + "\n" - children.forEach { res += it.toString() + "\n" + "--------".repeat(depth + 1) + "\n" } + if (children.isNotEmpty()) { + children.forEach { + res += it.printNicely(depth + 2) + "\n" + "--------".repeat(depth + 1) + "\n" + } + } return res } From 38c7486e303ac992fe8778c5b920e6a5a3720e12 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 9 Aug 2022 10:12:27 +0200 Subject: [PATCH 39/67] Broken stuff --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 97 ++++++-- .../fraunhofer/aisec/cpg/query/QueryTree.kt | 177 +++++++++++---- .../aisec/cpg/query/simple/SimpleQuery.kt | 212 ++++++++++++++++++ .../aisec/cpg/analysis/QueryTest.kt | 11 +- 4 files changed, 424 insertions(+), 73 deletions(-) create mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/simple/SimpleQuery.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index ad6f43c92b..7627ad79e8 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.analysis.NumberSet import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import kotlin.experimental.ExperimentalTypeInference /** * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. @@ -43,6 +44,8 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression * This method can be used similar to the logical implication to test "sel => mustSatisfy". */ @ExperimentalGraph +@OptIn(ExperimentalTypeInference::class) +@OverloadResolutionByLambdaReturnType inline fun TranslationResult.all( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree @@ -54,8 +57,13 @@ inline fun TranslationResult.all( nodes = nodes.filter(sel) } - val queryChildren = nodes.map(mustSatisfy) - return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) + val queryChildren = + nodes.map { n -> + val res = mustSatisfy(n) + res.stringRepresentation = "Starting at $n: " + res.stringRepresentation + res + } + return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList(), "all") } /** @@ -68,6 +76,8 @@ inline fun TranslationResult.all( * This method can be used similar to the logical implication to test "sel => mustSatisfy". */ @ExperimentalGraph +@OptIn(ExperimentalTypeInference::class) +@OverloadResolutionByLambdaReturnType inline fun Node.all( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree @@ -81,8 +91,37 @@ inline fun Node.all( nodes = nodes.filter(sel) } - val queryChildren = nodes.map(mustSatisfy) - return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList()) + val queryChildren = + nodes.map { n -> + val res = mustSatisfy(n) + res.stringRepresentation = "Starting at $n: " + res.stringRepresentation + res + } + return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList(), "all") +} + +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. The + * optional argument [sel] can be used to filter nodes for which the condition has to be fulfilled. + * + * This method can be used similar to the logical implication to test "sel => mustSatisfy". + */ +@ExperimentalGraph +@OptIn(ExperimentalTypeInference::class) +@OverloadResolutionByLambdaReturnType +inline fun TranslationResult.all( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean +): Pair> { + var nodes = this.graph.nodes.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val failedNodes = nodes.filterNot(mustSatisfy) as List + return Pair(failedNodes.isEmpty(), failedNodes) } /** @@ -104,8 +143,13 @@ inline fun TranslationResult.exists( nodes = nodes.filter(sel) } - val queryChildren = nodes.map(mustSatisfy) - return QueryTree(queryChildren.any { it.value }, queryChildren.toMutableList()) + val queryChildren = + nodes.map { n -> + val res = mustSatisfy(n) + res.stringRepresentation = "Starting at $n: " + res.stringRepresentation + res + } + return QueryTree(queryChildren.any { it.value }, queryChildren.toMutableList(), "exists") } /** @@ -127,8 +171,13 @@ inline fun Node.exists( nodes = nodes.filter(sel) } - val queryChildren = nodes.map(mustSatisfy) - return QueryTree(queryChildren.any { it.value }, queryChildren.toMutableList()) + val queryChildren = + nodes.map { n -> + val res = mustSatisfy(n) + res.stringRepresentation = "Starting at $n: " + res.stringRepresentation + res + } + return QueryTree(queryChildren.any { it.value }, queryChildren.toMutableList(), "exists") } /** @@ -136,9 +185,9 @@ inline fun Node.exists( * * @eval can be used to specify the evaluator but this method has to interpret the result correctly! */ -fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): QueryTree { - // The cast could potentially go wrong, but if its not an int, its not really a size - return QueryTree(eval.evaluate(n) as? Int ?: -1, mutableListOf(), "sizeof($n)") +fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): ComparableQueryTree { + // The cast could potentially go wrong, but if it's not an int, it's not really a size + return ComparableQueryTree(eval.evaluate(n) as? Int ?: -1, mutableListOf(), "sizeof($n)") } /** @@ -146,13 +195,13 @@ fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): QueryTree * * @eval can be used to specify the evaluator but this method has to interpret the result correctly! */ -fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { +fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): ComparableQueryTree<*> { val evalRes = eval.evaluate(n) - if (evalRes is Number) { - return QueryTree(evalRes, mutableListOf(QueryTree(n))) + if (evalRes is Comparable<*>) { + return ComparableQueryTree(evalRes, mutableListOf(QueryTree(n)), "min($n)") } // Extend this when we have other evaluators. - return QueryTree((evalRes as? NumberSet)?.min() ?: -1, mutableListOf(), "min($n)") + return ComparableQueryTree((evalRes as? NumberSet)?.min() ?: -1, mutableListOf(), "min($n)") } /** @@ -212,15 +261,15 @@ fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { +fun dataFlow(from: Node, to: Node): ComparableQueryTree { val evalRes = from.followNextDFGEdgesUntilHit { it == to } - return QueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) + return ComparableQueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) } /** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ -fun executionPath(from: Node, to: Node): QueryTree { +fun executionPath(from: Node, to: Node): ComparableQueryTree { val evalRes = from.followNextEOGEdgesUntilHit { it == to } - return QueryTree( + return ComparableQueryTree( evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList(), "executionPath($from, $to)" @@ -228,9 +277,9 @@ fun executionPath(from: Node, to: Node): QueryTree { } /** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ -fun executionPath(from: Node, predicate: (Node) -> Boolean): QueryTree { +fun executionPath(from: Node, predicate: (Node) -> Boolean): ComparableQueryTree { val evalRes = from.followNextEOGEdgesUntilHit(predicate) - return QueryTree( + return ComparableQueryTree( evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList(), "executionPath($from, $predicate)" @@ -243,7 +292,7 @@ operator fun Expression?.invoke(): QueryTree { } /** The size of this expression. It uses the default argument for `eval` of [size] */ -val Expression.size: QueryTree +val Expression.size: ComparableQueryTree get() { return sizeof(this) } @@ -274,8 +323,8 @@ val Expression.value: QueryTree * Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. The * result is interpreted as an integer. */ -val Expression.intValue: QueryTree? +val Expression.intValue: ComparableQueryTree? get() { val evalRes = evaluate() as? Int ?: return null - return QueryTree(evalRes, mutableListOf(), "$this") + return ComparableQueryTree(evalRes, mutableListOf(), "$this") } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index c49e05e927..649fd383be 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -56,74 +56,107 @@ import de.fraunhofer.aisec.cpg.graph.compareTo open class QueryTree( open var value: T, open val children: MutableList> = mutableListOf(), - open val stringRepresentation: String = "" + open var stringRepresentation: String = "" ) { fun printNicely(depth: Int = 0): String { var res = " ".repeat(depth) + "$stringRepresentation (==> $value)\n" + - "--------".repeat(depth + 1) + - "\n" + "--------".repeat(depth + 1) if (children.isNotEmpty()) { - children.forEach { - res += it.printNicely(depth + 2) + "\n" + "--------".repeat(depth + 1) + "\n" + res += "\n" + children.forEach { c -> + val next = c.printNicely(depth + 2) + if (next.isNotEmpty()) res += next + "\n" + "--------".repeat(depth + 1) + "\n" } } return res } /** Checks for equality of two [QueryTree]s. */ - infix fun eq(other: QueryTree): QueryTree { + infix fun eq(other: QueryTree): ComparableQueryTree { val result = this.value == other.value - return QueryTree(result, mutableListOf(this, other), "${this.value} == ${other.value}") + return ComparableQueryTree( + result, + mutableListOf(this, other), + "${this.value} == ${other.value}" + ) } /** * Checks for equality of a [QueryTree] with a value of the same type (e.g. useful to check for * constants). */ - infix fun eq(other: T): QueryTree { + infix fun eq(other: T): ComparableQueryTree { val result = this.value == other - return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} == $value") + return ComparableQueryTree( + result, + mutableListOf(this, QueryTree(other)), + "${this.value} == $value" + ) } /** Checks for inequality of two [QueryTree]s. */ - infix fun ne(other: QueryTree): QueryTree { + infix fun ne(other: QueryTree): ComparableQueryTree { val result = this.value != other.value - return QueryTree(result, mutableListOf(this, other), "${this.value} != ${other.value}") + return ComparableQueryTree( + result, + mutableListOf(this, other), + "${this.value} != ${other.value}" + ) } /** * Checks for inequality of a [QueryTree] with a value of the same type (e.g. useful to check * for constants). */ - infix fun ne(other: T): QueryTree { + infix fun ne(other: T): ComparableQueryTree { val result = this.value != other - return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} != $value") + return ComparableQueryTree( + result, + mutableListOf(this, QueryTree(other)), + "${this.value} != $value" + ) } /** Checks if the value is contained in the collection of the other [QueryTree]. */ - infix fun IN(other: QueryTree>): QueryTree { + infix fun IN(other: QueryTree>): ComparableQueryTree { val result = other.value.contains(this.value) - return QueryTree(result, mutableListOf(this, other), "${this.value} in ${other.value}") + return ComparableQueryTree( + result, + mutableListOf(this, other), + "${this.value} in ${other.value}" + ) } /** Checks if the value is contained in the collection [other]. */ - infix fun IN(other: Collection<*>): QueryTree { + infix fun IN(other: Collection<*>): ComparableQueryTree { val result = other.contains(this.value) - return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} in $other") + return ComparableQueryTree( + result, + mutableListOf(this, QueryTree(other)), + "${this.value} in $other" + ) } /** Checks if the value is a member of the type of the other [QueryTree]. */ - infix fun IS(other: QueryTree>): QueryTree { + infix fun IS(other: QueryTree>): ComparableQueryTree { val result = other.value.isInstance(this.value) - return QueryTree(result, mutableListOf(this, other), "${this.value} is ${other.value}") + return ComparableQueryTree( + result, + mutableListOf(this, other), + "${this.value} is ${other.value}" + ) } /** Checks if the value is a member of the type of [oter]. */ - infix fun IS(other: Class<*>): QueryTree { + infix fun IS(other: Class<*>): ComparableQueryTree { val result = other.isInstance(this.value) - return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} is $other") + return ComparableQueryTree( + result, + mutableListOf(this, QueryTree(other)), + "${this.value} is $other" + ) } override fun hashCode(): Int { @@ -139,9 +172,19 @@ open class QueryTree( } } +open class ComparableQueryTree>( + override var value: T, + override val children: MutableList> = mutableListOf(), + override var stringRepresentation: String = "" +) : QueryTree(value, children, stringRepresentation), Comparable> { + override fun compareTo(other: ComparableQueryTree): Int { + return this.value.compareTo(other.value) + } +} + /** Performs a logical and (&&) operation between the values of two [QueryTree]s. */ -infix fun QueryTree.and(other: QueryTree): QueryTree { - return QueryTree( +infix fun QueryTree.and(other: QueryTree): ComparableQueryTree { + return ComparableQueryTree( this.value && other.value, mutableListOf(this, other), stringRepresentation = "${this.value} && ${other.value}" @@ -149,8 +192,8 @@ infix fun QueryTree.and(other: QueryTree): QueryTree } /** Performs a logical or (||) operation between the values of two [QueryTree]s. */ -infix fun QueryTree.or(other: QueryTree): QueryTree { - return QueryTree( +infix fun QueryTree.or(other: QueryTree): ComparableQueryTree { + return ComparableQueryTree( this.value || other.value, mutableListOf(this, other), stringRepresentation = "${this.value} || ${other.value}" @@ -158,8 +201,8 @@ infix fun QueryTree.or(other: QueryTree): QueryTree { } /** Performs a logical xor operation between the values of two [QueryTree]s. */ -infix fun QueryTree.xor(other: QueryTree): QueryTree { - return QueryTree( +infix fun QueryTree.xor(other: QueryTree): ComparableQueryTree { + return ComparableQueryTree( this.value xor other.value, mutableListOf(this, other), stringRepresentation = "${this.value} xor ${other.value}" @@ -167,8 +210,8 @@ infix fun QueryTree.xor(other: QueryTree): QueryTree } /** Evaluates a logical implication (->) operation between the values of two [QueryTree]s. */ -infix fun QueryTree.implies(other: QueryTree): QueryTree { - return QueryTree( +infix fun QueryTree.implies(other: QueryTree): ComparableQueryTree { + return ComparableQueryTree( !this.value || other.value, mutableListOf(this, other), stringRepresentation = "${this.value} => ${other.value}" @@ -176,74 +219,114 @@ infix fun QueryTree.implies(other: QueryTree): QueryTree) [other]. */ -infix fun QueryTree.gt(other: QueryTree): QueryTree { +infix fun QueryTree.gt( + other: QueryTree +): ComparableQueryTree { val result = this.value.compareTo(other.value) > 0 - return QueryTree(result, mutableListOf(this, other), "${this.value} > ${other.value}") + return ComparableQueryTree(result, mutableListOf(this, other), "${this.value} > ${other.value}") } /** * Compares the numeric values of a [QueryTree] and another number for this being "greater than" (>) * [other]. */ -infix fun QueryTree.gt(other: Number): QueryTree { +infix fun QueryTree.gt(other: S): ComparableQueryTree { val result = this.value.compareTo(other) > 0 - return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} > $other") + return ComparableQueryTree( + result, + mutableListOf(this, QueryTree(other)), + "${this.value} > $other" + ) } /** * Compares the numeric values of two [QueryTree]s for this being "greater than or equal" (>=) * [other]. */ -infix fun QueryTree.ge(other: QueryTree): QueryTree { +infix fun QueryTree.ge( + other: QueryTree +): ComparableQueryTree { val result = this.value.compareTo(other.value) >= 0 - return QueryTree(result, mutableListOf(this, other), "${this.value} >= ${other.value}") + return ComparableQueryTree( + result, + mutableListOf(this, other), + "${this.value} >= ${other.value}" + ) } /** * Compares the numeric values of a [QueryTree] and another number for this being "greater than or * equal" (>=) [other]. */ -infix fun QueryTree.ge(other: Number): QueryTree { +infix fun QueryTree.ge(other: S): ComparableQueryTree { val result = this.value.compareTo(other) >= 0 - return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} >= $other") + return ComparableQueryTree( + result, + mutableListOf(this, QueryTree(other)), + "${this.value} >= $other" + ) } /** Compares the numeric values of two [QueryTree]s for this being "less than" (<) [other]. */ -infix fun QueryTree.lt(other: QueryTree): QueryTree { +infix fun QueryTree.lt( + other: QueryTree +): ComparableQueryTree { val result = this.value.compareTo(other.value) < 0 - return QueryTree(result, mutableListOf(this, other), "${this.value} < ${other.value}") + return ComparableQueryTree(result, mutableListOf(this, other), "${this.value} < ${other.value}") } /** * Compares the numeric values of a [QueryTree] and another number for this being "less than" (<) * [other]. */ -infix fun QueryTree.lt(other: Number): QueryTree { +infix fun QueryTree.lt(other: S): ComparableQueryTree { val result = this.value.compareTo(other) < 0 - return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} < $other") + return ComparableQueryTree( + result, + mutableListOf(this, QueryTree(other)), + "${this.value} < $other" + ) } /** * Compares the numeric values of two [QueryTree]s for this being "less than or equal" (=) [other]. */ -infix fun QueryTree.le(other: QueryTree): QueryTree { +infix fun QueryTree.le( + other: QueryTree +): ComparableQueryTree { val result = this.value.compareTo(other.value) <= 0 - return QueryTree(result, mutableListOf(this, other), "${this.value} <= ${other.value}") + return ComparableQueryTree( + result, + mutableListOf(this, other), + "${this.value} <= ${other.value}" + ) } /** * Compares the numeric values of a [QueryTree] and another number for this being "less than or * equal" (<=) [other]. */ -infix fun QueryTree.le(other: Number): QueryTree { +infix fun QueryTree.le(other: S): ComparableQueryTree { val result = this.value.compareTo(other) <= 0 - return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} <= $other") + return ComparableQueryTree( + result, + mutableListOf(this, QueryTree(other)), + "${this.value} <= $other" + ) } /** Negates the value of [arg] and returns the resulting [QueryTree]. */ -fun not(arg: QueryTree): QueryTree { +fun not(arg: QueryTree): ComparableQueryTree { val result = !arg.value - return QueryTree(result, mutableListOf(arg), "! ${arg.value}") + return ComparableQueryTree(result, mutableListOf(arg), "! ${arg.value}") +} + +/** + * This is a small wrapper to create a [QueryTree] containing a constant value, so that it can be + * used to in comparison with other [QueryTree] objects. + */ +fun > const(n: T): ComparableQueryTree { + return ComparableQueryTree(n, stringRepresentation = "$n") } /** diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/simple/SimpleQuery.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/simple/SimpleQuery.kt new file mode 100644 index 0000000000..872c7e8a36 --- /dev/null +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/simple/SimpleQuery.kt @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2022, 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.query.simple + +import de.fraunhofer.aisec.cpg.ExperimentalGraph +import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator +import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression + +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. The + * optional argument [sel] can be used to filter nodes for which the condition has to be fulfilled. + * + * This method can be used similar to the logical implication to test "sel => mustSatisfy". + */ +@ExperimentalGraph +inline fun TranslationResult.all( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean +): Pair> { + var nodes = this.graph.nodes.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val failedNodes = nodes.filterNot(mustSatisfy) + return Pair(failedNodes.isEmpty(), failedNodes) +} + +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. The + * optional argument [sel] can be used to filter nodes for which the condition has to be fulfilled. + * + * This method can be used similar to the logical implication to test "sel => mustSatisfy". + */ +@ExperimentalGraph +inline fun Node.all( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean +): Pair> { + val children = this.astChildren + + var nodes = children.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val failedNodes = nodes.filterNot(mustSatisfy) + return Pair(failedNodes.isEmpty(), failedNodes) +} + +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. + * The optional argument [sel] can be used to filter nodes which are considered during the + * evaluation. + */ +@ExperimentalGraph +inline fun TranslationResult.exists( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean +): Boolean { + var nodes = this.graph.nodes.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val queryChildren = nodes.map(mustSatisfy) + return queryChildren.any { it } +} + +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. + * The optional argument [sel] can be used to filter nodes which are considered during the + * evaluation. + */ +@ExperimentalGraph +inline fun Node.exists( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean +): Boolean { + var nodes = this.astChildren.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val queryChildren = nodes.map(mustSatisfy) + return queryChildren.any { it } +} + +/** + * Evaluates the size of a node. The implementation is very, very basic! + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ +fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): Int = + de.fraunhofer.aisec.cpg.query.sizeof(n, eval).value + +/** + * Retrieves the minimal value of the node. + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ +fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Number = + de.fraunhofer.aisec.cpg.query.min(n, eval).value + +/** + * Retrieves the minimal value of the nodes in the list. + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ +fun min(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Number = + de.fraunhofer.aisec.cpg.query.min(n, eval).value + +/** + * Retrieves the maximal value of the nodes in the list. + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ +fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Number = + de.fraunhofer.aisec.cpg.query.max(n, eval).value + +/** + * Retrieves the maximal value of the node. + * + * @eval can be used to specify the evaluator but this method has to interpret the result correctly! + */ +fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Number = + de.fraunhofer.aisec.cpg.query.max(n, eval).value + +/** Checks if a data flow is possible between the nodes [from] as a source and [to] as sink. */ +fun dataFlow(from: Node, to: Node): Boolean = de.fraunhofer.aisec.cpg.query.dataFlow(from, to).value + +/** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ +fun executionPath(from: Node, to: Node): Boolean = + de.fraunhofer.aisec.cpg.query.executionPath(from, to).value + +/** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ +fun executionPath(from: Node, predicate: (Node) -> Boolean): Boolean = + de.fraunhofer.aisec.cpg.query.executionPath(from, predicate).value + +/** Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. */ +operator fun Expression?.invoke(): Any? = this?.evaluate() + +/** The size of this expression. It uses the default argument for `eval` of [size] */ +val Expression.size: Number + get() { + return sizeof(this) + } + +/** + * The minimal integer value of this expression. It uses the default argument for `eval` of [min] + */ +val Expression.min: Number + get() { + return min(this) + } + +/** + * The maximal integer value of this expression. It uses the default argument for `eval` of [max] + */ +val Expression.max: Number + get() { + return max(this) + } + +/** Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. */ +val Expression.value: Any? + get() { + return evaluate() + } + +/** + * Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. The + * result is interpreted as an integer. + */ +val Expression.intValue: Number? + get() { + return evaluate() as? Int ?: return null + } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 563dee714d..98b0dc23aa 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -56,13 +56,20 @@ class QueryTest { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val queryTreeResult = + val mustSatisfy = { it: CallExpression -> + sizeof(it.arguments[0]) > sizeof(it.arguments[1]) + } + val queryTreeResult = result.all({ it.name == "memcpy" }, mustSatisfy) + + assertFalse(queryTreeResult.first) + + /*val queryTreeResult = result.all({ it.name == "memcpy" }) { sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) } assertFalse(queryTreeResult.value) - println(queryTreeResult.printNicely()) + println(queryTreeResult.printNicely())*/ // result.calls.name("memcpy").all { n -> sizeof(n.arguments[0]) >= sizeof(n.arguments[1]) } } From 17b5cb288f9f09f8969e378e31bf187e8d046764 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 9 Aug 2022 13:39:51 +0200 Subject: [PATCH 40/67] Comparable QueryTree --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 40 ++-- .../fraunhofer/aisec/cpg/query/QueryTree.kt | 176 +++++---------- .../aisec/cpg/query/simple/SimpleQuery.kt | 212 ------------------ .../aisec/cpg/analysis/QueryTest.kt | 170 +++++++++----- 4 files changed, 198 insertions(+), 400 deletions(-) delete mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/simple/SimpleQuery.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 7627ad79e8..27ff5f794f 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -107,8 +107,6 @@ inline fun Node.all( * This method can be used similar to the logical implication to test "sel => mustSatisfy". */ @ExperimentalGraph -@OptIn(ExperimentalTypeInference::class) -@OverloadResolutionByLambdaReturnType inline fun TranslationResult.all( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> Boolean @@ -185,9 +183,9 @@ inline fun Node.exists( * * @eval can be used to specify the evaluator but this method has to interpret the result correctly! */ -fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): ComparableQueryTree { +fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): QueryTree { // The cast could potentially go wrong, but if it's not an int, it's not really a size - return ComparableQueryTree(eval.evaluate(n) as? Int ?: -1, mutableListOf(), "sizeof($n)") + return QueryTree(eval.evaluate(n) as? Int ?: -1, mutableListOf(), "sizeof($n)") } /** @@ -195,13 +193,13 @@ fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): ComparableQueryTre * * @eval can be used to specify the evaluator but this method has to interpret the result correctly! */ -fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): ComparableQueryTree<*> { +fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { val evalRes = eval.evaluate(n) - if (evalRes is Comparable<*>) { - return ComparableQueryTree(evalRes, mutableListOf(QueryTree(n)), "min($n)") + if (evalRes is Number) { + return QueryTree(evalRes, mutableListOf(QueryTree(n)), "min($n)") } // Extend this when we have other evaluators. - return ComparableQueryTree((evalRes as? NumberSet)?.min() ?: -1, mutableListOf(), "min($n)") + return QueryTree((evalRes as? NumberSet)?.min() ?: -1, mutableListOf(), "min($n)") } /** @@ -261,15 +259,15 @@ fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { +fun dataFlow(from: Node, to: Node): QueryTree { val evalRes = from.followNextDFGEdgesUntilHit { it == to } - return ComparableQueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) + return QueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) } /** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ -fun executionPath(from: Node, to: Node): ComparableQueryTree { +fun executionPath(from: Node, to: Node): QueryTree { val evalRes = from.followNextEOGEdgesUntilHit { it == to } - return ComparableQueryTree( + return QueryTree( evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList(), "executionPath($from, $to)" @@ -277,9 +275,9 @@ fun executionPath(from: Node, to: Node): ComparableQueryTree { } /** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ -fun executionPath(from: Node, predicate: (Node) -> Boolean): ComparableQueryTree { +fun executionPath(from: Node, predicate: (Node) -> Boolean): QueryTree { val evalRes = from.followNextEOGEdgesUntilHit(predicate) - return ComparableQueryTree( + return QueryTree( evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList(), "executionPath($from, $predicate)" @@ -292,7 +290,7 @@ operator fun Expression?.invoke(): QueryTree { } /** The size of this expression. It uses the default argument for `eval` of [size] */ -val Expression.size: ComparableQueryTree +val Expression.size: QueryTree get() { return sizeof(this) } @@ -300,10 +298,10 @@ val Expression.size: ComparableQueryTree /** * The minimal integer value of this expression. It uses the default argument for `eval` of [min] */ -val Expression.min: QueryTree - get() { - return min(this) - } +/*val Expression.min: QueryTree +get() { + return min(this) +}*/ /** * The maximal integer value of this expression. It uses the default argument for `eval` of [max] @@ -323,8 +321,8 @@ val Expression.value: QueryTree * Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. The * result is interpreted as an integer. */ -val Expression.intValue: ComparableQueryTree? +val Expression.intValue: QueryTree? get() { val evalRes = evaluate() as? Int ?: return null - return ComparableQueryTree(evalRes, mutableListOf(), "$this") + return QueryTree(evalRes, mutableListOf(), "$this") } diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index 649fd383be..81d3c048dd 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -57,7 +57,7 @@ open class QueryTree( open var value: T, open val children: MutableList> = mutableListOf(), open var stringRepresentation: String = "" -) { +) : Comparable> { fun printNicely(depth: Int = 0): String { var res = " ".repeat(depth) + @@ -74,89 +74,57 @@ open class QueryTree( } /** Checks for equality of two [QueryTree]s. */ - infix fun eq(other: QueryTree): ComparableQueryTree { + infix fun eq(other: QueryTree): QueryTree { val result = this.value == other.value - return ComparableQueryTree( - result, - mutableListOf(this, other), - "${this.value} == ${other.value}" - ) + return QueryTree(result, mutableListOf(this, other), "${this.value} == ${other.value}") } /** * Checks for equality of a [QueryTree] with a value of the same type (e.g. useful to check for * constants). */ - infix fun eq(other: T): ComparableQueryTree { + infix fun eq(other: T): QueryTree { val result = this.value == other - return ComparableQueryTree( - result, - mutableListOf(this, QueryTree(other)), - "${this.value} == $value" - ) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} == $value") } /** Checks for inequality of two [QueryTree]s. */ - infix fun ne(other: QueryTree): ComparableQueryTree { + infix fun ne(other: QueryTree): QueryTree { val result = this.value != other.value - return ComparableQueryTree( - result, - mutableListOf(this, other), - "${this.value} != ${other.value}" - ) + return QueryTree(result, mutableListOf(this, other), "${this.value} != ${other.value}") } /** * Checks for inequality of a [QueryTree] with a value of the same type (e.g. useful to check * for constants). */ - infix fun ne(other: T): ComparableQueryTree { + infix fun ne(other: T): QueryTree { val result = this.value != other - return ComparableQueryTree( - result, - mutableListOf(this, QueryTree(other)), - "${this.value} != $value" - ) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} != $value") } /** Checks if the value is contained in the collection of the other [QueryTree]. */ - infix fun IN(other: QueryTree>): ComparableQueryTree { + infix fun IN(other: QueryTree>): QueryTree { val result = other.value.contains(this.value) - return ComparableQueryTree( - result, - mutableListOf(this, other), - "${this.value} in ${other.value}" - ) + return QueryTree(result, mutableListOf(this, other), "${this.value} in ${other.value}") } /** Checks if the value is contained in the collection [other]. */ - infix fun IN(other: Collection<*>): ComparableQueryTree { + infix fun IN(other: Collection<*>): QueryTree { val result = other.contains(this.value) - return ComparableQueryTree( - result, - mutableListOf(this, QueryTree(other)), - "${this.value} in $other" - ) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} in $other") } /** Checks if the value is a member of the type of the other [QueryTree]. */ - infix fun IS(other: QueryTree>): ComparableQueryTree { + infix fun IS(other: QueryTree>): QueryTree { val result = other.value.isInstance(this.value) - return ComparableQueryTree( - result, - mutableListOf(this, other), - "${this.value} is ${other.value}" - ) + return QueryTree(result, mutableListOf(this, other), "${this.value} is ${other.value}") } /** Checks if the value is a member of the type of [oter]. */ - infix fun IS(other: Class<*>): ComparableQueryTree { + infix fun IS(other: Class<*>): QueryTree { val result = other.isInstance(this.value) - return ComparableQueryTree( - result, - mutableListOf(this, QueryTree(other)), - "${this.value} is $other" - ) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} is $other") } override fun hashCode(): Int { @@ -170,21 +138,27 @@ open class QueryTree( return super.equals(other) } -} -open class ComparableQueryTree>( - override var value: T, - override val children: MutableList> = mutableListOf(), - override var stringRepresentation: String = "" -) : QueryTree(value, children, stringRepresentation), Comparable> { - override fun compareTo(other: ComparableQueryTree): Int { - return this.value.compareTo(other.value) + override fun compareTo(other: QueryTree): Int { + if (this.value is Number && other.value is Number) { + return (this.value as Number).compareTo(other.value as Number) + } else if (this.value is Comparable<*> && other.value is Comparable<*>) { + return (this.value as Comparable).compareTo(other.value as Any) + } + return -1 + } + + public operator fun compareTo(other: Number): Int { + if (this.value is Number) { + return (this.value as Number).compareTo(other) + } + return -1 } } /** Performs a logical and (&&) operation between the values of two [QueryTree]s. */ -infix fun QueryTree.and(other: QueryTree): ComparableQueryTree { - return ComparableQueryTree( +infix fun QueryTree.and(other: QueryTree): QueryTree { + return QueryTree( this.value && other.value, mutableListOf(this, other), stringRepresentation = "${this.value} && ${other.value}" @@ -192,8 +166,8 @@ infix fun QueryTree.and(other: QueryTree): ComparableQueryTree } /** Performs a logical or (||) operation between the values of two [QueryTree]s. */ -infix fun QueryTree.or(other: QueryTree): ComparableQueryTree { - return ComparableQueryTree( +infix fun QueryTree.or(other: QueryTree): QueryTree { + return QueryTree( this.value || other.value, mutableListOf(this, other), stringRepresentation = "${this.value} || ${other.value}" @@ -201,8 +175,8 @@ infix fun QueryTree.or(other: QueryTree): ComparableQueryTree< } /** Performs a logical xor operation between the values of two [QueryTree]s. */ -infix fun QueryTree.xor(other: QueryTree): ComparableQueryTree { - return ComparableQueryTree( +infix fun QueryTree.xor(other: QueryTree): QueryTree { + return QueryTree( this.value xor other.value, mutableListOf(this, other), stringRepresentation = "${this.value} xor ${other.value}" @@ -210,8 +184,8 @@ infix fun QueryTree.xor(other: QueryTree): ComparableQueryTree } /** Evaluates a logical implication (->) operation between the values of two [QueryTree]s. */ -infix fun QueryTree.implies(other: QueryTree): ComparableQueryTree { - return ComparableQueryTree( +infix fun QueryTree.implies(other: QueryTree): QueryTree { + return QueryTree( !this.value || other.value, mutableListOf(this, other), stringRepresentation = "${this.value} => ${other.value}" @@ -219,114 +193,82 @@ infix fun QueryTree.implies(other: QueryTree): ComparableQuery } /** Compares the numeric values of two [QueryTree]s for this being "greater than" (>) [other]. */ -infix fun QueryTree.gt( - other: QueryTree -): ComparableQueryTree { +infix fun QueryTree.gt(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) > 0 - return ComparableQueryTree(result, mutableListOf(this, other), "${this.value} > ${other.value}") + return QueryTree(result, mutableListOf(this, other), "${this.value} > ${other.value}") } /** * Compares the numeric values of a [QueryTree] and another number for this being "greater than" (>) * [other]. */ -infix fun QueryTree.gt(other: S): ComparableQueryTree { +infix fun QueryTree.gt(other: S): QueryTree { val result = this.value.compareTo(other) > 0 - return ComparableQueryTree( - result, - mutableListOf(this, QueryTree(other)), - "${this.value} > $other" - ) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} > $other") } /** * Compares the numeric values of two [QueryTree]s for this being "greater than or equal" (>=) * [other]. */ -infix fun QueryTree.ge( - other: QueryTree -): ComparableQueryTree { +infix fun QueryTree.ge(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) >= 0 - return ComparableQueryTree( - result, - mutableListOf(this, other), - "${this.value} >= ${other.value}" - ) + return QueryTree(result, mutableListOf(this, other), "${this.value} >= ${other.value}") } /** * Compares the numeric values of a [QueryTree] and another number for this being "greater than or * equal" (>=) [other]. */ -infix fun QueryTree.ge(other: S): ComparableQueryTree { +infix fun QueryTree.ge(other: S): QueryTree { val result = this.value.compareTo(other) >= 0 - return ComparableQueryTree( - result, - mutableListOf(this, QueryTree(other)), - "${this.value} >= $other" - ) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} >= $other") } /** Compares the numeric values of two [QueryTree]s for this being "less than" (<) [other]. */ -infix fun QueryTree.lt( - other: QueryTree -): ComparableQueryTree { +infix fun QueryTree.lt(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) < 0 - return ComparableQueryTree(result, mutableListOf(this, other), "${this.value} < ${other.value}") + return QueryTree(result, mutableListOf(this, other), "${this.value} < ${other.value}") } /** * Compares the numeric values of a [QueryTree] and another number for this being "less than" (<) * [other]. */ -infix fun QueryTree.lt(other: S): ComparableQueryTree { +infix fun QueryTree.lt(other: S): QueryTree { val result = this.value.compareTo(other) < 0 - return ComparableQueryTree( - result, - mutableListOf(this, QueryTree(other)), - "${this.value} < $other" - ) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} < $other") } /** * Compares the numeric values of two [QueryTree]s for this being "less than or equal" (=) [other]. */ -infix fun QueryTree.le( - other: QueryTree -): ComparableQueryTree { +infix fun QueryTree.le(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) <= 0 - return ComparableQueryTree( - result, - mutableListOf(this, other), - "${this.value} <= ${other.value}" - ) + return QueryTree(result, mutableListOf(this, other), "${this.value} <= ${other.value}") } /** * Compares the numeric values of a [QueryTree] and another number for this being "less than or * equal" (<=) [other]. */ -infix fun QueryTree.le(other: S): ComparableQueryTree { +infix fun QueryTree.le(other: S): QueryTree { val result = this.value.compareTo(other) <= 0 - return ComparableQueryTree( - result, - mutableListOf(this, QueryTree(other)), - "${this.value} <= $other" - ) + return QueryTree(result, mutableListOf(this, QueryTree(other)), "${this.value} <= $other") } /** Negates the value of [arg] and returns the resulting [QueryTree]. */ -fun not(arg: QueryTree): ComparableQueryTree { +fun not(arg: QueryTree): QueryTree { val result = !arg.value - return ComparableQueryTree(result, mutableListOf(arg), "! ${arg.value}") + return QueryTree(result, mutableListOf(arg), "! ${arg.value}") } /** * This is a small wrapper to create a [QueryTree] containing a constant value, so that it can be * used to in comparison with other [QueryTree] objects. */ -fun > const(n: T): ComparableQueryTree { - return ComparableQueryTree(n, stringRepresentation = "$n") +fun > const(n: T): QueryTree { + return QueryTree(n, stringRepresentation = "$n") } /** diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/simple/SimpleQuery.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/simple/SimpleQuery.kt deleted file mode 100644 index 872c7e8a36..0000000000 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/simple/SimpleQuery.kt +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (c) 2022, 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.query.simple - -import de.fraunhofer.aisec.cpg.ExperimentalGraph -import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator -import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator -import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression - -/** - * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. The - * optional argument [sel] can be used to filter nodes for which the condition has to be fulfilled. - * - * This method can be used similar to the logical implication to test "sel => mustSatisfy". - */ -@ExperimentalGraph -inline fun TranslationResult.all( - noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> Boolean -): Pair> { - var nodes = this.graph.nodes.filterIsInstance() - - // filter the nodes according to the selector - if (sel != null) { - nodes = nodes.filter(sel) - } - - val failedNodes = nodes.filterNot(mustSatisfy) - return Pair(failedNodes.isEmpty(), failedNodes) -} - -/** - * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. The - * optional argument [sel] can be used to filter nodes for which the condition has to be fulfilled. - * - * This method can be used similar to the logical implication to test "sel => mustSatisfy". - */ -@ExperimentalGraph -inline fun Node.all( - noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> Boolean -): Pair> { - val children = this.astChildren - - var nodes = children.filterIsInstance() - - // filter the nodes according to the selector - if (sel != null) { - nodes = nodes.filter(sel) - } - - val failedNodes = nodes.filterNot(mustSatisfy) - return Pair(failedNodes.isEmpty(), failedNodes) -} - -/** - * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. - * The optional argument [sel] can be used to filter nodes which are considered during the - * evaluation. - */ -@ExperimentalGraph -inline fun TranslationResult.exists( - noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> Boolean -): Boolean { - var nodes = this.graph.nodes.filterIsInstance() - - // filter the nodes according to the selector - if (sel != null) { - nodes = nodes.filter(sel) - } - - val queryChildren = nodes.map(mustSatisfy) - return queryChildren.any { it } -} - -/** - * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. - * The optional argument [sel] can be used to filter nodes which are considered during the - * evaluation. - */ -@ExperimentalGraph -inline fun Node.exists( - noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> Boolean -): Boolean { - var nodes = this.astChildren.filterIsInstance() - - // filter the nodes according to the selector - if (sel != null) { - nodes = nodes.filter(sel) - } - - val queryChildren = nodes.map(mustSatisfy) - return queryChildren.any { it } -} - -/** - * Evaluates the size of a node. The implementation is very, very basic! - * - * @eval can be used to specify the evaluator but this method has to interpret the result correctly! - */ -fun sizeof(n: Node?, eval: ValueEvaluator = SizeEvaluator()): Int = - de.fraunhofer.aisec.cpg.query.sizeof(n, eval).value - -/** - * Retrieves the minimal value of the node. - * - * @eval can be used to specify the evaluator but this method has to interpret the result correctly! - */ -fun min(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Number = - de.fraunhofer.aisec.cpg.query.min(n, eval).value - -/** - * Retrieves the minimal value of the nodes in the list. - * - * @eval can be used to specify the evaluator but this method has to interpret the result correctly! - */ -fun min(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Number = - de.fraunhofer.aisec.cpg.query.min(n, eval).value - -/** - * Retrieves the maximal value of the nodes in the list. - * - * @eval can be used to specify the evaluator but this method has to interpret the result correctly! - */ -fun max(n: List?, eval: ValueEvaluator = MultiValueEvaluator()): Number = - de.fraunhofer.aisec.cpg.query.max(n, eval).value - -/** - * Retrieves the maximal value of the node. - * - * @eval can be used to specify the evaluator but this method has to interpret the result correctly! - */ -fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): Number = - de.fraunhofer.aisec.cpg.query.max(n, eval).value - -/** Checks if a data flow is possible between the nodes [from] as a source and [to] as sink. */ -fun dataFlow(from: Node, to: Node): Boolean = de.fraunhofer.aisec.cpg.query.dataFlow(from, to).value - -/** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ -fun executionPath(from: Node, to: Node): Boolean = - de.fraunhofer.aisec.cpg.query.executionPath(from, to).value - -/** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ -fun executionPath(from: Node, predicate: (Node) -> Boolean): Boolean = - de.fraunhofer.aisec.cpg.query.executionPath(from, predicate).value - -/** Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. */ -operator fun Expression?.invoke(): Any? = this?.evaluate() - -/** The size of this expression. It uses the default argument for `eval` of [size] */ -val Expression.size: Number - get() { - return sizeof(this) - } - -/** - * The minimal integer value of this expression. It uses the default argument for `eval` of [min] - */ -val Expression.min: Number - get() { - return min(this) - } - -/** - * The maximal integer value of this expression. It uses the default argument for `eval` of [max] - */ -val Expression.max: Number - get() { - return max(this) - } - -/** Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. */ -val Expression.value: Any? - get() { - return evaluate() - } - -/** - * Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. The - * result is interpreted as an integer. - */ -val Expression.intValue: Number? - get() { - return evaluate() as? Int ?: return null - } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 98b0dc23aa..42a9429401 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -56,20 +56,21 @@ class QueryTest { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val mustSatisfy = { it: CallExpression -> - sizeof(it.arguments[0]) > sizeof(it.arguments[1]) - } - val queryTreeResult = result.all({ it.name == "memcpy" }, mustSatisfy) + val queryTreeResult = + result.all({ it.name == "memcpy" }) { + sizeof(it.arguments[0]) > sizeof(it.arguments[1]) + } assertFalse(queryTreeResult.first) - /*val queryTreeResult = - result.all({ it.name == "memcpy" }) { - sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) - } + val mustSatisfy = { it: CallExpression -> + sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) + } + val queryTreeResult2: QueryTree = + result.all({ it.name == "memcpy" }, mustSatisfy) - assertFalse(queryTreeResult.value) - println(queryTreeResult.printNicely())*/ + assertFalse(queryTreeResult2.value) + println(queryTreeResult2.printNicely()) // result.calls.name("memcpy").all { n -> sizeof(n.arguments[0]) >= sizeof(n.arguments[1]) } } @@ -88,10 +89,14 @@ class QueryTest { val queryTreeResult = result.all({ it.name == "memcpy" }) { - it.arguments[0].size gt it.arguments[1].size + it.arguments[0].size > it.arguments[1].size } + assertFalse(queryTreeResult.first) - assertFalse(queryTreeResult.value) + val mustSatisfy = { it: CallExpression -> it.arguments[0].size gt it.arguments[1].size } + val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + + assertFalse(queryTreeResult2.value) } @Test @@ -108,16 +113,27 @@ class QueryTest { val queryTreeResult = result.all({ it.name == "free" }) { outer -> - not( - executionPath(outer) { + !executionPath(outer) { (it as? DeclaredReferenceExpression)?.refersTo == (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo } - ) + .value } + assertFalse(queryTreeResult.first) + println(queryTreeResult.second) + + val mustSatisfy = { outer: CallExpression -> + not( + executionPath(outer) { + (it as? DeclaredReferenceExpression)?.refersTo == + (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + } + ) + } + val queryTreeResult2 = result.all({ it.name == "free" }, mustSatisfy) - assertFalse(queryTreeResult.value) - println(queryTreeResult.printNicely()) + assertFalse(queryTreeResult2.value) + println(queryTreeResult2.printNicely()) } @Test @@ -133,9 +149,15 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!! eq 11 } + result.all({ it.name == "memcpy" }) { + it.arguments[2].intValue!! == const(11) + } + assertTrue(queryTreeResult.first) + + val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! eq 11 } + val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) - assertTrue(queryTreeResult.value) + assertTrue(queryTreeResult2.value) } @Test @@ -150,9 +172,14 @@ class QueryTest { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val queryTreeResult = result.all { it.value.invoke() as QueryTree lt 5 } + val queryTreeResult = + result.all(mustSatisfy = { (it.value.invoke() as QueryTree) < 5 }) + assertTrue(queryTreeResult.first) + + val mustSatisfy = { it: Assignment -> it.value.invoke() as QueryTree lt 5 } + val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) - assertTrue(queryTreeResult.value) + assertTrue(queryTreeResult2.value) } @Test @@ -168,11 +195,19 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all { - (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) ge 0) - } - assertFalse(queryTreeResult.value) - println(queryTreeResult.printNicely()) + result.all( + mustSatisfy = { + max(it.subscriptExpression) < min(it.size) && min(it.subscriptExpression) > 0 + } + ) + assertFalse(queryTreeResult.first) + + val mustSatisfy = { it: ArraySubscriptionExpression -> + (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) gt 0) + } + val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) + assertFalse(queryTreeResult2.value) + println(queryTreeResult2.printNicely()) } @Test @@ -189,11 +224,19 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all { - (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) ge 0) - } - assertFalse(queryTreeResult.value) - println(queryTreeResult.printNicely()) + result.all( + mustSatisfy = { + max(it.subscriptExpression) < min(it.size) && min(it.subscriptExpression) >= 0 + } + ) + assertFalse(queryTreeResult.first) + + val mustSatisfy = { it: ArraySubscriptionExpression -> + (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) ge 0) + } + val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) + assertFalse(queryTreeResult2.value) + println(queryTreeResult2.printNicely()) } @Test @@ -210,17 +253,33 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all { - (max(it.subscriptExpression) lt - min( - ((it.arrayExpression as DeclaredReferenceExpression).refersTo - as VariableDeclaration) - .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } - .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } - )) and (min(it.subscriptExpression) ge 0) - } - assertFalse(queryTreeResult.value) - println(queryTreeResult) + result.all( + mustSatisfy = { + max(it.subscriptExpression) < + min( + ((it.arrayExpression as DeclaredReferenceExpression).refersTo + as VariableDeclaration) + .followPrevDFGEdgesUntilHit { node -> + node is ArrayCreationExpression + } + .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } + ) && min(it.subscriptExpression) > 0 + } + ) + assertFalse(queryTreeResult.first) + + val mustSatisfy = { it: ArraySubscriptionExpression -> + (max(it.subscriptExpression) lt + min( + ((it.arrayExpression as DeclaredReferenceExpression).refersTo + as VariableDeclaration) + .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } + .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } + )) and (min(it.subscriptExpression) ge 0) + } + val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) + assertFalse(queryTreeResult2.value) + println(queryTreeResult2.printNicely()) } @Test @@ -237,13 +296,24 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all { - val max_sub = max(it.subscriptExpression) - val min_dim = min(it.size) - val min_sub = min(it.subscriptExpression) - return@all (max_sub lt min_dim) and (min_sub ge 0) - } - assertTrue(queryTreeResult.value) - println(queryTreeResult) + result.all( + mustSatisfy = { + val max_sub = max(it.subscriptExpression) + val min_dim = min(it.size) + val min_sub = min(it.subscriptExpression) + return@all max_sub < min_dim && min_sub >= 0 + } + ) + assertTrue(queryTreeResult.first) + + val mustSatisfy = { it: ArraySubscriptionExpression -> + val max_sub = max(it.subscriptExpression) + val min_dim = min(it.size) + val min_sub = min(it.subscriptExpression) + (max_sub lt min_dim) and (min_sub ge 0) + } + val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) + assertTrue(queryTreeResult2.value) + println(queryTreeResult2.printNicely()) } } From 2bea4b85941bb8adbdd5642edc6e3fc03e561a3b Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 9 Aug 2022 14:41:43 +0200 Subject: [PATCH 41/67] More intuitive extensions for the Query API --- .../aisec/cpg/query/QueryShortcuts.kt | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt index d869a8ea58..9fc709a61b 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt @@ -27,9 +27,14 @@ package de.fraunhofer.aisec.cpg.query import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.graph +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement +import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.passes.astParent /** Returns all [CallExpression]s in this graph. */ @OptIn(ExperimentalGraph::class) @@ -38,12 +43,55 @@ val TranslationResult.calls: List /** Returns all [CallExpression]s in this graph which call a method with the given [name]. */ @OptIn(ExperimentalGraph::class) -fun TranslationResult.callByName(name: String): List { +fun TranslationResult.callsByName(name: String): List { return this.graph.nodes.filter { node -> (node as? CallExpression)?.invokes?.any { it.name == name } == true } as List } +/** Set of all functions which are called from this function */ +val FunctionDeclaration.callees: Set + get() { + return this.body.astChildren + .filterIsInstance() + .map { it.invokes } + .foldRight( + mutableListOf(), + { l, res -> + res.addAll(l) + res + } + ) + .toSet() + } + +/** Set of all functions calling [function] */ +@OptIn(ExperimentalGraph::class) +fun TranslationResult.callersOf(function: FunctionDeclaration): Set { + return this.graph.nodes + .filterIsInstance() + .filter { function in it.callees } + .toSet() +} + +/** All nodes which depend on this if statement */ +fun IfStatement.controls(): List { + return this.astChildren +} + +/** All nodes which depend on this if statement */ +fun Node.controlledBy(): List { + val result = mutableListOf() + if ( + this.astParent != null && + (this.astParent is IfStatement || this.astParent is SwitchStatement) + ) { + result.add(this.astParent!!) + result.addAll(this.astParent!!.controlledBy()) + } + return result +} + /** * Filters a list of [CallExpression]s for expressions which call a method with the given [name]. */ From 2c0c253e5d7e1f9a007678b359e359502c8938ea Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 9 Aug 2022 15:18:11 +0200 Subject: [PATCH 42/67] Simplified exists --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 27ff5f794f..e43a9931c8 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -122,6 +122,32 @@ inline fun TranslationResult.all( return Pair(failedNodes.isEmpty(), failedNodes) } +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. + * + * The optional argument [sel] can be used to filter nodes for which the condition has to be + * fulfilled. + * + * This method can be used similar to the logical implication to test "sel => mustSatisfy". + */ +@ExperimentalGraph +inline fun Node.all( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean +): Pair> { + val children = this.astChildren + + var nodes = children.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val failedNodes = nodes.filterNot(mustSatisfy) as List + return Pair(failedNodes.isEmpty(), failedNodes) +} + /** * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. * @@ -130,6 +156,8 @@ inline fun TranslationResult.all( * the resulting reasoning chain. */ @ExperimentalGraph +@OptIn(ExperimentalTypeInference::class) +@OverloadResolutionByLambdaReturnType inline fun TranslationResult.exists( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree @@ -158,6 +186,8 @@ inline fun TranslationResult.exists( * the resulting reasoning chain. */ @ExperimentalGraph +@OptIn(ExperimentalTypeInference::class) +@OverloadResolutionByLambdaReturnType inline fun Node.exists( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree @@ -178,6 +208,48 @@ inline fun Node.exists( return QueryTree(queryChildren.any { it.value }, queryChildren.toMutableList(), "exists") } +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. + * The optional argument [sel] can be used to filter nodes which are considered during the + * evaluation. + */ +@ExperimentalGraph +inline fun TranslationResult.exists( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean +): Pair> { + var nodes = this.graph.nodes.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val queryChildren = nodes.filter(mustSatisfy) as List + return Pair(queryChildren.isNotEmpty(), queryChildren) +} + +/** + * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. + * The optional argument [sel] can be used to filter nodes which are considered during the + * evaluation. + */ +@ExperimentalGraph +inline fun Node.exists( + noinline sel: ((T) -> Boolean)? = null, + noinline mustSatisfy: (T) -> Boolean +): Pair> { + var nodes = this.astChildren.filterIsInstance() + + // filter the nodes according to the selector + if (sel != null) { + nodes = nodes.filter(sel) + } + + val queryChildren = nodes.filter(mustSatisfy) as List + return Pair(queryChildren.isNotEmpty(), queryChildren) +} + /** * Evaluates the size of a node. The implementation is very, very basic! * From 933740f562f6e75f456e477f7e820084432a64cc Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 9 Aug 2022 15:23:06 +0200 Subject: [PATCH 43/67] Throw exception if we cannot compare anything --- .../main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index 81d3c048dd..35f42bdeea 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -145,14 +145,14 @@ open class QueryTree( } else if (this.value is Comparable<*> && other.value is Comparable<*>) { return (this.value as Comparable).compareTo(other.value as Any) } - return -1 + throw QueryException("Cannot compare objects of type ${this.value} and ${other.value}") } public operator fun compareTo(other: Number): Int { if (this.value is Number) { return (this.value as Number).compareTo(other) } - return -1 + throw QueryException("Cannot compare objects of type ${this.value} and $other") } } @@ -278,3 +278,5 @@ fun > const(n: T): QueryTree { fun const(n: T): QueryTree { return QueryTree(n, stringRepresentation = "$n") } + +class QueryException(override val message: String) : Exception(message) From e1106f24f70b8aefda931d3b1d10e15a9edd8702 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 9 Aug 2022 15:50:40 +0200 Subject: [PATCH 44/67] Fix --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 139 +++--------------- .../aisec/cpg/analysis/QueryTest.kt | 7 +- 2 files changed, 28 insertions(+), 118 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index e43a9931c8..4092c50afc 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -34,38 +34,6 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import kotlin.experimental.ExperimentalTypeInference -/** - * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. - * - * The optional argument [sel] can be used to filter nodes for which the condition has to be - * fulfilled. This filter should be rather simple in most cases since its evaluation is not part of - * the resulting reasoning chain. - * - * This method can be used similar to the logical implication to test "sel => mustSatisfy". - */ -@ExperimentalGraph -@OptIn(ExperimentalTypeInference::class) -@OverloadResolutionByLambdaReturnType -inline fun TranslationResult.all( - noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> QueryTree -): QueryTree { - var nodes = this.graph.nodes.filterIsInstance() - - // filter the nodes according to the selector - if (sel != null) { - nodes = nodes.filter(sel) - } - - val queryChildren = - nodes.map { n -> - val res = mustSatisfy(n) - res.stringRepresentation = "Starting at $n: " + res.stringRepresentation - res - } - return QueryTree(queryChildren.all { it.value }, queryChildren.toMutableList(), "all") -} - /** * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. * @@ -82,9 +50,12 @@ inline fun Node.all( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree ): QueryTree { - val children = this.astChildren - - var nodes = children.filterIsInstance() + var nodes = + if (this is TranslationResult) { + this.graph.nodes.filterIsInstance() + } else { + this.astChildren.filterIsInstance() + } // filter the nodes according to the selector if (sel != null) { @@ -107,37 +78,16 @@ inline fun Node.all( * This method can be used similar to the logical implication to test "sel => mustSatisfy". */ @ExperimentalGraph -inline fun TranslationResult.all( - noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> Boolean -): Pair> { - var nodes = this.graph.nodes.filterIsInstance() - - // filter the nodes according to the selector - if (sel != null) { - nodes = nodes.filter(sel) - } - - val failedNodes = nodes.filterNot(mustSatisfy) as List - return Pair(failedNodes.isEmpty(), failedNodes) -} - -/** - * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. - * - * The optional argument [sel] can be used to filter nodes for which the condition has to be - * fulfilled. - * - * This method can be used similar to the logical implication to test "sel => mustSatisfy". - */ -@ExperimentalGraph inline fun Node.all( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> Boolean ): Pair> { - val children = this.astChildren - - var nodes = children.filterIsInstance() + var nodes = + if (this is TranslationResult) { + this.graph.nodes.filterIsInstance() + } else { + this.astChildren.filterIsInstance() + } // filter the nodes according to the selector if (sel != null) { @@ -148,36 +98,6 @@ inline fun Node.all( return Pair(failedNodes.isEmpty(), failedNodes) } -/** - * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. - * - * The optional argument [sel] can be used to filter nodes which are considered during the - * evaluation. This filter should be rather simple in most cases since its evaluation is not part of - * the resulting reasoning chain. - */ -@ExperimentalGraph -@OptIn(ExperimentalTypeInference::class) -@OverloadResolutionByLambdaReturnType -inline fun TranslationResult.exists( - noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> QueryTree -): QueryTree { - var nodes = this.graph.nodes.filterIsInstance() - - // filter the nodes according to the selector - if (sel != null) { - nodes = nodes.filter(sel) - } - - val queryChildren = - nodes.map { n -> - val res = mustSatisfy(n) - res.stringRepresentation = "Starting at $n: " + res.stringRepresentation - res - } - return QueryTree(queryChildren.any { it.value }, queryChildren.toMutableList(), "exists") -} - /** * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. * @@ -192,7 +112,12 @@ inline fun Node.exists( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree ): QueryTree { - var nodes = this.astChildren.filterIsInstance() + var nodes = + if (this is TranslationResult) { + this.graph.nodes.filterIsInstance() + } else { + this.astChildren.filterIsInstance() + } // filter the nodes according to the selector if (sel != null) { @@ -208,27 +133,6 @@ inline fun Node.exists( return QueryTree(queryChildren.any { it.value }, queryChildren.toMutableList(), "exists") } -/** - * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. - * The optional argument [sel] can be used to filter nodes which are considered during the - * evaluation. - */ -@ExperimentalGraph -inline fun TranslationResult.exists( - noinline sel: ((T) -> Boolean)? = null, - noinline mustSatisfy: (T) -> Boolean -): Pair> { - var nodes = this.graph.nodes.filterIsInstance() - - // filter the nodes according to the selector - if (sel != null) { - nodes = nodes.filter(sel) - } - - val queryChildren = nodes.filter(mustSatisfy) as List - return Pair(queryChildren.isNotEmpty(), queryChildren) -} - /** * Evaluates if the conditions specified in [mustSatisfy] hold for at least one node in the graph. * The optional argument [sel] can be used to filter nodes which are considered during the @@ -239,7 +143,12 @@ inline fun Node.exists( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> Boolean ): Pair> { - var nodes = this.astChildren.filterIsInstance() + var nodes = + if (this is TranslationResult) { + this.graph.nodes.filterIsInstance() + } else { + this.astChildren.filterIsInstance() + } // filter the nodes according to the selector if (sel != null) { diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 42a9429401..61db0b772e 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -57,9 +57,10 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { - sizeof(it.arguments[0]) > sizeof(it.arguments[1]) - } + result.all( + { it.name == "memcpy" }, + { sizeof(it.arguments[0]) > sizeof(it.arguments[1]) } + ) assertFalse(queryTreeResult.first) From db8a15441186eb2735d06f88cd8d3ae5d6b09bc4 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 10 Aug 2022 11:43:15 +0200 Subject: [PATCH 45/67] Test for multi value evaluator, fixes --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 16 +- .../aisec/cpg/analysis/NumberSet.kt | 2 +- .../aisec/cpg/analysis/ValueEvaluator.kt | 4 +- .../cpg/analysis/MultiValueEvaluatorTest.kt | 184 ++++++++++++++++++ .../resources/value_evaluation/cfexample.cpp | 29 +++ 5 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt create mode 100644 cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index c60145851a..82e23a9e45 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -38,7 +38,7 @@ import org.slf4j.LoggerFactory class MultiValueEvaluator : ValueEvaluator() { companion object { - const val MAX_DEPTH: Int = 10 + const val MAX_DEPTH: Int = 20 } override val log: Logger @@ -76,10 +76,10 @@ class MultiValueEvaluator : ValueEvaluator() { is BinaryOperator -> return handleBinaryOperator(node, depth) // Casts are just a wrapper in this case, we are interested in the inner expression is CastExpression -> return this.evaluateInternal(node.expression, depth + 1) - is ArraySubscriptionExpression -> handleArraySubscriptionExpression(node, depth) + is ArraySubscriptionExpression -> return handleArraySubscriptionExpression(node, depth) // While we are not handling different paths of variables with If statements, we can // easily be partly path-sensitive in a conditional expression - is ConditionalExpression -> handleConditionalExpression(node, depth) + is ConditionalExpression -> return handleConditionalExpression(node, depth) } // At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe @@ -142,7 +142,12 @@ class MultiValueEvaluator : ValueEvaluator() { ) { evaluateInternal(expr.thenExpr, depth + 1) } else { - evaluateInternal(expr.elseExpr, depth + 1) + val result = mutableListOf() + val elseResult = evaluateInternal(expr.elseExpr, depth + 1) + val thenResult = evaluateInternal(expr.thenExpr, depth + 1) + if (thenResult is List<*>) result.addAll(thenResult) else result.add(thenResult) + if (elseResult is List<*>) result.addAll(elseResult) else result.add(elseResult) + result } } @@ -191,7 +196,8 @@ class MultiValueEvaluator : ValueEvaluator() { if (prevDFG.size == 1) { // There's only one incoming DFG edge, so we follow this one. - return mutableListOf(evaluateInternal(prevDFG.first(), depth + 1)) + val internalRes = evaluateInternal(prevDFG.first(), depth + 1) + return if (internalRes is List<*>) internalRes else mutableListOf(internalRes) } // We are only interested in expressions diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt index 83ad067909..6501fd6f46 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt @@ -56,7 +56,7 @@ class Interval : NumberSet() { } } -class ConcreteNumberSet(private var values: MutableSet = mutableSetOf()) : NumberSet() { +class ConcreteNumberSet(var values: MutableSet = mutableSetOf()) : NumberSet() { override fun addValue(value: Long) { values.add(value) } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index a383124e1a..414881c4db 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -88,10 +88,10 @@ open class ValueEvaluator( is BinaryOperator -> return handleBinaryOperator(node, depth) // Casts are just a wrapper in this case, we are interested in the inner expression is CastExpression -> return this.evaluateInternal(node.expression, depth + 1) - is ArraySubscriptionExpression -> handleArraySubscriptionExpression(node, depth) + is ArraySubscriptionExpression -> return handleArraySubscriptionExpression(node, depth) // While we are not handling different paths of variables with If statements, we can // easily be partly path-sensitive in a conditional expression - is ConditionalExpression -> handleConditionalExpression(node, depth) + is ConditionalExpression -> return handleConditionalExpression(node, depth) } // At this point, we cannot evaluate, and we are calling our [cannotEvaluate] hook, maybe diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt new file mode 100644 index 0000000000..4087e2c2a2 --- /dev/null +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2022, 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.TestUtils +import de.fraunhofer.aisec.cpg.graph.bodyOrNull +import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.evaluate +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import java.nio.file.Path +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class MultiValueEvaluatorTest { + @Test + fun testSingleValue() { + val topLevel = Path.of("src", "test", "resources", "value_evaluation") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("example.cpp").toFile()), + topLevel, + true + ) + + assertNotNull(tu) + + val main = tu.byNameOrNull("main") + assertNotNull(main) + + val b = main.bodyOrNull()?.singleDeclaration + assertNotNull(b) + + var value = b.evaluate() + assertEquals(2L, value) + + val printB = main.bodyOrNull() + assertNotNull(printB) + + val evaluator = MultiValueEvaluator() + value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet + assertEquals(value.min(), value.max()) + assertEquals(2L, value.min()) + + val path = evaluator.path + assertEquals(4, path.size) + + val printA = main.bodyOrNull(1) + assertNotNull(printA) + + value = evaluator.evaluate(printA.arguments.firstOrNull()) as ConcreteNumberSet + assertEquals(value.min(), value.max()) + assertEquals(2, value.min()) + + val c = main.bodyOrNull(2)?.singleDeclaration + assertNotNull(c) + + value = evaluator.evaluate(c) + assertEquals(3L, value) + + val d = main.bodyOrNull(3)?.singleDeclaration + assertNotNull(d) + + value = evaluator.evaluate(d) + assertEquals(2L, value) + + val e = main.bodyOrNull(4)?.singleDeclaration + assertNotNull(e) + value = evaluator.evaluate(e) + assertEquals(3.5, value) + + val f = main.bodyOrNull(5)?.singleDeclaration + assertNotNull(f) + value = evaluator.evaluate(f) + assertEquals(10L, value) + + val g = main.bodyOrNull(6)?.singleDeclaration + assertNotNull(g) + value = evaluator.evaluate(g) as ConcreteNumberSet + assertEquals(value.min(), value.max()) + assertEquals(-3L, value.min()) + + val i = main.bodyOrNull(8)?.singleDeclaration + assertNotNull(i) + value = evaluator.evaluate(i) + assertFalse(value as Boolean) + + val j = main.bodyOrNull(9)?.singleDeclaration + assertNotNull(j) + value = evaluator.evaluate(j) + assertFalse(value as Boolean) + + val k = main.bodyOrNull(10)?.singleDeclaration + assertNotNull(k) + value = evaluator.evaluate(k) + assertFalse(value as Boolean) + + val l = main.bodyOrNull(11)?.singleDeclaration + assertNotNull(l) + value = evaluator.evaluate(l) + assertFalse(value as Boolean) + + val m = main.bodyOrNull(12)?.singleDeclaration + assertNotNull(m) + value = evaluator.evaluate(m) + assertFalse(value as Boolean) + } + + @Test + fun testMultipleValues() { + val topLevel = Path.of("src", "test", "resources", "value_evaluation") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("cfexample.cpp").toFile()), + topLevel, + true + ) + + assertNotNull(tu) + + val main = tu.byNameOrNull("main") + assertNotNull(main) + + val b = main.bodyOrNull()?.singleDeclaration + assertNotNull(b) + + var printB = main.bodyOrNull() + assertNotNull(printB) + + val evaluator = MultiValueEvaluator() + var value = printB.arguments.firstOrNull()?.evaluate() + assertTrue(value is String) // could not evaluate + + value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet + assertEquals(setOf(1, 2), value.values) + + printB = main.bodyOrNull(1) + assertNotNull(printB) + value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet + assertEquals(setOf(0, 1, 2), value.values) + + printB = main.bodyOrNull(2) + assertNotNull(printB) + value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet + assertEquals(setOf(0, 1, 2, 4), value.values) + + printB = main.bodyOrNull(3) + assertNotNull(printB) + value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet + assertEquals(setOf(-4, -2, -1, 0, 1, 2, 4), value.values) + + printB = main.bodyOrNull(4) + assertNotNull(printB) + value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet + assertEquals(setOf(3, 5), value.values) + } +} diff --git a/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp b/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp new file mode 100644 index 0000000000..668c55580e --- /dev/null +++ b/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp @@ -0,0 +1,29 @@ +#include +#include + +int main() { + srand(time(NULL)); + int b = 1; + if(rand() < 10) { + b = b+1; + } + println(b); // {1, 2} + + if(rand() > 5) { + b = b-1; + } + println(b); // {0, 1, 2} + + if(rand() > 3) { + b = b*2; + } + println(b); // {0, 1, 2, 4} + + if(rand() < 4) { + b = -b; + } + println(b); // {-4, -2, -1, 0, 1, 2, 4} + + int a = b < 2 ? 3 : 5; + println(a); // {3, 5} +} \ No newline at end of file From 067e93a27af22e623f85beaefdc01bd8da5ecfc1 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 10 Aug 2022 13:48:59 +0200 Subject: [PATCH 46/67] More coverage in tests, Sizeof test, remove duplicate code --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 78 ++++--------------- .../aisec/cpg/analysis/SizeEvaluator.kt | 7 +- .../aisec/cpg/analysis/ValueEvaluator.kt | 14 ++-- .../cpg/analysis/MultiValueEvaluatorTest.kt | 2 +- .../cpg/analysis/fsm/SizeEvaluatorTest.kt | 73 +++++++++++++++++ .../resources/value_evaluation/cfexample.cpp | 5 +- .../test/resources/value_evaluation/size.java | 10 +++ 7 files changed, 117 insertions(+), 72 deletions(-) create mode 100644 cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SizeEvaluatorTest.kt create mode 100644 cpg-analysis/src/test/resources/value_evaluation/size.java diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 82e23a9e45..71d3277427 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -120,44 +120,18 @@ class MultiValueEvaluator : ValueEvaluator() { } override fun handleConditionalExpression(expr: ConditionalExpression, depth: Int): Any? { - // Assume that condition is a binary operator - if (expr.condition is BinaryOperator) { - val lhs = evaluateInternal((expr.condition as? BinaryOperator)?.lhs, depth + 1) - val rhs = evaluateInternal((expr.condition as? BinaryOperator)?.rhs, depth + 1) - - return if (lhs is List<*> && lhs.size > 1 && rhs is List<*> && rhs.size > 1) { - val result = mutableListOf() - val elseResult = evaluateInternal(expr.elseExpr, depth + 1) - if (elseResult is List<*>) result.addAll(elseResult) else result.add(elseResult) - if (lhs.any { l -> l in rhs }) { - val thenResult = evaluateInternal(expr.thenExpr, depth + 1) - if (thenResult is List<*>) result.addAll(thenResult) else result.add(thenResult) - } - result - } else if ( - lhs is List<*> && rhs is List<*> && lhs.firstOrNull() == rhs.firstOrNull() || - lhs is List<*> && lhs.firstOrNull() == rhs || - rhs is List<*> && rhs.firstOrNull() == lhs || - lhs == rhs - ) { - evaluateInternal(expr.thenExpr, depth + 1) - } else { - val result = mutableListOf() - val elseResult = evaluateInternal(expr.elseExpr, depth + 1) - val thenResult = evaluateInternal(expr.thenExpr, depth + 1) - if (thenResult is List<*>) result.addAll(thenResult) else result.add(thenResult) - if (elseResult is List<*>) result.addAll(elseResult) else result.add(elseResult) - result - } - } - - return cannotEvaluate(expr, this) + val result = mutableListOf() + val elseResult = evaluateInternal(expr.elseExpr, depth + 1) + val thenResult = evaluateInternal(expr.thenExpr, depth + 1) + if (thenResult is List<*>) result.addAll(thenResult) else result.add(thenResult) + if (elseResult is List<*>) result.addAll(elseResult) else result.add(elseResult) + return result } - override fun handleUnaryOp(expr: UnaryOperator, depth: Int): Any? { + override fun handleUnaryOp(expr: UnaryOperator, depth: Int, input: Any?): Any? { return when (expr.operatorCode) { "-" -> { - when (val input = evaluateInternal(expr.input, depth + 1)) { + when (input) { is List<*> -> input.map { n -> (n as? Number)?.negate() } is Number -> input.negate() else -> cannotEvaluate(expr, this) @@ -165,17 +139,17 @@ class MultiValueEvaluator : ValueEvaluator() { } "++" -> { if (expr.astParent is ForStatement) { - evaluateInternal(expr.input, depth + 1) + input } else { - when (val input = evaluateInternal(expr.input, depth + 1)) { + when (input) { is Number -> input.toLong() + 1 is List<*> -> input.map { n -> (n as? Number)?.toLong()?.plus(1) } else -> cannotEvaluate(expr, this) } } } - "*" -> evaluateInternal(expr.input, depth + 1) - "&" -> evaluateInternal(expr.input, depth + 1) + "*" -> input + "&" -> input else -> cannotEvaluate(expr, this) } } @@ -299,7 +273,7 @@ class MultiValueEvaluator : ValueEvaluator() { computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number } is UnaryOperator -> { - computeUnaryOpEffect( + val input = if ( (loopOp.input as? DeclaredReferenceExpression)?.refersTo == expr.refersTo @@ -307,10 +281,8 @@ class MultiValueEvaluator : ValueEvaluator() { loopVar!! } else { loopOp.input - }, - loopOp - ) - as? Number + } + handleUnaryOp(loopOp, depth, input) as? Number } else -> { null @@ -331,24 +303,4 @@ class MultiValueEvaluator : ValueEvaluator() { } return result } - - private fun computeUnaryOpEffect(input: Any, expr: UnaryOperator): Any? { - return when (expr.operatorCode) { - "-" -> { - when (input) { - is List<*> -> input.map { n -> (n as? Number)?.negate() } - is Number -> input.negate() - else -> cannotEvaluate(expr, this) - } - } - "++" -> { - when (input) { - is Number -> input.toLong() + 1 - is List<*> -> input.map { n -> (n as? Number)?.toLong()?.plus(1) } - else -> cannotEvaluate(expr, this) - } - } - else -> cannotEvaluate(expr, this) - } - } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt index 32659305b9..291748fd2b 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt @@ -53,7 +53,12 @@ class SizeEvaluator : ValueEvaluator() { node?.let { this.path += it } return when (node) { - is ArrayCreationExpression -> evaluateInternal(node.initializer, depth + 1) + is ArrayCreationExpression -> + if (node.initializer != null) { + evaluateInternal(node.initializer, depth + 1) + } else { + evaluateInternal(node.dimensions.firstOrNull(), depth + 1) + } is VariableDeclaration -> evaluateInternal(node.initializer, depth + 1) is DeclaredReferenceExpression -> evaluateInternal(node.refersTo, depth + 1) // For a literal, we can just take its value, and we are finished diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 414881c4db..deed207653 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -256,22 +256,26 @@ open class ValueEvaluator( * We handle some basic unary operators. These also affect pointers and dereferences for * languages that support them. */ - protected open fun handleUnaryOp(expr: UnaryOperator, depth: Int): Any? { + protected open fun handleUnaryOp( + expr: UnaryOperator, + depth: Int, + input: Any? = evaluateInternal(expr.input, depth + 1) + ): Any? { return when (expr.operatorCode) { "-" -> { - when (val input = evaluateInternal(expr.input, depth + 1)) { + when (input) { is Number -> input.negate() else -> cannotEvaluate(expr, this) } } "++" -> { - when (val input = evaluateInternal(expr.input, depth + 1)) { + when (input) { is Number -> input.toLong() + 1 else -> cannotEvaluate(expr, this) } } - "*" -> evaluateInternal(expr.input, depth + 1) - "&" -> evaluateInternal(expr.input, depth + 1) + "*" -> input + "&" -> input else -> cannotEvaluate(expr, this) } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt index 4087e2c2a2..14138211e3 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -179,6 +179,6 @@ class MultiValueEvaluatorTest { printB = main.bodyOrNull(4) assertNotNull(printB) value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet - assertEquals(setOf(3, 5), value.values) + assertEquals(setOf(3, 6), value.values) } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SizeEvaluatorTest.kt new file mode 100644 index 0000000000..14fe8701de --- /dev/null +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SizeEvaluatorTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, 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.fsm + +import de.fraunhofer.aisec.cpg.TestUtils +import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator +import de.fraunhofer.aisec.cpg.graph.bodyOrNull +import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import java.nio.file.Path +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class SizeEvaluatorTest { + + @Test + fun testSingleValue() { + val topLevel = Path.of("src", "test", "resources", "value_evaluation") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("size.java").toFile()), + topLevel, + true + ) + + assertNotNull(tu) + + val mainClass = tu.byNameOrNull("MainClass") + assertNotNull(mainClass) + val main = mainClass.byNameOrNull("main") + assertNotNull(main) + + val array = main.bodyOrNull()?.singleDeclaration + assertNotNull(array) + + val evaluator = SizeEvaluator() + var value = evaluator.evaluate(array) + assertEquals(3, value) + + val printCall = main.bodyOrNull(0) + assertNotNull(printCall) + + value = evaluator.evaluate(printCall.arguments.firstOrNull()) as Int + assertEquals(3, value) + } +} diff --git a/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp b/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp index 668c55580e..32120c1c30 100644 --- a/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp +++ b/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp @@ -24,6 +24,7 @@ int main() { } println(b); // {-4, -2, -1, 0, 1, 2, 4} - int a = b < 2 ? 3 : 5; - println(a); // {3, 5} + int a = b < 2 ? 3 : 5++; + println(a); // {3, 6} + return 0; } \ No newline at end of file diff --git a/cpg-analysis/src/test/resources/value_evaluation/size.java b/cpg-analysis/src/test/resources/value_evaluation/size.java new file mode 100644 index 0000000000..f3a48d884b --- /dev/null +++ b/cpg-analysis/src/test/resources/value_evaluation/size.java @@ -0,0 +1,10 @@ +public class MainClass { + public static void main(String[] args) { + int[] array = new int[3]; + for(int i = 0; i < array.length; i++) { + array[i] = i; + } + System.out.println(array[1]); + return 0; + } + } \ No newline at end of file From 57cc01dadf46f9bd1002a10a2bce7ff4ce9d982a Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 10 Aug 2022 17:18:39 +0200 Subject: [PATCH 47/67] Fix --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 40 ++++++++++++++----- .../aisec/cpg/analysis/ValueEvaluator.kt | 14 +++---- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 71d3277427..59e7aebde8 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -128,10 +128,10 @@ class MultiValueEvaluator : ValueEvaluator() { return result } - override fun handleUnaryOp(expr: UnaryOperator, depth: Int, input: Any?): Any? { + override fun handleUnaryOp(expr: UnaryOperator, depth: Int): Any? { return when (expr.operatorCode) { "-" -> { - when (input) { + when (val input = evaluateInternal(expr.input, depth + 1)) { is List<*> -> input.map { n -> (n as? Number)?.negate() } is Number -> input.negate() else -> cannotEvaluate(expr, this) @@ -139,17 +139,17 @@ class MultiValueEvaluator : ValueEvaluator() { } "++" -> { if (expr.astParent is ForStatement) { - input + evaluateInternal(expr.input, depth + 1) } else { - when (input) { + when (val input = evaluateInternal(expr.input, depth + 1)) { is Number -> input.toLong() + 1 is List<*> -> input.map { n -> (n as? Number)?.toLong()?.plus(1) } else -> cannotEvaluate(expr, this) } } } - "*" -> input - "&" -> input + "*" -> evaluateInternal(expr.input, depth + 1) + "&" -> evaluateInternal(expr.input, depth + 1) else -> cannotEvaluate(expr, this) } } @@ -273,7 +273,7 @@ class MultiValueEvaluator : ValueEvaluator() { computeBinaryOpEffect(opLhs, opRhs, loopOp) as? Number } is UnaryOperator -> { - val input = + computeUnaryOpEffect( if ( (loopOp.input as? DeclaredReferenceExpression)?.refersTo == expr.refersTo @@ -281,8 +281,10 @@ class MultiValueEvaluator : ValueEvaluator() { loopVar!! } else { loopOp.input - } - handleUnaryOp(loopOp, depth, input) as? Number + }, + loopOp + ) + as? Number } else -> { null @@ -303,4 +305,24 @@ class MultiValueEvaluator : ValueEvaluator() { } return result } + + private fun computeUnaryOpEffect(input: Any, expr: UnaryOperator): Any? { + return when (expr.operatorCode) { + "-" -> { + when (input) { + is List<*> -> input.map { n -> (n as? Number)?.negate() } + is Number -> input.negate() + else -> cannotEvaluate(expr, this) + } + } + "++" -> { + when (input) { + is Number -> input.toLong() + 1 + is List<*> -> input.map { n -> (n as? Number)?.toLong()?.plus(1) } + else -> cannotEvaluate(expr, this) + } + } + else -> cannotEvaluate(expr, this) + } + } } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index deed207653..414881c4db 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -256,26 +256,22 @@ open class ValueEvaluator( * We handle some basic unary operators. These also affect pointers and dereferences for * languages that support them. */ - protected open fun handleUnaryOp( - expr: UnaryOperator, - depth: Int, - input: Any? = evaluateInternal(expr.input, depth + 1) - ): Any? { + protected open fun handleUnaryOp(expr: UnaryOperator, depth: Int): Any? { return when (expr.operatorCode) { "-" -> { - when (input) { + when (val input = evaluateInternal(expr.input, depth + 1)) { is Number -> input.negate() else -> cannotEvaluate(expr, this) } } "++" -> { - when (input) { + when (val input = evaluateInternal(expr.input, depth + 1)) { is Number -> input.toLong() + 1 else -> cannotEvaluate(expr, this) } } - "*" -> input - "&" -> input + "*" -> evaluateInternal(expr.input, depth + 1) + "&" -> evaluateInternal(expr.input, depth + 1) else -> cannotEvaluate(expr, this) } } From 6b28bdfd0e6d776909d709c07f037c55820c9f61 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 11 Aug 2022 09:53:28 +0200 Subject: [PATCH 48/67] Tests and fixes for shortcuts --- .../analysis/{fsm => }/SizeEvaluatorTest.kt | 3 +- .../aisec/cpg/query/QueryShortcuts.kt | 21 +- .../aisec/cpg/analysis/ShortcutsTest.kt | 282 ++++++++++++++++++ .../src/test/resources/ShortcutClass.java | 29 ++ 4 files changed, 325 insertions(+), 10 deletions(-) rename cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/{fsm => }/SizeEvaluatorTest.kt (96%) create mode 100644 cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ShortcutsTest.kt create mode 100644 cpg-console/src/test/resources/ShortcutClass.java diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SizeEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt similarity index 96% rename from cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SizeEvaluatorTest.kt rename to cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt index 14fe8701de..17514f2463 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/fsm/SizeEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluatorTest.kt @@ -23,10 +23,9 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis.fsm +package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.TestUtils -import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt index 9fc709a61b..802b0d2865 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt @@ -34,6 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.graph import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement import de.fraunhofer.aisec.cpg.graph.statements.expressions.* +import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.astParent /** Returns all [CallExpression]s in this graph. */ @@ -52,7 +53,8 @@ fun TranslationResult.callsByName(name: String): List { /** Set of all functions which are called from this function */ val FunctionDeclaration.callees: Set get() { - return this.body.astChildren + + return SubgraphWalker.flattenAST(this.body) .filterIsInstance() .map { it.invokes } .foldRight( @@ -76,18 +78,21 @@ fun TranslationResult.callersOf(function: FunctionDeclaration): Set { - return this.astChildren + val result = mutableListOf() + result.addAll(SubgraphWalker.flattenAST(this.thenStatement)) + result.addAll(SubgraphWalker.flattenAST(this.elseStatement)) + return result } /** All nodes which depend on this if statement */ fun Node.controlledBy(): List { val result = mutableListOf() - if ( - this.astParent != null && - (this.astParent is IfStatement || this.astParent is SwitchStatement) - ) { - result.add(this.astParent!!) - result.addAll(this.astParent!!.controlledBy()) + var checkedNode: Node = this + while (checkedNode !is FunctionDeclaration) { + checkedNode = checkedNode.astParent!! + if (checkedNode is IfStatement || checkedNode is SwitchStatement) { + result.add(checkedNode) + } } return result } diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ShortcutsTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ShortcutsTest.kt new file mode 100644 index 0000000000..2aca2f6f8c --- /dev/null +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ShortcutsTest.kt @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2022, 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.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationManager +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.bodyOrNull +import de.fraunhofer.aisec.cpg.graph.byNameOrNull +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.NewExpression +import de.fraunhofer.aisec.cpg.passes.EdgeCachePass +import de.fraunhofer.aisec.cpg.query.* +import java.io.File +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class ShortcutsTest { + @Test + fun testCalls() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/ShortcutClass.java")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val actual = result.calls + + val expected = mutableListOf() + val classDecl = + result.translationUnits.firstOrNull()?.declarations?.firstOrNull() as RecordDeclaration + val main = classDecl.byNameOrNull("main") + assertNotNull(main) + expected.add( + ((((main.body as CompoundStatement).statements[0] as DeclarationStatement) + .declarations[0] + as VariableDeclaration) + .initializer as NewExpression) + .initializer as ConstructExpression + ) + expected.add((main.body as CompoundStatement).statements[1] as MemberCallExpression) + expected.add((main.body as CompoundStatement).statements[2] as MemberCallExpression) + + val print = classDecl.byNameOrNull("print") + assertNotNull(print) + expected.add(print.bodyOrNull(0)!!) + expected.add(print.bodyOrNull(0)?.arguments?.get(0) as CallExpression) + + assertTrue(expected.containsAll(actual)) + assertTrue(actual.containsAll(expected)) + + assertEquals( + listOf((main.body as CompoundStatement).statements[1] as MemberCallExpression), + expected.filterByName("print") + ) + } + + @Test + fun testCallsByName() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/ShortcutClass.java")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val actual = result.callsByName("print") + + val expected = mutableListOf() + val classDecl = + result.translationUnits.firstOrNull()?.declarations?.firstOrNull() as RecordDeclaration + val main = classDecl.byNameOrNull("main") + assertNotNull(main) + expected.add((main.body as CompoundStatement).statements[1] as MemberCallExpression) + assertTrue(expected.containsAll(actual)) + assertTrue(actual.containsAll(expected)) + } + + @Test + fun testCalleesOf() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/ShortcutClass.java")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val expected = mutableListOf() + val classDecl = + result.translationUnits.firstOrNull()?.declarations?.firstOrNull() as RecordDeclaration + + val print = classDecl.byNameOrNull("print") + assertNotNull(print) + expected.add(print) + + val magic = classDecl.byNameOrNull("magic") + assertNotNull(magic) + expected.add(magic) + + val main = classDecl.byNameOrNull("main") + assertNotNull(main) + val actual = main.callees + + expected.add( + (((((main.body as CompoundStatement).statements[0] as DeclarationStatement) + .declarations[0] + as VariableDeclaration) + .initializer as NewExpression) + .initializer as ConstructExpression) + .constructor!! + ) + + assertTrue(expected.containsAll(actual)) + assertTrue(actual.containsAll(expected)) + } + + @Test + fun testCallersOf() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/ShortcutClass.java")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val classDecl = + result.translationUnits.firstOrNull()?.declarations?.firstOrNull() as RecordDeclaration + val print = classDecl.byNameOrNull("print") + assertNotNull(print) + + val actual = result.callersOf(print) + + val expected = mutableListOf() + val main = classDecl.byNameOrNull("main") + assertNotNull(main) + expected.add(main) + assertTrue(expected.containsAll(actual)) + assertTrue(actual.containsAll(expected)) + } + + @Test + fun testControls() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/ShortcutClass.java")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val expected = mutableListOf() + val classDecl = + result.translationUnits.firstOrNull()?.declarations?.firstOrNull() as RecordDeclaration + val magic = classDecl.byNameOrNull("magic") + assertNotNull(magic) + val ifStatement = (magic.body as CompoundStatement).statements[0] as IfStatement + + val actual = ifStatement.controls() + expected.add(ifStatement.thenStatement) + val thenStatement = + (ifStatement.thenStatement as CompoundStatement).statements[0] as IfStatement + expected.add(thenStatement) + expected.add(thenStatement.condition) + expected.add((thenStatement.condition as BinaryOperator).lhs) + expected.add(((thenStatement.condition as BinaryOperator).lhs as MemberExpression).base) + expected.add((thenStatement.condition as BinaryOperator).rhs) + val nestedThen = thenStatement.thenStatement as CompoundStatement + expected.add(nestedThen) + expected.add(nestedThen.statements[0]) + expected.add((nestedThen.statements[0] as BinaryOperator).lhs) + expected.add(((nestedThen.statements[0] as BinaryOperator).lhs as MemberExpression).base) + expected.add((nestedThen.statements[0] as BinaryOperator).rhs) + val nestedElse = thenStatement.elseStatement as CompoundStatement + expected.add(nestedElse) + expected.add(nestedElse.statements[0]) + expected.add((nestedElse.statements[0] as BinaryOperator).lhs) + expected.add(((nestedElse.statements[0] as BinaryOperator).lhs as MemberExpression).base) + expected.add((nestedElse.statements[0] as BinaryOperator).rhs) + + expected.add(ifStatement.elseStatement) + expected.add((ifStatement.elseStatement as CompoundStatement).statements[0]) + expected.add( + ((ifStatement.elseStatement as CompoundStatement).statements[0] as BinaryOperator).lhs + ) + expected.add( + (((ifStatement.elseStatement as CompoundStatement).statements[0] as BinaryOperator).lhs + as MemberExpression) + .base + ) + expected.add( + ((ifStatement.elseStatement as CompoundStatement).statements[0] as BinaryOperator).rhs + ) + + assertTrue(expected.containsAll(actual)) + assertTrue(actual.containsAll(expected)) + } + + @Test + fun testControlledBy() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/ShortcutClass.java")) + .defaultPasses() + .defaultLanguages() + .registerPass(EdgeCachePass()) + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val expected = mutableListOf() + val classDecl = + result.translationUnits.firstOrNull()?.declarations?.firstOrNull() as RecordDeclaration + val magic = classDecl.byNameOrNull("magic") + assertNotNull(magic) + + // get the statement attr = 3; + val ifStatement = (magic.body as CompoundStatement).statements[0] as IfStatement + val thenStatement = + (ifStatement.thenStatement as CompoundStatement).statements[0] as IfStatement + val nestedThen = thenStatement.thenStatement as CompoundStatement + val interestingNode = nestedThen.statements[0] + val actual = interestingNode.controlledBy() + + expected.add(ifStatement) + expected.add(thenStatement) + + assertTrue(expected.containsAll(actual)) + assertTrue(actual.containsAll(expected)) + } +} diff --git a/cpg-console/src/test/resources/ShortcutClass.java b/cpg-console/src/test/resources/ShortcutClass.java new file mode 100644 index 0000000000..3179341049 --- /dev/null +++ b/cpg-console/src/test/resources/ShortcutClass.java @@ -0,0 +1,29 @@ +public class ShortcutClass { + private int attr = 0; + + public String toString() { + return "ShortcutClass: attr=" + attr; + } + + public int print() { + System.out.println(this.toString()); + } + + public void magic(int b) { + if(b > 5) { + if(attr == 2) { + attr = 3; + } else { + attr = 2; + } + } else { + attr = b; + } + } + + public static void main(String[] args) { + ShortcutClass sc = new ShortcutClass(); + sc.print(); + sc.magic(3); + } +} \ No newline at end of file From e127232b75af047f2e173dd2281b568b42800cd2 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 11 Aug 2022 15:19:42 +0200 Subject: [PATCH 49/67] Test coverage++ --- .../aisec/cpg/analysis/MultiValueEvaluator.kt | 5 ++ .../aisec/cpg/analysis/NumberSet.kt | 4 +- .../aisec/cpg/analysis/SizeEvaluator.kt | 2 +- .../cpg/analysis/MultiValueEvaluatorTest.kt | 65 ++++++++++++++++++ .../aisec/cpg/analysis/SizeEvaluatorTest.kt | 67 ++++++++++++++++++- .../resources/value_evaluation/cfexample.cpp | 8 +++ .../test/resources/value_evaluation/size.java | 3 + 7 files changed, 149 insertions(+), 5 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt index 59e7aebde8..1ce91bb668 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluator.kt @@ -36,6 +36,11 @@ import de.fraunhofer.aisec.cpg.passes.astParent import org.slf4j.Logger import org.slf4j.LoggerFactory +/** + * This [ValueEvaluator] can resolve multiple possible values of a node. + * + * It requires running the [EdgeCachePass] after the translation to add all necessary edges. + */ class MultiValueEvaluator : ValueEvaluator() { companion object { const val MAX_DEPTH: Int = 20 diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt index 6501fd6f46..e2929a9c9d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt @@ -61,10 +61,10 @@ class ConcreteNumberSet(var values: MutableSet = mutableSetOf()) : NumberS values.add(value) } override fun min(): Long { - return values.minOrNull()!! + return values.minOrNull() ?: Long.MAX_VALUE } override fun max(): Long { - return values.maxOrNull()!! + return values.maxOrNull() ?: Long.MIN_VALUE } override fun clear() { values.clear() diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt index 291748fd2b..77ef652dfa 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/SizeEvaluator.kt @@ -38,7 +38,7 @@ import org.slf4j.LoggerFactory */ class SizeEvaluator : ValueEvaluator() { override val log: Logger - get() = LoggerFactory.getLogger(MultiValueEvaluator::class.java) + get() = LoggerFactory.getLogger(SizeEvaluator::class.java) override fun evaluate(node: Any?): Any? { if (node is String) { diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt index 14138211e3..03f4494ee2 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -30,8 +30,12 @@ import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.evaluate +import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import java.nio.file.Path import kotlin.test.Test import kotlin.test.assertEquals @@ -181,4 +185,65 @@ class MultiValueEvaluatorTest { value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(setOf(3, 6), value.values) } + + @Test + fun testLoop() { + val topLevel = Path.of("src", "test", "resources", "value_evaluation") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("cfexample.cpp").toFile()), + topLevel, + true + ) { it.registerPass(EdgeCachePass()) } + + assertNotNull(tu) + + val main = tu.byNameOrNull("loop") + assertNotNull(main) + + val forLoop = main.bodyOrNull() + assertNotNull(forLoop) + + val evaluator = MultiValueEvaluator() + val iVar = ((forLoop.statement as CompoundStatement).statements[0] as BinaryOperator).rhs + val value = evaluator.evaluate(iVar) as ConcreteNumberSet + assertEquals(setOf(0, 1, 2, 3, 4, 5), value.values) + } + + @Test + fun testInterval() { + val interval = Interval() + interval.addValue(0) + assertEquals(0, interval.min()) + assertEquals(0, interval.max()) + interval.addValue(3) + interval.addValue(2) + assertEquals(0, interval.min()) + assertEquals(3, interval.max()) + interval.addValue(-5) + assertEquals(-5, interval.min()) + assertEquals(3, interval.max()) + interval.clear() + assertEquals(Long.MAX_VALUE, interval.min()) + assertEquals(Long.MIN_VALUE, interval.max()) + } + + @Test + fun testConcreteNumberSet() { + val values = ConcreteNumberSet() + values.addValue(0) + assertEquals(setOf(0), values.values) + values.addValue(3) + values.addValue(2) + assertEquals(setOf(0, 2, 3), values.values) + assertEquals(0, values.min()) + assertEquals(3, values.max()) + values.addValue(-5) + assertEquals(setOf(-5, 0, 2, 3), values.values) + assertEquals(-5, values.min()) + assertEquals(3, values.max()) + values.clear() + assertEquals(Long.MAX_VALUE, values.min()) + assertEquals(Long.MIN_VALUE, values.max()) + } } 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 17514f2463..796b327277 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 @@ -30,7 +30,10 @@ import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration +import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement import de.fraunhofer.aisec.cpg.graph.statements.DeclarationStatement +import de.fraunhofer.aisec.cpg.graph.statements.ForStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import java.nio.file.Path import kotlin.test.Test @@ -38,9 +41,8 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull class SizeEvaluatorTest { - @Test - fun testSingleValue() { + fun testArraySize() { val topLevel = Path.of("src", "test", "resources", "value_evaluation") val tu = TestUtils.analyzeAndGetFirstTU( @@ -69,4 +71,65 @@ class SizeEvaluatorTest { value = evaluator.evaluate(printCall.arguments.firstOrNull()) as Int assertEquals(3, value) } + + @Test + fun testArraySizeFromSubscript() { + val topLevel = Path.of("src", "test", "resources", "value_evaluation") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("size.java").toFile()), + topLevel, + true + ) + + assertNotNull(tu) + + val mainClass = tu.byNameOrNull("MainClass") + assertNotNull(mainClass) + val main = mainClass.byNameOrNull("main") + assertNotNull(main) + + val array = main.bodyOrNull()?.singleDeclaration + assertNotNull(array) + + val evaluator = SizeEvaluator() + var value = evaluator.evaluate(array) + assertEquals(3, value) + + val forLoop = main.bodyOrNull(0) + assertNotNull(forLoop) + + val subscriptExpr = + ((forLoop.statement as CompoundStatement).statements[0] as BinaryOperator).lhs + + value = evaluator.evaluate(subscriptExpr) as Int + assertEquals(3, value) + } + + @Test + fun testStringSize() { + val topLevel = Path.of("src", "test", "resources", "value_evaluation") + val tu = + TestUtils.analyzeAndGetFirstTU( + listOf(topLevel.resolve("size.java").toFile()), + topLevel, + true + ) + + assertNotNull(tu) + + val mainClass = tu.byNameOrNull("MainClass") + assertNotNull(mainClass) + val main = mainClass.byNameOrNull("main") + assertNotNull(main) + val printCall = main.bodyOrNull(1) + assertNotNull(printCall) + + val evaluator = SizeEvaluator() + val value = evaluator.evaluate(printCall.arguments.firstOrNull()) as Int + assertEquals(5, value) + + val strValue = evaluator.evaluate("abcd") as Int + assertEquals(4, strValue) + } } diff --git a/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp b/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp index 32120c1c30..bb9181b48b 100644 --- a/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp +++ b/cpg-analysis/src/test/resources/value_evaluation/cfexample.cpp @@ -27,4 +27,12 @@ int main() { int a = b < 2 ? 3 : 5++; println(a); // {3, 6} return 0; +} + +int loop() { + int array[6]; + for(int i = 0; i < 6; i++) { + array[i] = i; + } + return 0; } \ No newline at end of file diff --git a/cpg-analysis/src/test/resources/value_evaluation/size.java b/cpg-analysis/src/test/resources/value_evaluation/size.java index f3a48d884b..5bde3a1edd 100644 --- a/cpg-analysis/src/test/resources/value_evaluation/size.java +++ b/cpg-analysis/src/test/resources/value_evaluation/size.java @@ -5,6 +5,9 @@ public static void main(String[] args) { array[i] = i; } System.out.println(array[1]); + + String str = "abcde"; + System.out.println(str); return 0; } } \ No newline at end of file From c9c43f5ff32761836d76afa36ca08ece2c8f9af9 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 11 Aug 2022 15:59:42 +0200 Subject: [PATCH 50/67] Test coverage++ --- .../fraunhofer/aisec/cpg/query/QueryTree.kt | 10 + .../aisec/cpg/analysis/QueryTest.kt | 202 ++++++++++++++++++ 2 files changed, 212 insertions(+) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index 35f42bdeea..afedb84eb9 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -192,6 +192,16 @@ infix fun QueryTree.implies(other: QueryTree): QueryTree) operation between the values of two [QueryTree]s. */ +infix fun QueryTree.implies(other: Lazy>): QueryTree { + return QueryTree( + !this.value || other.value.value, + if (!this.value) mutableListOf(this) else mutableListOf(this, other.value), + stringRepresentation = + if (!this.value) "false => XYZ" else "${this.value} => ${other.value}" + ) +} + /** Compares the numeric values of two [QueryTree]s for this being "greater than" (>) [other]. */ infix fun QueryTree.gt(other: QueryTree): QueryTree { val result = this.value.compareTo(other.value) > 0 diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 61db0b772e..ed738f6d01 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -100,6 +100,27 @@ class QueryTest { assertFalse(queryTreeResult2.value) } + @Test + fun testMemcpyTooLargeQueryImplies() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val mustSatisfy = { it: CallExpression -> + (const("memcpy") eq it.name) implies + (lazy { it.arguments[0].size gt it.arguments[1].size }) + } + val queryTreeResult = result.all(mustSatisfy = mustSatisfy) + + assertFalse(queryTreeResult.value) + } + @Test fun testDoubleFree() { val config = @@ -137,6 +158,93 @@ class QueryTest { println(queryTreeResult2.printNicely()) } + @Test + fun testParameterGreaterThanOrEqualConst() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all({ it.name == "memcpy" }) { + it.arguments[2].intValue!! >= const(11) + } + assertTrue(queryTreeResult.first) + + val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! ge 11 } + val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + + assertTrue(queryTreeResult2.value) + + val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! ge const(11) } + val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + + assertTrue(queryTreeResult3.value) + } + + @Test + fun testParameterGreaterThanConst() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all({ it.name == "memcpy" }) { + it.arguments[2].intValue!! > const(11) + } + assertTrue(queryTreeResult.first) + + val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! gt 11 } + val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + + assertTrue(queryTreeResult2.value) + + val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! gt const(11) } + val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + + assertTrue(queryTreeResult3.value) + } + + @Test + fun testParameterLessThanOrEqualConst() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all({ it.name == "memcpy" }) { + it.arguments[2].intValue!! <= const(11) + } + assertTrue(queryTreeResult.first) + + val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! le 11 } + val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + + assertTrue(queryTreeResult2.value) + + val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! le const(11) } + val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + + assertTrue(queryTreeResult3.value) + } + @Test fun testParameterEqualsConst() { val config = @@ -159,6 +267,100 @@ class QueryTest { val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) assertTrue(queryTreeResult2.value) + + val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! eq const(11) } + val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + + assertTrue(queryTreeResult3.value) + } + + @Test + fun testParameterLessThanConst() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all({ it.name == "memcpy" }) { + it.arguments[2].intValue!! < const(11) + } + assertFalse(queryTreeResult.first) + + val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! lt 11 } + val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + + assertFalse(queryTreeResult2.value) + + val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! lt const(11) } + val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + + assertFalse(queryTreeResult3.value) + } + + @Test + fun testParameterNotEqualsConst() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all({ it.name == "memcpy" }) { + it.arguments[2].intValue!! != const(11) + } + assertFalse(queryTreeResult.first) + + val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! ne 11 } + val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + + assertFalse(queryTreeResult2.value) + + val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! ne const(11) } + val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + + assertFalse(queryTreeResult3.value) + } + + @Test + fun testParameterIn() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all({ it.name == "memcpy" }) { + it.arguments[2].intValue!!.value in listOf(11, 2, 3) + } + assertTrue(queryTreeResult.first) + + val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! IN listOf(11, 2, 3) } + val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + + assertTrue(queryTreeResult2.value) + + val mustSatisfy2 = { it: CallExpression -> + it.arguments[2].intValue!! IN const(listOf(11, 2, 3)) + } + val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + + assertTrue(queryTreeResult3.value) } @Test From 4330fde169d2ff092a161fbbd46f6bb7bfa2272b Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 11 Aug 2022 16:08:42 +0200 Subject: [PATCH 51/67] Fix --- .../kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index ed738f6d01..d20bbbbf2d 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -203,17 +203,17 @@ class QueryTest { result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!! > const(11) } - assertTrue(queryTreeResult.first) + assertFalse(queryTreeResult.first) val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! gt 11 } val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) - assertTrue(queryTreeResult2.value) + assertFalse(queryTreeResult2.value) val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! gt const(11) } val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) - assertTrue(queryTreeResult3.value) + assertFalse(queryTreeResult3.value) } @Test From 994e048eb1834a2ffc8bbfbf059ec9ae2c96e9b2 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 11 Aug 2022 16:38:59 +0200 Subject: [PATCH 52/67] Readme for query api --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 13 ++- .../de/fraunhofer/aisec/cpg/query/README.md | 105 ++++++++++++++++++ 2 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 4092c50afc..970786768d 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -255,7 +255,10 @@ fun executionPath(from: Node, to: Node): QueryTree { ) } -/** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ +/** + * Checks if a path of execution flow is possible between the nodes [from] and fulfilling the + * requirement specified in [predicate]. + */ fun executionPath(from: Node, predicate: (Node) -> Boolean): QueryTree { val evalRes = from.followNextEOGEdgesUntilHit(predicate) return QueryTree( @@ -279,10 +282,10 @@ val Expression.size: QueryTree /** * The minimal integer value of this expression. It uses the default argument for `eval` of [min] */ -/*val Expression.min: QueryTree -get() { - return min(this) -}*/ +val Expression.min: QueryTree + get() { + return min(this) + } /** * The maximal integer value of this expression. It uses the default argument for `eval` of [max] diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md new file mode 100644 index 0000000000..54c8e41557 --- /dev/null +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md @@ -0,0 +1,105 @@ +# The Query API + +The Query API provides a way validate if nodes in the graph fulfil certain requirements. It is a mixture of typical logical expressions (e.g. and, or, xor, implies), quantors (e.g. forall, exists), comparisons (e.g. <, >, ==, !=), some special operations (e.g., `in` to check for collections or `is` for types) and a couple of operations. + +## Operation modes +The Query API has two modes of operations which determine the depth of the output: +1. The detailed mode reasons about every single step performed to check if the query is fulfilled. +2. The less detailed mode only provides the final output (true, false) and the nodes which serve as input. + +To use the detailed mode, it is necessary to use specific operators in a textual representation whereas the other modes relies on the operators as known from any programming language. + +The following example output from the test case `testMemcpyTooLargeQuery2` shows the difference: + +**Less detailed:** +``` +[CallExpression[name=memcpy,location=vulnerable.cpp(3:5-3:38),type=UNKNOWN,base=]] +``` +**Detailed mode:** +``` +all (==> false) +-------- + Starting at CallExpression[name=memcpy,location=vulnerable.cpp(3:5-3:38),type=UNKNOWN,base=]: 5 > 11 (==> false) +------------------------ + sizeof(DeclaredReferenceExpression[DeclaredReferenceExpression[name=array,location=vulnerable.cpp(3:12-3:17),type=PointerType[name=char[]]],refersTo=VariableDeclaration[name=array,location=vulnerable.cpp(2:10-2:28),initializer=Literal[location=vulnerable.cpp(2:21-2:28),type=PointerType[name=char[]],value=hello]]]) (==> 5) +---------------------------------------- +------------------------ + sizeof(Literal[location=vulnerable.cpp(3:19-3:32),type=PointerType[name=char[]],value=Hello world]) (==> 11) +---------------------------------------- +------------------------ +-------- +``` + +## Operators of the detailed mode +Numerous methods allow to evaluate the queries while keeping track of all the steps. Currently, the following operations are supported: +- **eq**: Equality of two values. +- **ne**: Inequality of two values. +- **IN**: Checks if a value is contained in a [Collection] +- **IS**: Checks if a value implements a type ([Class]). + +Additionally, some functions are available only for certain types of values. + +For boolean values: +- **and**: Logical and operation (&&) +- **or**: Logical or operation (||) +- **xor**: Logical exclusive or operation (xor) +- **implies**: Logical implication + +For numeric values: +- **gt**: Grater than (>) +- **ge**: Grater than or equal (>=) +- **lt**: Less than (<) +- **le**: Less than or equal (<=) + +**Note:** The detailed mode and its operators require the user to take care of the correct order. I.e., the user has to put the brackets! + +## Operators of the less detailed mode +Numerous methods allow to evaluate the queries: +- **==**: Equality of two values. +- **!=**: Inequality of two values. +- **in** : Checks if a value is contained in a [Collection]. The value of a query tree has to be accessed by the property `value`. +- **is**: Checks if a value implements a type ([Class]). The value of a query tree has to be accessed by the property `value`. +- **&&**: Logical and operation +- **||**: Logical or operation +- **xor**: Logical exclusive or operation +- **>**: Grater than +- **>=**: Grater than or equal +- **<**: Less than +- **<=**: Less than or equal + +## Functions of the Query API +Since these operators cannot cover all interesting values, we provide an initial set of analyses and functions to use them. These are: +- **min(n: Node)**: Minimal value of a node +- **max(n: Node)**: Maximal value of a node +- **sizeof(n: Node)**: The length of an array or string +- **dataFlow(from: Node, to: Node)**: Checks if a data flow is possible between the nodes `from` as a source and `to` as sink. +- **executionPath(from: Node, to: Node)**: Checks if a path of execution flow is possible between the nodes `from` and `to`. +- **executionPath(from: Node, predicate: (Node) -> Boolean)**: Checks if a path of execution flow is possible starting at node `from` and fulfilling the requirement specified in `predicate`. + +## Running a query +The query can use any of these operators and functions and additionally operate on the fields of a node. To simplify the generation of queries, we provide an initial set of extensions for certain nodes. + +An example for such a query could look as follows for the detailed mode: +```kotlin +val memcpyTooLargeQuery = { node: CallExpression -> + sizeof(node.arguments[0]) gt sizeof(node.arguments[1]) +} +``` +The same query in the less detailed mode: +```kotlin +val memcpyTooLargeQuery = { node: CallExpression -> + sizeof(node.arguments[0]) > sizeof(node.arguments[1]) +} +``` + +After assembling a query of the respective operators and functions, we want to run it for a subset of nodes in the graph. We therefore provide two operators: `all` and `exists`. Both are used in a similar way. +They enable the user to optionally specify conditions to determine on which nodes we want to run a query (e.g., only on `CallExpression`s which call a function called "memcpy"). + +The following snippet uses the query from above to run it on all calls of the function "memcpy" contained in the `TranslationResult` `result`: +```kotlin +val queryTreeResult = + result.all( + { it.name == "memcpy" }, + { sizeof(it.arguments[0]) > sizeof(it.arguments[1]) } + ) +``` \ No newline at end of file From df35a5d0b9f00d5cb70dc2fab150226870930996 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 12 Aug 2022 10:43:50 +0200 Subject: [PATCH 53/67] Some fixes, more operators --- .../aisec/cpg/analysis/NumberSet.kt | 7 ++ .../aisec/cpg/analysis/ValueEvaluator.kt | 1 + .../aisec/cpg/graph/EvaluateExtensions.kt | 8 +-- .../cpg/analysis/MultiValueEvaluatorTest.kt | 2 + .../de/fraunhofer/aisec/cpg/query/Query.kt | 37 ++++++++++ .../fraunhofer/aisec/cpg/query/QueryTree.kt | 6 ++ .../aisec/cpg/analysis/QueryTest.kt | 70 +++++++++++++++++-- cpg-console/src/test/resources/vulnerable.cpp | 9 +++ .../fraunhofer/aisec/cpg/graph/Assignment.kt | 2 +- 9 files changed, 131 insertions(+), 11 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt index e2929a9c9d..b9cd312214 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/NumberSet.kt @@ -29,6 +29,7 @@ abstract class NumberSet { abstract fun min(): Long abstract fun max(): Long abstract fun addValue(value: Long) + abstract fun maybe(value: Long): Boolean abstract fun clear() } @@ -50,6 +51,9 @@ class Interval : NumberSet() { override fun max(): Long { return max } + override fun maybe(value: Long): Boolean { + return value in min..max + } override fun clear() { min = Long.MAX_VALUE max = Long.MIN_VALUE @@ -66,6 +70,9 @@ class ConcreteNumberSet(var values: MutableSet = mutableSetOf()) : NumberS override fun max(): Long { return values.maxOrNull() ?: Long.MIN_VALUE } + override fun maybe(value: Long): Boolean { + return value in values + } override fun clear() { values.clear() } diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 414881c4db..6aa4d6cfe2 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -174,6 +174,7 @@ open class ValueEvaluator( private fun handleDiv(lhsValue: Any?, rhsValue: Any?, expr: BinaryOperator): Any? { return when { + rhsValue == 0 -> cannotEvaluate(expr, this) lhsValue is Int && (rhsValue is Double || rhsValue is Float) -> lhsValue / (rhsValue as Number).toDouble() lhsValue is Int && rhsValue is Number -> lhsValue / rhsValue.toLong() diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EvaluateExtensions.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EvaluateExtensions.kt index cc038e231d..86bf86e4b4 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EvaluateExtensions.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/EvaluateExtensions.kt @@ -29,12 +29,12 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -fun Expression.evaluate(): Any? { - return ValueEvaluator().evaluate(this) +fun Expression.evaluate(evaluator: ValueEvaluator = ValueEvaluator()): Any? { + return evaluator.evaluate(this) } -fun Declaration.evaluate(): Any? { - return ValueEvaluator().evaluate(this) +fun Declaration.evaluate(evaluator: ValueEvaluator = ValueEvaluator()): Any? { + return evaluator.evaluate(this) } val ArrayCreationExpression.capacity: Int diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt index 03f4494ee2..a108c7269c 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -242,6 +242,8 @@ class MultiValueEvaluatorTest { assertEquals(setOf(-5, 0, 2, 3), values.values) assertEquals(-5, values.min()) assertEquals(3, values.max()) + assertTrue(values.maybe(3)) + assertFalse(values.maybe(1)) values.clear() assertEquals(Long.MAX_VALUE, values.min()) assertEquals(Long.MIN_VALUE, values.max()) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 970786768d..dd5841aba1 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.analysis.NumberSet import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.types.Type import kotlin.experimental.ExperimentalTypeInference /** @@ -273,6 +274,42 @@ operator fun Expression?.invoke(): QueryTree { return QueryTree(this?.evaluate(), mutableListOf(QueryTree(this))) } +/** + * Determines the maximal value. Only works for a couple of types! TODO: This method needs + * improvement! It only works for Java types! + */ +fun maxSizeOfType(type: Type): QueryTree { + val maxVal = + when (type.typeName) { + "byte" -> Byte.MAX_VALUE + "short" -> Short.MAX_VALUE + "int" -> Int.MAX_VALUE + "long" -> Long.MAX_VALUE + "float" -> Float.MAX_VALUE + "double" -> Double.MAX_VALUE + else -> Long.MAX_VALUE + } + return QueryTree(maxVal, mutableListOf(QueryTree(type)), "maxSizeOfType($type)") +} + +/** + * Determines the minimal value. Only works for a couple of types! TODO: This method needs + * improvement! It only works for Java types! + */ +fun minSizeOfType(type: Type): QueryTree { + val maxVal = + when (type.typeName) { + "byte" -> Byte.MIN_VALUE + "short" -> Short.MIN_VALUE + "int" -> Int.MIN_VALUE + "long" -> Long.MIN_VALUE + "float" -> Float.MIN_VALUE + "double" -> Double.MIN_VALUE + else -> Long.MIN_VALUE + } + return QueryTree(maxVal, mutableListOf(QueryTree(type)), "minSizeOfType($type)") +} + /** The size of this expression. It uses the default argument for `eval` of [size] */ val Expression.size: QueryTree get() { diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index afedb84eb9..af8163e0cf 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -273,6 +273,12 @@ fun not(arg: QueryTree): QueryTree { return QueryTree(result, mutableListOf(arg), "! ${arg.value}") } +/** Negates the value of [arg] and returns the resulting [QueryTree]. */ +fun not(arg: Boolean): QueryTree { + val result = !arg + return QueryTree(result, mutableListOf(QueryTree(arg)), "! ${arg}") +} + /** * This is a small wrapper to create a [QueryTree] containing a constant value, so that it can be * used to in comparison with other [QueryTree] objects. diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index d20bbbbf2d..23aec2965f 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -30,11 +30,9 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager import de.fraunhofer.aisec.cpg.graph.Assignment import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.evaluate import de.fraunhofer.aisec.cpg.graph.followPrevDFGEdgesUntilHit -import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArrayCreationExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.ArraySubscriptionExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import de.fraunhofer.aisec.cpg.query.* import java.io.File @@ -400,13 +398,13 @@ class QueryTest { val queryTreeResult = result.all( mustSatisfy = { - max(it.subscriptExpression) < min(it.size) && min(it.subscriptExpression) > 0 + max(it.subscriptExpression) < min(it.size) && min(it.subscriptExpression) >= 0 } ) assertFalse(queryTreeResult.first) val mustSatisfy = { it: ArraySubscriptionExpression -> - (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) gt 0) + (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) ge 0) } val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) assertFalse(queryTreeResult2.value) @@ -519,4 +517,64 @@ class QueryTest { assertTrue(queryTreeResult2.value) println(queryTreeResult2.printNicely()) } + + @Test + fun testDivisionBy0() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all( + { it.operatorCode == "/" }, + { !(it.rhs.evaluate(MultiValueEvaluator()) as NumberSet).maybe(0) } + ) + assertFalse(queryTreeResult.first) + + val mustSatisfy = { it: BinaryOperator -> + not((it.rhs.evaluate(MultiValueEvaluator()) as NumberSet).maybe(0)) + } + val queryTreeResult2 = result.all({ it.operatorCode == "/" }, mustSatisfy) + + assertFalse(queryTreeResult2.value) + } + + @Test + fun testIntOverflowAssignment() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all( + { it.target?.type?.isPrimitive == true }, + { + max(it.value) <= maxSizeOfType(it.target!!.type) && + min(it.value) >= minSizeOfType(it.target!!.type) + } + ) + // assertFalse(queryTreeResult.first) + + val mustSatisfy = { it: Assignment -> + (max(it.value) le maxSizeOfType(it.target!!.type)) and + (min(it.value) ge minSizeOfType(it.target!!.type)) + } + val queryTreeResult2 = + result.all({ it.target?.type?.isPrimitive == true }, mustSatisfy) + + println(queryTreeResult2.printNicely()) + assertFalse(queryTreeResult2.value) + } } diff --git a/cpg-console/src/test/resources/vulnerable.cpp b/cpg-console/src/test/resources/vulnerable.cpp index 01eecb057b..c3aa5998d6 100644 --- a/cpg-console/src/test/resources/vulnerable.cpp +++ b/cpg-console/src/test/resources/vulnerable.cpp @@ -4,4 +4,13 @@ int main() { printf(array); free(array); free(array); + + int a = 2; + if(array == "hello") { + a = 0; + } + double x = 5/a; + + int b = 2147483648; + b = 2147483648; } \ No newline at end of file diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt index b2cab1aa49..af91b40677 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Assignment.kt @@ -50,6 +50,6 @@ interface Assignment { * The target of an assignment. The target is usually either a [VariableDeclaration] or a * [DeclaredReferenceExpression]. */ -interface AssignmentTarget { +interface AssignmentTarget : HasType { val name: String } From 51e4913c04fb359ad4d3cfd48d42a01b1ec93bed Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 12 Aug 2022 11:25:09 +0200 Subject: [PATCH 54/67] Tests++ --- .../aisec/cpg/analysis/QueryTest.kt | 30 ++++++++++++++++++- cpg-console/src/test/resources/vulnerable.cpp | 4 ++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 23aec2965f..4fe2f287b9 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -411,6 +411,34 @@ class QueryTest { println(queryTreeResult2.printNicely()) } + @Test + fun testOutOfBoundsQueryExists() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/array.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.exists( + mustSatisfy = { + max(it.subscriptExpression) >= min(it.size) || min(it.subscriptExpression) < 0 + } + ) + assertTrue(queryTreeResult.first) + + val mustSatisfy = { it: ArraySubscriptionExpression -> + (it.subscriptExpression.max ge it.size.min) or (it.subscriptExpression.min lt 0) + } + val queryTreeResult2 = result.exists(mustSatisfy = mustSatisfy) + assertTrue(queryTreeResult2.value) + println(queryTreeResult2.printNicely()) + } + @Test fun testOutOfBoundsQuery2() { val config = @@ -565,7 +593,7 @@ class QueryTest { min(it.value) >= minSizeOfType(it.target!!.type) } ) - // assertFalse(queryTreeResult.first) + assertFalse(queryTreeResult.first) val mustSatisfy = { it: Assignment -> (max(it.value) le maxSizeOfType(it.target!!.type)) and diff --git a/cpg-console/src/test/resources/vulnerable.cpp b/cpg-console/src/test/resources/vulnerable.cpp index c3aa5998d6..dcaa510dbb 100644 --- a/cpg-console/src/test/resources/vulnerable.cpp +++ b/cpg-console/src/test/resources/vulnerable.cpp @@ -5,12 +5,14 @@ int main() { free(array); free(array); - int a = 2; + short a = 2; if(array == "hello") { a = 0; } + double x = 5/a; int b = 2147483648; b = 2147483648; + long c = -10000; } \ No newline at end of file From e318e79c7e01aac928982526d46713ee46bf1703 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 12 Aug 2022 13:37:37 +0200 Subject: [PATCH 55/67] Fix test to match its name --- .../aisec/cpg/analysis/QueryTest.kt | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 4fe2f287b9..051a9c6371 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -120,7 +120,7 @@ class QueryTest { } @Test - fun testDoubleFree() { + fun testUseAfterFree() { val config = TranslationConfiguration.builder() .sourceLocations(File("src/test/resources/vulnerable.cpp")) @@ -139,8 +139,8 @@ class QueryTest { } .value } + assertFalse(queryTreeResult.first) - println(queryTreeResult.second) val mustSatisfy = { outer: CallExpression -> not( @@ -152,6 +152,48 @@ class QueryTest { } val queryTreeResult2 = result.all({ it.name == "free" }, mustSatisfy) + assertFalse(queryTreeResult2.value) + } + + @Test + fun testDoubleFree() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all({ it.name == "free" }) { outer -> + !executionPath(outer) { + (it as? CallExpression)?.name == "free" && + ((it as? CallExpression)?.arguments?.getOrNull(0) + as? DeclaredReferenceExpression) + ?.refersTo == + (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + } + .value + } + assertFalse(queryTreeResult.first) + println(queryTreeResult.second) + + val mustSatisfy = { outer: CallExpression -> + not( + executionPath(outer) { + (it as? CallExpression)?.name == "free" && + ((it as? CallExpression)?.arguments?.getOrNull(0) + as? DeclaredReferenceExpression) + ?.refersTo == + (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + } + ) + } + val queryTreeResult2 = result.all({ it.name == "free" }, mustSatisfy) + assertFalse(queryTreeResult2.value) println(queryTreeResult2.printNicely()) } From 412fe7141890378307ae09b98d549d5bc5714114 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 16 Aug 2022 08:50:01 +0200 Subject: [PATCH 56/67] Javadoc, Consistency of followPrevDFGEdgesUntilHit --- .../aisec/cpg/analysis/QueryTest.kt | 57 +++++++++++-- cpg-console/src/test/resources/Dataflow.java | 20 +++++ .../fraunhofer/aisec/cpg/graph/Extensions.kt | 79 ++++++++++++++++--- 3 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 cpg-console/src/test/resources/Dataflow.java diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 051a9c6371..21e83f364f 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -28,14 +28,14 @@ package de.fraunhofer.aisec.cpg.analysis import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager -import de.fraunhofer.aisec.cpg.graph.Assignment +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.evaluate -import de.fraunhofer.aisec.cpg.graph.followPrevDFGEdgesUntilHit import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.passes.EdgeCachePass import de.fraunhofer.aisec.cpg.query.* import java.io.File +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue import org.junit.jupiter.api.Test @@ -533,7 +533,9 @@ class QueryTest { .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } - .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } + .map { it2 -> + (it2.last() as ArrayCreationExpression).dimensions[0] + } ) && min(it.subscriptExpression) > 0 } ) @@ -545,7 +547,7 @@ class QueryTest { ((it.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } - .map { it2 -> (it2 as ArrayCreationExpression).dimensions[0] } + .map { it2 -> (it2.last() as ArrayCreationExpression).dimensions[0] } )) and (min(it.subscriptExpression) ge 0) } val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) @@ -647,4 +649,49 @@ class QueryTest { println(queryTreeResult2.printNicely()) assertFalse(queryTreeResult2.value) } + + @Test + fun testDataFlowRequirement() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/Dataflow.java")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.all( + { it.name == "toString" }, + { n1 -> + result + .all( + { it.name == "print" }, + { n2 -> dataFlow(n1, n2.parameters[0]).value } + ) + .first + } + ) + + assertTrue(queryTreeResult.first) + assertEquals(1, queryTreeResult.second.size) + + /*val queryTreeResult2 = + result.all( + { (it.value as? CallExpression)?.name == "toString" }, + { n1 -> + result + .all( + { it.name == "print" }, + { n2 -> dataFlow(n1 as Node, n2.parameters[0]).value } + ) + .first + } + ) + + assertTrue(queryTreeResult2.first) + assertEquals(1, queryTreeResult2.second.size)*/ + } } diff --git a/cpg-console/src/test/resources/Dataflow.java b/cpg-console/src/test/resources/Dataflow.java new file mode 100644 index 0000000000..4f905fac8e --- /dev/null +++ b/cpg-console/src/test/resources/Dataflow.java @@ -0,0 +1,20 @@ +public class Dataflow { + public String toString() { + return "Dataflow: attr=" + attr; + } + + public String test() { return "abcd"; } + + public int print(String s) { + System.out.println(s); + } + + + public static void main(String[] args) { + Dataflow sc = new Dataflow(); + String s = sc.toString(); + sc.print(s); + + sc.print(sc.test()); + } +} \ No newline at end of file diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 550028a944..80d8d9ddef 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -136,21 +136,41 @@ class StatementNotFound : Exception() class DeclarationNotFound(message: String) : Exception(message) -fun Node.followPrevDFGEdgesUntilHit(predicate: (Node) -> Boolean): List { - val result = mutableListOf() - val alreadySeen = mutableListOf() - val worklist = mutableListOf() - worklist.add(this) +/** + * Returns a list of nodes which are data flow paths between the starting node [this] and the end + * node fulfilling [predicate]. Paths which do not end at such a node are not included in the + * result. Hence, if the return value is a non-empty list, a data flow from the end node to [this] + * is **possible but not mandatory**. This method traverses the path backwards! + * + * It returns all possible paths. + */ +fun Node.followPrevDFGEdgesUntilHit(predicate: (Node) -> Boolean): List> { + val result = mutableListOf>() + val failedPaths = mutableListOf>() + val worklist = mutableListOf>() + worklist.add(listOf(this)) while (worklist.isNotEmpty()) { - val currentNode = worklist.removeFirst() - alreadySeen.add(currentNode) - for (prev in currentNode.prevDFG) { + val currentPath = worklist.removeFirst() + if (currentPath.last().prevDFG.isEmpty()) { + // No further nodes in the path and the path criteria are not satisfied. + failedPaths.add(currentPath) + continue + } + + for (prev in currentPath.last().prevDFG) { + // Copy the path for each outgoing DFG edge and add the prev node + val nextPath = mutableListOf() + nextPath.addAll(currentPath) + nextPath.add(prev) + if (predicate(prev)) { - result.add(prev) + result.add(nextPath) } - if (!alreadySeen.contains(prev)) { - worklist.add(prev) + // The prev node is new in the current path (i.e., there's no loop), so we add the path + // with the next step to the worklist. + if (!currentPath.contains(prev)) { + worklist.add(nextPath) } } } @@ -163,11 +183,15 @@ fun Node.followPrevDFGEdgesUntilHit(predicate: (Node) -> Boolean): List { * node fulfilling [predicate]. Paths which do not end at such a node are not included in the * result. Hence, if the return value is a non-empty list, a data flow from [this] to such a node is * **possible but not mandatory**. + * + * It returns all possible paths. */ fun Node.followNextDFGEdgesUntilHit(predicate: (Node) -> Boolean): List> { // Looks complicated but at least it's not recursive... // result: List of paths (between from and to) val result = mutableListOf>() + // failedPaths: All the paths which do not satisfy "predicate" + val failedPaths = mutableListOf>() // The list of paths where we're not done yet. val worklist = mutableListOf>() worklist.add(listOf(this)) // We start only with the "from" node (=this) @@ -176,13 +200,20 @@ fun Node.followNextDFGEdgesUntilHit(predicate: (Node) -> Boolean): List() nextPath.addAll(currentPath) nextPath.add(next) if (predicate(next)) { - // We ended up in the node "to", so we're done. Add the path to the results. + // We ended up in the node fulfilling "predicate", so we're done for this path. Add + // the path to the results. result.add(nextPath) } // The next node is new in the current path (i.e., there's no loop), so we add the path @@ -201,11 +232,15 @@ fun Node.followNextDFGEdgesUntilHit(predicate: (Node) -> Boolean): List Boolean): List> { // Looks complicated but at least it's not recursive... // result: List of paths (between from and to) val result = mutableListOf>() + // failedPaths: All the paths which do not satisfy "predicate" + val failedPaths = mutableListOf>() // The list of paths where we're not done yet. val worklist = mutableListOf>() worklist.add(listOf(this)) // We start only with the "from" node (=this) @@ -214,6 +249,12 @@ fun Node.followNextEOGEdgesUntilHit(predicate: (Node) -> Boolean): List() @@ -234,6 +275,13 @@ fun Node.followNextEOGEdgesUntilHit(predicate: (Node) -> Boolean): List) -> Boolean): List>? { val path = mutableListOf>() @@ -257,6 +305,13 @@ fun Node.followPrevEOG(predicate: (PropertyEdge<*>) -> Boolean): List Boolean): MutableList? { val path = mutableListOf() From 3e486ae9b3e3986aedb739466ee69eb5d042a529 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 16 Aug 2022 09:03:09 +0200 Subject: [PATCH 57/67] Rename overloaded functions to allExtended/existsExtended --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 9 +- .../aisec/cpg/analysis/QueryTest.kt | 268 +++++++++++------- 2 files changed, 172 insertions(+), 105 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index dd5841aba1..b7bc5ec34b 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -33,7 +33,6 @@ import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.types.Type -import kotlin.experimental.ExperimentalTypeInference /** * Evaluates if the conditions specified in [mustSatisfy] hold for all nodes in the graph. @@ -45,9 +44,7 @@ import kotlin.experimental.ExperimentalTypeInference * This method can be used similar to the logical implication to test "sel => mustSatisfy". */ @ExperimentalGraph -@OptIn(ExperimentalTypeInference::class) -@OverloadResolutionByLambdaReturnType -inline fun Node.all( +inline fun Node.allExtended( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree ): QueryTree { @@ -107,9 +104,7 @@ inline fun Node.all( * the resulting reasoning chain. */ @ExperimentalGraph -@OptIn(ExperimentalTypeInference::class) -@OverloadResolutionByLambdaReturnType -inline fun Node.exists( +inline fun Node.existsExtended( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree ): QueryTree { diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 21e83f364f..86730dfaad 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -62,11 +62,11 @@ class QueryTest { assertFalse(queryTreeResult.first) - val mustSatisfy = { it: CallExpression -> - sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) - } val queryTreeResult2: QueryTree = - result.all({ it.name == "memcpy" }, mustSatisfy) + result.allExtended( + { it.name == "memcpy" }, + { sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) } + ) assertFalse(queryTreeResult2.value) println(queryTreeResult2.printNicely()) @@ -92,8 +92,11 @@ class QueryTest { } assertFalse(queryTreeResult.first) - val mustSatisfy = { it: CallExpression -> it.arguments[0].size gt it.arguments[1].size } - val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + val queryTreeResult2 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[0].size gt it.arguments[1].size } + ) assertFalse(queryTreeResult2.value) } @@ -110,11 +113,13 @@ class QueryTest { val analyzer = TranslationManager.builder().config(config).build() val result = analyzer.analyze().get() - val mustSatisfy = { it: CallExpression -> - (const("memcpy") eq it.name) implies - (lazy { it.arguments[0].size gt it.arguments[1].size }) - } - val queryTreeResult = result.all(mustSatisfy = mustSatisfy) + val queryTreeResult = + result.allExtended( + mustSatisfy = { + (const("memcpy") eq it.name) implies + (lazy { it.arguments[0].size gt it.arguments[1].size }) + } + ) assertFalse(queryTreeResult.value) } @@ -142,15 +147,18 @@ class QueryTest { assertFalse(queryTreeResult.first) - val mustSatisfy = { outer: CallExpression -> - not( - executionPath(outer) { - (it as? DeclaredReferenceExpression)?.refersTo == - (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + val queryTreeResult2 = + result.allExtended( + { it.name == "free" }, + { outer -> + not( + executionPath(outer) { + (it as? DeclaredReferenceExpression)?.refersTo == + (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + } + ) } ) - } - val queryTreeResult2 = result.all({ it.name == "free" }, mustSatisfy) assertFalse(queryTreeResult2.value) } @@ -181,18 +189,21 @@ class QueryTest { assertFalse(queryTreeResult.first) println(queryTreeResult.second) - val mustSatisfy = { outer: CallExpression -> - not( - executionPath(outer) { - (it as? CallExpression)?.name == "free" && - ((it as? CallExpression)?.arguments?.getOrNull(0) - as? DeclaredReferenceExpression) - ?.refersTo == - (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + val queryTreeResult2 = + result.allExtended( + { it.name == "free" }, + { outer -> + not( + executionPath(outer) { + (it as? CallExpression)?.name == "free" && + ((it as? CallExpression)?.arguments?.getOrNull(0) + as? DeclaredReferenceExpression) + ?.refersTo == + (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo + } + ) } ) - } - val queryTreeResult2 = result.all({ it.name == "free" }, mustSatisfy) assertFalse(queryTreeResult2.value) println(queryTreeResult2.printNicely()) @@ -216,13 +227,19 @@ class QueryTest { } assertTrue(queryTreeResult.first) - val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! ge 11 } - val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + val queryTreeResult2 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! ge 11 } + ) assertTrue(queryTreeResult2.value) - val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! ge const(11) } - val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + val queryTreeResult3 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! ge const(11) } + ) assertTrue(queryTreeResult3.value) } @@ -245,13 +262,19 @@ class QueryTest { } assertFalse(queryTreeResult.first) - val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! gt 11 } - val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + val queryTreeResult2 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! gt 11 } + ) assertFalse(queryTreeResult2.value) - val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! gt const(11) } - val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + val queryTreeResult3 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! gt const(11) } + ) assertFalse(queryTreeResult3.value) } @@ -274,13 +297,19 @@ class QueryTest { } assertTrue(queryTreeResult.first) - val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! le 11 } - val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + val queryTreeResult2 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! le 11 } + ) assertTrue(queryTreeResult2.value) - val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! le const(11) } - val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + val queryTreeResult3 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! le const(11) } + ) assertTrue(queryTreeResult3.value) } @@ -303,13 +332,19 @@ class QueryTest { } assertTrue(queryTreeResult.first) - val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! eq 11 } - val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + val queryTreeResult2 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! eq 11 } + ) assertTrue(queryTreeResult2.value) - val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! eq const(11) } - val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + val queryTreeResult3 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! eq const(11) } + ) assertTrue(queryTreeResult3.value) } @@ -332,13 +367,19 @@ class QueryTest { } assertFalse(queryTreeResult.first) - val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! lt 11 } - val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + val queryTreeResult2 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! lt 11 } + ) assertFalse(queryTreeResult2.value) - val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! lt const(11) } - val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + val queryTreeResult3 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! lt const(11) } + ) assertFalse(queryTreeResult3.value) } @@ -361,13 +402,19 @@ class QueryTest { } assertFalse(queryTreeResult.first) - val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! ne 11 } - val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + val queryTreeResult2 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! ne 11 } + ) assertFalse(queryTreeResult2.value) - val mustSatisfy2 = { it: CallExpression -> it.arguments[2].intValue!! ne const(11) } - val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + val queryTreeResult3 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! ne const(11) } + ) assertFalse(queryTreeResult3.value) } @@ -390,15 +437,19 @@ class QueryTest { } assertTrue(queryTreeResult.first) - val mustSatisfy = { it: CallExpression -> it.arguments[2].intValue!! IN listOf(11, 2, 3) } - val queryTreeResult2 = result.all({ it.name == "memcpy" }, mustSatisfy) + val queryTreeResult2 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! IN listOf(11, 2, 3) } + ) assertTrue(queryTreeResult2.value) - val mustSatisfy2 = { it: CallExpression -> - it.arguments[2].intValue!! IN const(listOf(11, 2, 3)) - } - val queryTreeResult3 = result.all({ it.name == "memcpy" }, mustSatisfy2) + val queryTreeResult3 = + result.allExtended( + { it.name == "memcpy" }, + { it.arguments[2].intValue!! IN const(listOf(11, 2, 3)) } + ) assertTrue(queryTreeResult3.value) } @@ -419,8 +470,10 @@ class QueryTest { result.all(mustSatisfy = { (it.value.invoke() as QueryTree) < 5 }) assertTrue(queryTreeResult.first) - val mustSatisfy = { it: Assignment -> it.value.invoke() as QueryTree lt 5 } - val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) + val queryTreeResult2 = + result.allExtended( + mustSatisfy = { it.value.invoke() as QueryTree lt 5 } + ) assertTrue(queryTreeResult2.value) } @@ -445,10 +498,13 @@ class QueryTest { ) assertFalse(queryTreeResult.first) - val mustSatisfy = { it: ArraySubscriptionExpression -> - (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) ge 0) - } - val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) + val queryTreeResult2 = + result.allExtended( + mustSatisfy = { + (max(it.subscriptExpression) lt min(it.size)) and + (min(it.subscriptExpression) ge 0) + } + ) assertFalse(queryTreeResult2.value) println(queryTreeResult2.printNicely()) } @@ -473,10 +529,12 @@ class QueryTest { ) assertTrue(queryTreeResult.first) - val mustSatisfy = { it: ArraySubscriptionExpression -> - (it.subscriptExpression.max ge it.size.min) or (it.subscriptExpression.min lt 0) - } - val queryTreeResult2 = result.exists(mustSatisfy = mustSatisfy) + val queryTreeResult2 = + result.existsExtended( + mustSatisfy = { + (it.subscriptExpression.max ge it.size.min) or (it.subscriptExpression.min lt 0) + } + ) assertTrue(queryTreeResult2.value) println(queryTreeResult2.printNicely()) } @@ -502,10 +560,13 @@ class QueryTest { ) assertFalse(queryTreeResult.first) - val mustSatisfy = { it: ArraySubscriptionExpression -> - (max(it.subscriptExpression) lt min(it.size)) and (min(it.subscriptExpression) ge 0) - } - val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) + val queryTreeResult2 = + result.allExtended( + mustSatisfy = { + (max(it.subscriptExpression) lt min(it.size)) and + (min(it.subscriptExpression) ge 0) + } + ) assertFalse(queryTreeResult2.value) println(queryTreeResult2.printNicely()) } @@ -541,16 +602,22 @@ class QueryTest { ) assertFalse(queryTreeResult.first) - val mustSatisfy = { it: ArraySubscriptionExpression -> - (max(it.subscriptExpression) lt - min( - ((it.arrayExpression as DeclaredReferenceExpression).refersTo - as VariableDeclaration) - .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } - .map { it2 -> (it2.last() as ArrayCreationExpression).dimensions[0] } - )) and (min(it.subscriptExpression) ge 0) - } - val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) + val queryTreeResult2 = + result.allExtended( + mustSatisfy = { + (max(it.subscriptExpression) lt + min( + ((it.arrayExpression as DeclaredReferenceExpression).refersTo + as VariableDeclaration) + .followPrevDFGEdgesUntilHit { node -> + node is ArrayCreationExpression + } + .map { it2 -> + (it2.last() as ArrayCreationExpression).dimensions[0] + } + )) and (min(it.subscriptExpression) ge 0) + } + ) assertFalse(queryTreeResult2.value) println(queryTreeResult2.printNicely()) } @@ -579,13 +646,15 @@ class QueryTest { ) assertTrue(queryTreeResult.first) - val mustSatisfy = { it: ArraySubscriptionExpression -> - val max_sub = max(it.subscriptExpression) - val min_dim = min(it.size) - val min_sub = min(it.subscriptExpression) - (max_sub lt min_dim) and (min_sub ge 0) - } - val queryTreeResult2 = result.all(mustSatisfy = mustSatisfy) + val queryTreeResult2 = + result.allExtended( + mustSatisfy = { + val max_sub = max(it.subscriptExpression) + val min_dim = min(it.size) + val min_sub = min(it.subscriptExpression) + return@allExtended (max_sub lt min_dim) and (min_sub ge 0) + } + ) assertTrue(queryTreeResult2.value) println(queryTreeResult2.printNicely()) } @@ -609,10 +678,11 @@ class QueryTest { ) assertFalse(queryTreeResult.first) - val mustSatisfy = { it: BinaryOperator -> - not((it.rhs.evaluate(MultiValueEvaluator()) as NumberSet).maybe(0)) - } - val queryTreeResult2 = result.all({ it.operatorCode == "/" }, mustSatisfy) + val queryTreeResult2 = + result.allExtended( + { it.operatorCode == "/" }, + { not((it.rhs.evaluate(MultiValueEvaluator()) as NumberSet).maybe(0)) } + ) assertFalse(queryTreeResult2.value) } @@ -639,12 +709,14 @@ class QueryTest { ) assertFalse(queryTreeResult.first) - val mustSatisfy = { it: Assignment -> - (max(it.value) le maxSizeOfType(it.target!!.type)) and - (min(it.value) ge minSizeOfType(it.target!!.type)) - } val queryTreeResult2 = - result.all({ it.target?.type?.isPrimitive == true }, mustSatisfy) + result.allExtended( + { it.target?.type?.isPrimitive == true }, + { + (max(it.value) le maxSizeOfType(it.target!!.type)) and + (min(it.value) ge minSizeOfType(it.target!!.type)) + } + ) println(queryTreeResult2.printNicely()) assertFalse(queryTreeResult2.value) From c2e7b9eb9dd77d51a90a8639f5c03848243e0def Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 16 Aug 2022 09:06:13 +0200 Subject: [PATCH 58/67] Update Readme --- .../kotlin/de/fraunhofer/aisec/cpg/query/README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md index 54c8e41557..9c25867dd7 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md @@ -92,10 +92,18 @@ val memcpyTooLargeQuery = { node: CallExpression -> } ``` -After assembling a query of the respective operators and functions, we want to run it for a subset of nodes in the graph. We therefore provide two operators: `all` and `exists`. Both are used in a similar way. +After assembling a query of the respective operators and functions, we want to run it for a subset of nodes in the graph. We therefore provide two operators: `all` (or `allExtended` for the detailed output) and `exists` (or `existsExtended` for the detailed output). Both are used in a similar way. They enable the user to optionally specify conditions to determine on which nodes we want to run a query (e.g., only on `CallExpression`s which call a function called "memcpy"). -The following snippet uses the query from above to run it on all calls of the function "memcpy" contained in the `TranslationResult` `result`: +The following snippets use the queries from above to run them on all calls of the function "memcpy" contained in the `TranslationResult` `result`: +```kotlin +val queryTreeResult = + result.allExtended( + { it.name == "memcpy" }, + { sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) } + ) +``` +Less detailled: ```kotlin val queryTreeResult = result.all( From 98c894701ac1564f713a6364c0c49b7ef09f3c09 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 16 Aug 2022 09:10:09 +0200 Subject: [PATCH 59/67] Javadoc++ --- .../main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt index af8163e0cf..76b2e62d66 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt +++ b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt @@ -30,7 +30,9 @@ import de.fraunhofer.aisec.cpg.graph.compareTo /** * Holds the [value] to which the statements have been evaluated. The [children] define previous * steps of the evaluation, thus building a tree of all steps of the evaluation recursively until we - * reach the nodes of the CPG. + * reach the nodes of the CPG. This is necessary if we want to store all steps which are performed + * when evaluating a query. It helps to make the reasoning of the query more understandable to the + * user and gives an analyst the maximum of information available. * * Numerous methods allow to evaluate the queries while keeping track of all the steps. Currently, * the following operations are supported: From b465f93ae1db49a8b3f5207a7124ba36757698a4 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 16 Aug 2022 10:09:42 +0200 Subject: [PATCH 60/67] Fix broken dataflow test --- .../aisec/cpg/analysis/QueryTest.kt | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt index 86730dfaad..3f67f1b151 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt @@ -748,11 +748,25 @@ class QueryTest { ) assertTrue(queryTreeResult.first) - assertEquals(1, queryTreeResult.second.size) + assertEquals(0, queryTreeResult.second.size) - /*val queryTreeResult2 = - result.all( - { (it.value as? CallExpression)?.name == "toString" }, + val queryTreeResultExtended = + result.allExtended( + { it.name == "toString" }, + { n1 -> + result.allExtended( + { it.name == "print" }, + { n2 -> dataFlow(n1, n2.parameters[0]) } + ) + } + ) + + assertTrue(queryTreeResultExtended.value) + assertEquals(1, queryTreeResultExtended.children.size) + + val queryTreeResult2 = + result.all( + { it.name == "test" }, { n1 -> result .all( @@ -764,6 +778,20 @@ class QueryTest { ) assertTrue(queryTreeResult2.first) - assertEquals(1, queryTreeResult2.second.size)*/ + assertEquals(0, queryTreeResult2.second.size) + + val queryTreeResult2Extended = + result.allExtended( + { it.name == "test" }, + { n1 -> + result.allExtended( + { it.name == "print" }, + { n2 -> dataFlow(n1 as Node, n2.parameters[0]) } + ) + } + ) + + assertTrue(queryTreeResult2Extended.value) + assertEquals(1, queryTreeResult2Extended.children.size) } } From d55e6829deffcfb874c49a55502250e70e467101 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 16 Aug 2022 12:05:53 +0200 Subject: [PATCH 61/67] Move query API to cpg-analysis and merge Shortcuts with Extensions --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 4 +- .../fraunhofer/aisec/cpg/query/QueryTree.kt | 0 .../de/fraunhofer/aisec/cpg/query/README.md | 0 .../fraunhofer/aisec/cpg/query}/QueryTest.kt | 176 +++++++++--------- .../src/test/resources/query}/Dataflow.java | 0 .../src/test/resources/query}/array.cpp | 0 .../src/test/resources/query}/array2.cpp | 0 .../src/test/resources/query}/array3.cpp | 0 .../test/resources/query}/array_correct.cpp | 0 .../src/test/resources/query}/assign.cpp | 0 .../src/test/resources/query}/vulnerable.cpp | 0 .../aisec/cpg/query/QueryShortcuts.kt | 115 ------------ .../fraunhofer/aisec/cpg/graph/Extensions.kt | 97 +++++++++- .../aisec/cpg/passes/EdgeCachePass.kt | 0 .../aisec/cpg/graph}/ShortcutsTest.kt | 6 +- .../src/test/resources/ShortcutClass.java | 0 16 files changed, 185 insertions(+), 213 deletions(-) rename {cpg-console => cpg-analysis}/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt (99%) rename {cpg-console => cpg-analysis}/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt (100%) rename {cpg-console => cpg-analysis}/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md (100%) rename {cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis => cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query}/QueryTest.kt (79%) rename {cpg-console/src/test/resources => cpg-analysis/src/test/resources/query}/Dataflow.java (100%) rename {cpg-console/src/test/resources => cpg-analysis/src/test/resources/query}/array.cpp (100%) rename {cpg-console/src/test/resources => cpg-analysis/src/test/resources/query}/array2.cpp (100%) rename {cpg-console/src/test/resources => cpg-analysis/src/test/resources/query}/array3.cpp (100%) rename {cpg-console/src/test/resources => cpg-analysis/src/test/resources/query}/array_correct.cpp (100%) rename {cpg-console/src/test/resources => cpg-analysis/src/test/resources/query}/assign.cpp (100%) rename {cpg-console/src/test/resources => cpg-analysis/src/test/resources/query}/vulnerable.cpp (100%) delete mode 100644 cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt rename {cpg-analysis/src/main/kotlin => cpg-core/src/main/java}/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt (100%) rename {cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis => cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph}/ShortcutsTest.kt (98%) rename {cpg-console => cpg-core}/src/test/resources/ShortcutClass.java (100%) diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt similarity index 99% rename from cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index b7bc5ec34b..54e7077702 100644 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -44,7 +44,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type * This method can be used similar to the logical implication to test "sel => mustSatisfy". */ @ExperimentalGraph -inline fun Node.allExtended( +inline fun Node.forallExtended( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree ): QueryTree { @@ -76,7 +76,7 @@ inline fun Node.allExtended( * This method can be used similar to the logical implication to test "sel => mustSatisfy". */ @ExperimentalGraph -inline fun Node.all( +inline fun Node.forall( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> Boolean ): Pair> { diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt similarity index 100% rename from cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryTree.kt diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md similarity index 100% rename from cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md rename to cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt similarity index 79% rename from cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt rename to cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt index 3f67f1b151..8cfe59355f 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/QueryTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt @@ -23,11 +23,13 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis +package de.fraunhofer.aisec.cpg.query import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager +import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator +import de.fraunhofer.aisec.cpg.analysis.NumberSet import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration @@ -46,7 +48,7 @@ class QueryTest { fun testMemcpyTooLargeQuery2() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -55,7 +57,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.forall( { it.name == "memcpy" }, { sizeof(it.arguments[0]) > sizeof(it.arguments[1]) } ) @@ -63,7 +65,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2: QueryTree = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) } ) @@ -78,7 +80,7 @@ class QueryTest { fun testMemcpyTooLargeQuery() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -87,13 +89,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { + result.forall({ it.name == "memcpy" }) { it.arguments[0].size > it.arguments[1].size } assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[0].size gt it.arguments[1].size } ) @@ -105,7 +107,7 @@ class QueryTest { fun testMemcpyTooLargeQueryImplies() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -114,7 +116,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.allExtended( + result.forallExtended( mustSatisfy = { (const("memcpy") eq it.name) implies (lazy { it.arguments[0].size gt it.arguments[1].size }) @@ -128,7 +130,7 @@ class QueryTest { fun testUseAfterFree() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -137,7 +139,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "free" }) { outer -> + result.forall({ it.name == "free" }) { outer -> !executionPath(outer) { (it as? DeclaredReferenceExpression)?.refersTo == (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo @@ -148,7 +150,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "free" }, { outer -> not( @@ -167,7 +169,7 @@ class QueryTest { fun testDoubleFree() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -176,7 +178,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "free" }) { outer -> + result.forall({ it.name == "free" }) { outer -> !executionPath(outer) { (it as? CallExpression)?.name == "free" && ((it as? CallExpression)?.arguments?.getOrNull(0) @@ -190,7 +192,7 @@ class QueryTest { println(queryTreeResult.second) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "free" }, { outer -> not( @@ -213,7 +215,7 @@ class QueryTest { fun testParameterGreaterThanOrEqualConst() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -222,13 +224,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { + result.forall({ it.name == "memcpy" }) { it.arguments[2].intValue!! >= const(11) } assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! ge 11 } ) @@ -236,7 +238,7 @@ class QueryTest { assertTrue(queryTreeResult2.value) val queryTreeResult3 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! ge const(11) } ) @@ -248,7 +250,7 @@ class QueryTest { fun testParameterGreaterThanConst() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -257,13 +259,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { + result.forall({ it.name == "memcpy" }) { it.arguments[2].intValue!! > const(11) } assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! gt 11 } ) @@ -271,7 +273,7 @@ class QueryTest { assertFalse(queryTreeResult2.value) val queryTreeResult3 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! gt const(11) } ) @@ -283,7 +285,7 @@ class QueryTest { fun testParameterLessThanOrEqualConst() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -292,13 +294,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { + result.forall({ it.name == "memcpy" }) { it.arguments[2].intValue!! <= const(11) } assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! le 11 } ) @@ -306,7 +308,7 @@ class QueryTest { assertTrue(queryTreeResult2.value) val queryTreeResult3 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! le const(11) } ) @@ -318,7 +320,7 @@ class QueryTest { fun testParameterEqualsConst() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -327,13 +329,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { + result.forall({ it.name == "memcpy" }) { it.arguments[2].intValue!! == const(11) } assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! eq 11 } ) @@ -341,7 +343,7 @@ class QueryTest { assertTrue(queryTreeResult2.value) val queryTreeResult3 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! eq const(11) } ) @@ -353,7 +355,7 @@ class QueryTest { fun testParameterLessThanConst() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -362,13 +364,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { + result.forall({ it.name == "memcpy" }) { it.arguments[2].intValue!! < const(11) } assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! lt 11 } ) @@ -376,7 +378,7 @@ class QueryTest { assertFalse(queryTreeResult2.value) val queryTreeResult3 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! lt const(11) } ) @@ -388,7 +390,7 @@ class QueryTest { fun testParameterNotEqualsConst() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -397,13 +399,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { + result.forall({ it.name == "memcpy" }) { it.arguments[2].intValue!! != const(11) } assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! ne 11 } ) @@ -411,7 +413,7 @@ class QueryTest { assertFalse(queryTreeResult2.value) val queryTreeResult3 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! ne const(11) } ) @@ -423,7 +425,7 @@ class QueryTest { fun testParameterIn() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -432,13 +434,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all({ it.name == "memcpy" }) { + result.forall({ it.name == "memcpy" }) { it.arguments[2].intValue!!.value in listOf(11, 2, 3) } assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! IN listOf(11, 2, 3) } ) @@ -446,7 +448,7 @@ class QueryTest { assertTrue(queryTreeResult2.value) val queryTreeResult3 = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! IN const(listOf(11, 2, 3)) } ) @@ -458,7 +460,7 @@ class QueryTest { fun testAssign() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/assign.cpp")) + .sourceLocations(File("src/test/resources/query/assign.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -467,11 +469,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all(mustSatisfy = { (it.value.invoke() as QueryTree) < 5 }) + result.forall( + mustSatisfy = { (it.value.invoke() as QueryTree) < 5 } + ) assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( mustSatisfy = { it.value.invoke() as QueryTree lt 5 } ) @@ -482,7 +486,7 @@ class QueryTest { fun testOutOfBoundsQuery() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array.cpp")) + .sourceLocations(File("src/test/resources/query/array.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -491,17 +495,18 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.forall( mustSatisfy = { - max(it.subscriptExpression) < min(it.size) && min(it.subscriptExpression) >= 0 + max(it.subscriptExpression) < min(it.arraySize) && + min(it.subscriptExpression) >= 0 } ) assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( mustSatisfy = { - (max(it.subscriptExpression) lt min(it.size)) and + (max(it.subscriptExpression) lt min(it.arraySize)) and (min(it.subscriptExpression) ge 0) } ) @@ -513,7 +518,7 @@ class QueryTest { fun testOutOfBoundsQueryExists() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array.cpp")) + .sourceLocations(File("src/test/resources/query/array.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -524,7 +529,8 @@ class QueryTest { val queryTreeResult = result.exists( mustSatisfy = { - max(it.subscriptExpression) >= min(it.size) || min(it.subscriptExpression) < 0 + max(it.subscriptExpression) >= min(it.arraySize) || + min(it.subscriptExpression) < 0 } ) assertTrue(queryTreeResult.first) @@ -532,7 +538,8 @@ class QueryTest { val queryTreeResult2 = result.existsExtended( mustSatisfy = { - (it.subscriptExpression.max ge it.size.min) or (it.subscriptExpression.min lt 0) + (it.subscriptExpression.max ge it.arraySize.min) or + (it.subscriptExpression.min lt 0) } ) assertTrue(queryTreeResult2.value) @@ -543,7 +550,7 @@ class QueryTest { fun testOutOfBoundsQuery2() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array2.cpp")) + .sourceLocations(File("src/test/resources/query/array2.cpp")) .defaultPasses() .defaultLanguages() .registerPass(EdgeCachePass()) @@ -553,17 +560,18 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.forall( mustSatisfy = { - max(it.subscriptExpression) < min(it.size) && min(it.subscriptExpression) >= 0 + max(it.subscriptExpression) < min(it.arraySize) && + min(it.subscriptExpression) >= 0 } ) assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( mustSatisfy = { - (max(it.subscriptExpression) lt min(it.size)) and + (max(it.subscriptExpression) lt min(it.arraySize)) and (min(it.subscriptExpression) ge 0) } ) @@ -575,7 +583,7 @@ class QueryTest { fun testOutOfBoundsQuery3() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array3.cpp")) + .sourceLocations(File("src/test/resources/query/array3.cpp")) .defaultPasses() .defaultLanguages() .registerPass(EdgeCachePass()) @@ -585,7 +593,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.forall( mustSatisfy = { max(it.subscriptExpression) < min( @@ -603,7 +611,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( mustSatisfy = { (max(it.subscriptExpression) lt min( @@ -626,7 +634,7 @@ class QueryTest { fun testOutOfBoundsQueryCorrect() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/array_correct.cpp")) + .sourceLocations(File("src/test/resources/query/array_correct.cpp")) .defaultPasses() .defaultLanguages() .registerPass(EdgeCachePass()) @@ -636,23 +644,23 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.forall( mustSatisfy = { val max_sub = max(it.subscriptExpression) - val min_dim = min(it.size) + val min_dim = min(it.arraySize) val min_sub = min(it.subscriptExpression) - return@all max_sub < min_dim && min_sub >= 0 + return@forall max_sub < min_dim && min_sub >= 0 } ) assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( mustSatisfy = { val max_sub = max(it.subscriptExpression) - val min_dim = min(it.size) + val min_dim = min(it.arraySize) val min_sub = min(it.subscriptExpression) - return@allExtended (max_sub lt min_dim) and (min_sub ge 0) + return@forallExtended (max_sub lt min_dim) and (min_sub ge 0) } ) assertTrue(queryTreeResult2.value) @@ -663,7 +671,7 @@ class QueryTest { fun testDivisionBy0() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -672,14 +680,14 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.forall( { it.operatorCode == "/" }, { !(it.rhs.evaluate(MultiValueEvaluator()) as NumberSet).maybe(0) } ) assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.operatorCode == "/" }, { not((it.rhs.evaluate(MultiValueEvaluator()) as NumberSet).maybe(0)) } ) @@ -691,7 +699,7 @@ class QueryTest { fun testIntOverflowAssignment() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/vulnerable.cpp")) + .sourceLocations(File("src/test/resources/query/vulnerable.cpp")) .defaultPasses() .defaultLanguages() .build() @@ -700,7 +708,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.forall( { it.target?.type?.isPrimitive == true }, { max(it.value) <= maxSizeOfType(it.target!!.type) && @@ -710,7 +718,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.allExtended( + result.forallExtended( { it.target?.type?.isPrimitive == true }, { (max(it.value) le maxSizeOfType(it.target!!.type)) and @@ -726,7 +734,7 @@ class QueryTest { fun testDataFlowRequirement() { val config = TranslationConfiguration.builder() - .sourceLocations(File("src/test/resources/Dataflow.java")) + .sourceLocations(File("src/test/resources/query/Dataflow.java")) .defaultPasses() .defaultLanguages() .build() @@ -735,11 +743,11 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.all( + result.forall( { it.name == "toString" }, { n1 -> result - .all( + .forall( { it.name == "print" }, { n2 -> dataFlow(n1, n2.parameters[0]).value } ) @@ -751,10 +759,10 @@ class QueryTest { assertEquals(0, queryTreeResult.second.size) val queryTreeResultExtended = - result.allExtended( + result.forallExtended( { it.name == "toString" }, { n1 -> - result.allExtended( + result.forallExtended( { it.name == "print" }, { n2 -> dataFlow(n1, n2.parameters[0]) } ) @@ -765,11 +773,11 @@ class QueryTest { assertEquals(1, queryTreeResultExtended.children.size) val queryTreeResult2 = - result.all( + result.forall( { it.name == "test" }, { n1 -> result - .all( + .forall( { it.name == "print" }, { n2 -> dataFlow(n1 as Node, n2.parameters[0]).value } ) @@ -781,10 +789,10 @@ class QueryTest { assertEquals(0, queryTreeResult2.second.size) val queryTreeResult2Extended = - result.allExtended( + result.forallExtended( { it.name == "test" }, { n1 -> - result.allExtended( + result.forallExtended( { it.name == "print" }, { n2 -> dataFlow(n1 as Node, n2.parameters[0]) } ) diff --git a/cpg-console/src/test/resources/Dataflow.java b/cpg-analysis/src/test/resources/query/Dataflow.java similarity index 100% rename from cpg-console/src/test/resources/Dataflow.java rename to cpg-analysis/src/test/resources/query/Dataflow.java diff --git a/cpg-console/src/test/resources/array.cpp b/cpg-analysis/src/test/resources/query/array.cpp similarity index 100% rename from cpg-console/src/test/resources/array.cpp rename to cpg-analysis/src/test/resources/query/array.cpp diff --git a/cpg-console/src/test/resources/array2.cpp b/cpg-analysis/src/test/resources/query/array2.cpp similarity index 100% rename from cpg-console/src/test/resources/array2.cpp rename to cpg-analysis/src/test/resources/query/array2.cpp diff --git a/cpg-console/src/test/resources/array3.cpp b/cpg-analysis/src/test/resources/query/array3.cpp similarity index 100% rename from cpg-console/src/test/resources/array3.cpp rename to cpg-analysis/src/test/resources/query/array3.cpp diff --git a/cpg-console/src/test/resources/array_correct.cpp b/cpg-analysis/src/test/resources/query/array_correct.cpp similarity index 100% rename from cpg-console/src/test/resources/array_correct.cpp rename to cpg-analysis/src/test/resources/query/array_correct.cpp diff --git a/cpg-console/src/test/resources/assign.cpp b/cpg-analysis/src/test/resources/query/assign.cpp similarity index 100% rename from cpg-console/src/test/resources/assign.cpp rename to cpg-analysis/src/test/resources/query/assign.cpp diff --git a/cpg-console/src/test/resources/vulnerable.cpp b/cpg-analysis/src/test/resources/query/vulnerable.cpp similarity index 100% rename from cpg-console/src/test/resources/vulnerable.cpp rename to cpg-analysis/src/test/resources/query/vulnerable.cpp diff --git a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt b/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt deleted file mode 100644 index 802b0d2865..0000000000 --- a/cpg-console/src/main/kotlin/de/fraunhofer/aisec/cpg/query/QueryShortcuts.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2022, 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.query - -import de.fraunhofer.aisec.cpg.ExperimentalGraph -import de.fraunhofer.aisec.cpg.TranslationResult -import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.graph -import de.fraunhofer.aisec.cpg.graph.statements.IfStatement -import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement -import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker -import de.fraunhofer.aisec.cpg.passes.astParent - -/** Returns all [CallExpression]s in this graph. */ -@OptIn(ExperimentalGraph::class) -val TranslationResult.calls: List - get() = this.graph.nodes.filterIsInstance() - -/** Returns all [CallExpression]s in this graph which call a method with the given [name]. */ -@OptIn(ExperimentalGraph::class) -fun TranslationResult.callsByName(name: String): List { - return this.graph.nodes.filter { node -> - (node as? CallExpression)?.invokes?.any { it.name == name } == true - } as List -} - -/** Set of all functions which are called from this function */ -val FunctionDeclaration.callees: Set - get() { - - return SubgraphWalker.flattenAST(this.body) - .filterIsInstance() - .map { it.invokes } - .foldRight( - mutableListOf(), - { l, res -> - res.addAll(l) - res - } - ) - .toSet() - } - -/** Set of all functions calling [function] */ -@OptIn(ExperimentalGraph::class) -fun TranslationResult.callersOf(function: FunctionDeclaration): Set { - return this.graph.nodes - .filterIsInstance() - .filter { function in it.callees } - .toSet() -} - -/** All nodes which depend on this if statement */ -fun IfStatement.controls(): List { - val result = mutableListOf() - result.addAll(SubgraphWalker.flattenAST(this.thenStatement)) - result.addAll(SubgraphWalker.flattenAST(this.elseStatement)) - return result -} - -/** All nodes which depend on this if statement */ -fun Node.controlledBy(): List { - val result = mutableListOf() - var checkedNode: Node = this - while (checkedNode !is FunctionDeclaration) { - checkedNode = checkedNode.astParent!! - if (checkedNode is IfStatement || checkedNode is SwitchStatement) { - result.add(checkedNode) - } - } - return result -} - -/** - * Filters a list of [CallExpression]s for expressions which call a method with the given [name]. - */ -fun List.filterByName(name: String): List { - return this.filter { n -> n.invokes.any { it.name == name } } -} - -/** - * Returns the expression specifying the dimension (i.e., size) of the array during its - * initialization. - */ -val ArraySubscriptionExpression.size: Expression - get() = - (((this.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) - .initializer as ArrayCreationExpression) - .dimensions[0] diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 80d8d9ddef..094421bde5 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -25,31 +25,37 @@ */ package de.fraunhofer.aisec.cpg.graph +import de.fraunhofer.aisec.cpg.ExperimentalGraph import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.Statement +import de.fraunhofer.aisec.cpg.graph.statements.SwitchStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.astParent @JvmName("allNodes") -fun TranslationResult.all(): List { - return this.all() +fun TranslationResult.allChildren(): List { + return this.allChildren() } -inline fun TranslationResult.all(): List { +inline fun TranslationResult.allChildren(): List { val children = SubgraphWalker.flattenAST(this) return children.filterIsInstance() } -@JvmName("allNodes") -fun Node.all(): List { - return this.all() +@JvmName("allChildrenNodes") +fun Node.allChildren(): List { + return this.allChildren() } -inline fun Node.all(): List { +inline fun Node.allChildren(): List { val children = SubgraphWalker.flattenAST(this) return children.filterIsInstance() @@ -332,3 +338,80 @@ fun Node.followPrevDFG(predicate: (Node) -> Boolean): MutableList? { return null } + +/** Returns all [CallExpression]s in this graph. */ +@OptIn(ExperimentalGraph::class) +val TranslationResult.calls: List + get() = this.graph.nodes.filterIsInstance() + +/** Returns all [CallExpression]s in this graph which call a method with the given [name]. */ +@OptIn(ExperimentalGraph::class) +fun TranslationResult.callsByName(name: String): List { + return this.graph.nodes.filter { node -> + (node as? CallExpression)?.invokes?.any { it.name == name } == true + } as List +} + +/** Set of all functions which are called from this function */ +val FunctionDeclaration.callees: Set + get() { + + return SubgraphWalker.flattenAST(this.body) + .filterIsInstance() + .map { it.invokes } + .foldRight( + mutableListOf(), + { l, res -> + res.addAll(l) + res + } + ) + .toSet() + } + +/** Set of all functions calling [function] */ +@OptIn(ExperimentalGraph::class) +fun TranslationResult.callersOf(function: FunctionDeclaration): Set { + return this.graph.nodes + .filterIsInstance() + .filter { function in it.callees } + .toSet() +} + +/** All nodes which depend on this if statement */ +fun IfStatement.controls(): List { + val result = mutableListOf() + result.addAll(SubgraphWalker.flattenAST(this.thenStatement)) + result.addAll(SubgraphWalker.flattenAST(this.elseStatement)) + return result +} + +/** All nodes which depend on this if statement */ +fun Node.controlledBy(): List { + val result = mutableListOf() + var checkedNode: Node = this + while (checkedNode !is FunctionDeclaration) { + checkedNode = checkedNode.astParent!! + if (checkedNode is IfStatement || checkedNode is SwitchStatement) { + result.add(checkedNode) + } + } + return result +} + +/** + * Filters a list of [CallExpression]s for expressions which call a method with the given [name]. + */ +fun List.filterByName(name: String): List { + return this.filter { n -> n.invokes.any { it.name == name } } +} + +/** + * Returns the expression specifying the dimension (i.e., size) of the array during its + * initialization. + */ +val ArraySubscriptionExpression.arraySize: Expression + get() = + (((this.arrayExpression as DeclaredReferenceExpression).refersTo as VariableDeclaration) + .initializer as ArrayCreationExpression) + .dimensions[0] diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt similarity index 100% rename from cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt rename to cpg-core/src/main/java/de/fraunhofer/aisec/cpg/passes/EdgeCachePass.kt diff --git a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ShortcutsTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt similarity index 98% rename from cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ShortcutsTest.kt rename to cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt index 2aca2f6f8c..c2ecd5a783 100644 --- a/cpg-console/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/ShortcutsTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt @@ -23,13 +23,10 @@ * \______/ \__| \______/ * */ -package de.fraunhofer.aisec.cpg.analysis +package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationManager -import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.bodyOrNull -import de.fraunhofer.aisec.cpg.graph.byNameOrNull import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration @@ -44,7 +41,6 @@ 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.passes.EdgeCachePass -import de.fraunhofer.aisec.cpg.query.* import java.io.File import kotlin.test.Test import kotlin.test.assertEquals diff --git a/cpg-console/src/test/resources/ShortcutClass.java b/cpg-core/src/test/resources/ShortcutClass.java similarity index 100% rename from cpg-console/src/test/resources/ShortcutClass.java rename to cpg-core/src/test/resources/ShortcutClass.java From 2acf9c6992941cf5cf3ca3a805ba82ba1611ac4c Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 16 Aug 2022 13:58:35 +0200 Subject: [PATCH 62/67] Some fixes, more test --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 16 +++-- .../fraunhofer/aisec/cpg/query/QueryTest.kt | 2 + .../fraunhofer/aisec/cpg/graph/Extensions.kt | 70 +++++++++++-------- .../aisec/cpg/graph/ShortcutsTest.kt | 28 ++++++++ cpg-core/src/test/resources/Dataflow.java | 20 ++++++ 5 files changed, 102 insertions(+), 34 deletions(-) create mode 100644 cpg-core/src/test/resources/Dataflow.java diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 54e7077702..5051dd0338 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -238,15 +238,19 @@ fun max(n: Node?, eval: ValueEvaluator = MultiValueEvaluator()): QueryTree { val evalRes = from.followNextDFGEdgesUntilHit { it == to } - return QueryTree(evalRes.isNotEmpty(), evalRes.map { QueryTree(it) }.toMutableList()) + val allPaths = evalRes.fulfilled.map { QueryTree(it) }.toMutableList() + allPaths.addAll(evalRes.failed.map { QueryTree(it) }) + return QueryTree(evalRes.fulfilled.isNotEmpty(), allPaths.toMutableList()) } /** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ fun executionPath(from: Node, to: Node): QueryTree { val evalRes = from.followNextEOGEdgesUntilHit { it == to } + val allPaths = evalRes.fulfilled.map { QueryTree(it) }.toMutableList() + allPaths.addAll(evalRes.failed.map { QueryTree(it) }) return QueryTree( - evalRes.isNotEmpty(), - evalRes.map { QueryTree(it) }.toMutableList(), + evalRes.fulfilled.isNotEmpty(), + allPaths.toMutableList(), "executionPath($from, $to)" ) } @@ -257,9 +261,11 @@ fun executionPath(from: Node, to: Node): QueryTree { */ fun executionPath(from: Node, predicate: (Node) -> Boolean): QueryTree { val evalRes = from.followNextEOGEdgesUntilHit(predicate) + val allPaths = evalRes.fulfilled.map { QueryTree(it) }.toMutableList() + allPaths.addAll(evalRes.failed.map { QueryTree(it) }) return QueryTree( - evalRes.isNotEmpty(), - evalRes.map { QueryTree(it) }.toMutableList(), + evalRes.fulfilled.isNotEmpty(), + allPaths.toMutableList(), "executionPath($from, $predicate)" ) } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt index 8cfe59355f..9ba9bb1942 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt @@ -602,6 +602,7 @@ class QueryTest { .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } + .fulfilled .map { it2 -> (it2.last() as ArrayCreationExpression).dimensions[0] } @@ -620,6 +621,7 @@ class QueryTest { .followPrevDFGEdgesUntilHit { node -> node is ArrayCreationExpression } + .fulfilled .map { it2 -> (it2.last() as ArrayCreationExpression).dimensions[0] } diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 094421bde5..b142c18e8d 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -142,16 +142,22 @@ class StatementNotFound : Exception() class DeclarationNotFound(message: String) : Exception(message) +class FulfilledAndFailedPaths(val fulfilled: List>, val failed: List>) { + operator fun component1(): List> = fulfilled + operator fun component2(): List> = failed +} + /** - * Returns a list of nodes which are data flow paths between the starting node [this] and the end - * node fulfilling [predicate]. Paths which do not end at such a node are not included in the - * result. Hence, if the return value is a non-empty list, a data flow from the end node to [this] - * is **possible but not mandatory**. This method traverses the path backwards! + * Returns an instance of [FulfilledAndFailedPaths] where [FulfilledAndFailedPaths.fulfilled] + * contains all possible shortest data flow paths between the end node [this] and the starting node + * fulfilling [predicate]. The paths are represented as lists of nodes. Paths which do not end at + * such a node are included in [FulfilledAndFailedPaths.failed]. * - * It returns all possible paths. + * Hence, if "fulfilled" is a non-empty list, a data flow from [this] to such a node is **possible + * but not mandatory**. If the list "failed" is empty, the data flow is mandatory. */ -fun Node.followPrevDFGEdgesUntilHit(predicate: (Node) -> Boolean): List> { - val result = mutableListOf>() +fun Node.followPrevDFGEdgesUntilHit(predicate: (Node) -> Boolean): FulfilledAndFailedPaths { + val fulfilledPaths = mutableListOf>() val failedPaths = mutableListOf>() val worklist = mutableListOf>() worklist.add(listOf(this)) @@ -171,7 +177,8 @@ fun Node.followPrevDFGEdgesUntilHit(predicate: (Node) -> Boolean): List Boolean): List Boolean): List> { +fun Node.followNextDFGEdgesUntilHit(predicate: (Node) -> Boolean): FulfilledAndFailedPaths { // Looks complicated but at least it's not recursive... // result: List of paths (between from and to) - val result = mutableListOf>() + val fulfilledPaths = mutableListOf>() // failedPaths: All the paths which do not satisfy "predicate" val failedPaths = mutableListOf>() // The list of paths where we're not done yet. @@ -220,7 +228,8 @@ fun Node.followNextDFGEdgesUntilHit(predicate: (Node) -> Boolean): List Boolean): List Boolean): List> { +fun Node.followNextEOGEdgesUntilHit(predicate: (Node) -> Boolean): FulfilledAndFailedPaths { // Looks complicated but at least it's not recursive... // result: List of paths (between from and to) - val result = mutableListOf>() + val fulfilledPaths = mutableListOf>() // failedPaths: All the paths which do not satisfy "predicate" val failedPaths = mutableListOf>() // The list of paths where we're not done yet. @@ -258,7 +269,7 @@ fun Node.followNextEOGEdgesUntilHit(predicate: (Node) -> Boolean): List Boolean): List Boolean): List /** Returns all [CallExpression]s in this graph which call a method with the given [name]. */ @OptIn(ExperimentalGraph::class) fun TranslationResult.callsByName(name: String): List { - return this.graph.nodes.filter { node -> + return SubgraphWalker.flattenAST(this).filter { node -> (node as? CallExpression)?.invokes?.any { it.name == name } == true } as List } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt index c2ecd5a783..1a0fa87a1f 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt @@ -48,6 +48,34 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue class ShortcutsTest { + @Test + fun followDFGUntilHitTest() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/Dataflow.java")) + .defaultPasses() + .defaultLanguages() + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val toStringCall = result.callsByName("toString")[0] + val printDecl = + result.translationUnits[0] + .byNameOrNull("Dataflow") + ?.byNameOrNull("print") + + val (fulfilled, failed) = + toStringCall.followNextDFGEdgesUntilHit { it == printDecl!!.parameters[0] } + + assertEquals(1, fulfilled.size) + assertEquals( + 1, + failed.size + ) // For some reason, the flow to the VariableDeclaration doesn't end in the call to print() + } + @Test fun testCalls() { val config = diff --git a/cpg-core/src/test/resources/Dataflow.java b/cpg-core/src/test/resources/Dataflow.java new file mode 100644 index 0000000000..4f905fac8e --- /dev/null +++ b/cpg-core/src/test/resources/Dataflow.java @@ -0,0 +1,20 @@ +public class Dataflow { + public String toString() { + return "Dataflow: attr=" + attr; + } + + public String test() { return "abcd"; } + + public int print(String s) { + System.out.println(s); + } + + + public static void main(String[] args) { + Dataflow sc = new Dataflow(); + String s = sc.toString(); + sc.print(s); + + sc.print(sc.test()); + } +} \ No newline at end of file From 240bdb015934702e64d1627536d828d4d80c0da2 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 17 Aug 2022 09:29:26 +0200 Subject: [PATCH 63/67] A more tricky test --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 43 ++++++++++++++++++- .../fraunhofer/aisec/cpg/query/QueryTest.kt | 36 +++++++++++++++- .../test/resources/query/ComplexDataflow.java | 17 ++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 cpg-analysis/src/test/resources/query/ComplexDataflow.java diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 5051dd0338..8576280bd0 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -31,7 +31,9 @@ import de.fraunhofer.aisec.cpg.analysis.MultiValueEvaluator import de.fraunhofer.aisec.cpg.analysis.NumberSet import de.fraunhofer.aisec.cpg.analysis.SizeEvaluator import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import de.fraunhofer.aisec.cpg.graph.types.Type /** @@ -240,7 +242,11 @@ fun dataFlow(from: Node, to: Node): QueryTree { val evalRes = from.followNextDFGEdgesUntilHit { it == to } val allPaths = evalRes.fulfilled.map { QueryTree(it) }.toMutableList() allPaths.addAll(evalRes.failed.map { QueryTree(it) }) - return QueryTree(evalRes.fulfilled.isNotEmpty(), allPaths.toMutableList()) + return QueryTree( + evalRes.fulfilled.isNotEmpty(), + allPaths.toMutableList(), + "data flow from $from to $to" + ) } /** Checks if a path of execution flow is possible between the nodes [from] and [to]. */ @@ -348,3 +354,38 @@ val Expression.intValue: QueryTree? val evalRes = evaluate() as? Int ?: return null return QueryTree(evalRes, mutableListOf(), "$this") } + +/** + * Does some magic to identify if the value which is in [from] also reaches [to]. To do so, it goes + * some data flow steps backwards in the graph (ideally to find the last assignment) and then + * follows this value to the node [to]. + */ +fun allNonLiteralsFromFlowTo(from: Node, to: Node): QueryTree { + return when (from) { + is CallExpression -> { + val prevEdges = + from.prevDFG + .fold( + mutableListOf(), + { l, e -> + if (e !is Literal<*>) { + l.add(e) + } + l + } + ) + .toMutableList() + prevEdges.addAll(from.arguments) + // For a call, we collect the incoming data flows (typically only the arguments) + val prevQTs = prevEdges.map { allNonLiteralsFromFlowTo(it, to) } + QueryTree(prevQTs.all { it.value }, prevQTs.toMutableList()) + } + is Literal<*> -> + QueryTree(true, mutableListOf(QueryTree(from)), "DF Irrelevant for Literal node") + else -> { + // We go one step back to see if that one goes into to (e.g., the last assignment) + val prevQTs = from.prevDFG.map { dataFlow(it, to) } + QueryTree(prevQTs.all { it.value }, prevQTs.toMutableList()) + } + } +} diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt index 9ba9bb1942..b2b7ec39e2 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt @@ -35,7 +35,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.passes.EdgeCachePass -import de.fraunhofer.aisec.cpg.query.* import java.io.File import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -804,4 +803,39 @@ class QueryTest { assertTrue(queryTreeResult2Extended.value) assertEquals(1, queryTreeResult2Extended.children.size) } + + @Test + fun testClomplexDFGAndEOGRequirement() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/query/ComplexDataflow.java")) + .defaultPasses() + .defaultLanguages() + .registerPass(EdgeCachePass()) + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.forallExtended( + { it.name == "highlyCriticalOperation" }, + { n1 -> + val loggingQuery = + executionPath(n1, { (it as? CallExpression)?.fqn == "Logger.log" }) + val allCalls = + loggingQuery.children.map { (it.value as List<*>).last() as CallExpression } + // Problem: n1.arguments[0] is the method call. But we care about sc.a + val dataFlowPaths = + allCalls.map { allNonLiteralsFromFlowTo(n1.arguments[0], it.arguments[1]) } + val dataFlowQuery = + QueryTree(dataFlowPaths.all { it.value }, dataFlowPaths.toMutableList()) + return@forallExtended loggingQuery and dataFlowQuery + } + ) + + println(queryTreeResult.printNicely()) + assertTrue(queryTreeResult.value) + assertEquals(1, queryTreeResult.children.size) + } } diff --git a/cpg-analysis/src/test/resources/query/ComplexDataflow.java b/cpg-analysis/src/test/resources/query/ComplexDataflow.java new file mode 100644 index 0000000000..20805bcfb6 --- /dev/null +++ b/cpg-analysis/src/test/resources/query/ComplexDataflow.java @@ -0,0 +1,17 @@ +public class Dataflow { + static Logger logger = Logger.getLogger("DataflowLogger"); + + public int a; + + public static void highlyCriticalOperation(String s) { + System.out.println(s); + } + + + public static void main(String[] args) { + Dataflow sc = new Dataflow(); + sc.a = 5; + Dataflow.highlyCriticalOperation(Integer.toString(sc.a)); + logger.log(Level.INFO, "put " + sc.a + " into highlyCriticalOperation()"); + } +} \ No newline at end of file From b9024e7a960ab480a85077e45799fc4bbd706ed7 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 17 Aug 2022 09:31:07 +0200 Subject: [PATCH 64/67] Update readme --- .../src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md index 9c25867dd7..b601ee4377 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md @@ -92,13 +92,13 @@ val memcpyTooLargeQuery = { node: CallExpression -> } ``` -After assembling a query of the respective operators and functions, we want to run it for a subset of nodes in the graph. We therefore provide two operators: `all` (or `allExtended` for the detailed output) and `exists` (or `existsExtended` for the detailed output). Both are used in a similar way. +After assembling a query of the respective operators and functions, we want to run it for a subset of nodes in the graph. We therefore provide two operators: `forall` (or `forallExtended` for the detailed output) and `exists` (or `existsExtended` for the detailed output). Both are used in a similar way. They enable the user to optionally specify conditions to determine on which nodes we want to run a query (e.g., only on `CallExpression`s which call a function called "memcpy"). The following snippets use the queries from above to run them on all calls of the function "memcpy" contained in the `TranslationResult` `result`: ```kotlin val queryTreeResult = - result.allExtended( + result.forallExtended( { it.name == "memcpy" }, { sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) } ) @@ -106,7 +106,7 @@ val queryTreeResult = Less detailled: ```kotlin val queryTreeResult = - result.all( + result.forall( { it.name == "memcpy" }, { sizeof(it.arguments[0]) > sizeof(it.arguments[1]) } ) From 8abbab78e7934c218c16038c1c6e5c45bed94861 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 17 Aug 2022 10:39:47 +0200 Subject: [PATCH 65/67] Even more complex, better stability of the test --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 42 +++++- .../fraunhofer/aisec/cpg/query/QueryTest.kt | 124 +++++++++++++++++- .../resources/query/ComplexDataflow2.java | 17 +++ .../resources/query/ComplexDataflow3.java | 18 +++ .../fraunhofer/aisec/cpg/graph/Extensions.kt | 51 +++++++ 5 files changed, 241 insertions(+), 11 deletions(-) create mode 100644 cpg-analysis/src/test/resources/query/ComplexDataflow2.java create mode 100644 cpg-analysis/src/test/resources/query/ComplexDataflow3.java diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index 8576280bd0..da6b03fcf1 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -34,6 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression import de.fraunhofer.aisec.cpg.graph.types.Type /** @@ -262,7 +263,7 @@ fun executionPath(from: Node, to: Node): QueryTree { } /** - * Checks if a path of execution flow is possible between the nodes [from] and fulfilling the + * Checks if a path of execution flow is possible starting at the node [from] and fulfilling the * requirement specified in [predicate]. */ fun executionPath(from: Node, predicate: (Node) -> Boolean): QueryTree { @@ -276,6 +277,21 @@ fun executionPath(from: Node, predicate: (Node) -> Boolean): QueryTree ) } +/** + * Checks if a path of execution flow is possible ending at the node [to] and fulfilling the + * requirement specified in [predicate]. + */ +fun executionPathBackwards(to: Node, predicate: (Node) -> Boolean): QueryTree { + val evalRes = to.followPrevEOGEdgesUntilHit(predicate) + val allPaths = evalRes.fulfilled.map { QueryTree(it) }.toMutableList() + allPaths.addAll(evalRes.failed.map { QueryTree(it) }) + return QueryTree( + evalRes.fulfilled.isNotEmpty(), + allPaths.toMutableList(), + "executionPathBackwards($to, $predicate)" + ) +} + /** Calls [ValueEvaluator.evaluate] for this expression, thus trying to resolve a constant value. */ operator fun Expression?.invoke(): QueryTree { return QueryTree(this?.evaluate(), mutableListOf(QueryTree(this))) @@ -360,7 +376,7 @@ val Expression.intValue: QueryTree? * some data flow steps backwards in the graph (ideally to find the last assignment) and then * follows this value to the node [to]. */ -fun allNonLiteralsFromFlowTo(from: Node, to: Node): QueryTree { +fun allNonLiteralsFromFlowTo(from: Node, to: Node, allPaths: List>): QueryTree { return when (from) { is CallExpression -> { val prevEdges = @@ -377,15 +393,31 @@ fun allNonLiteralsFromFlowTo(from: Node, to: Node): QueryTree { .toMutableList() prevEdges.addAll(from.arguments) // For a call, we collect the incoming data flows (typically only the arguments) - val prevQTs = prevEdges.map { allNonLiteralsFromFlowTo(it, to) } + val prevQTs = prevEdges.map { allNonLiteralsFromFlowTo(it, to, allPaths) } QueryTree(prevQTs.all { it.value }, prevQTs.toMutableList()) } is Literal<*> -> QueryTree(true, mutableListOf(QueryTree(from)), "DF Irrelevant for Literal node") else -> { - // We go one step back to see if that one goes into to (e.g., the last assignment) + // We go one step back to see if that one goes into to but also check that no assignment + // to from happens in the paths between from and to val prevQTs = from.prevDFG.map { dataFlow(it, to) } - QueryTree(prevQTs.all { it.value }, prevQTs.toMutableList()) + var noAssignmentToFrom = + allPaths.none { + it.any { it2 -> + if (it2 is Assignment) { + val prevMemberFrom = (from as? MemberExpression)?.prevDFG + val nextMemberTo = (it2.target as? MemberExpression)?.nextDFG + it2.target == from || + prevMemberFrom != null && + nextMemberTo != null && + prevMemberFrom.any { it3 -> nextMemberTo.contains(it3) } + } else { + false + } + } + } + QueryTree(prevQTs.all { it.value } && noAssignmentToFrom, prevQTs.toMutableList()) } } } diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt index b2b7ec39e2..4ee16e43b5 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt @@ -821,16 +821,79 @@ class QueryTest { result.forallExtended( { it.name == "highlyCriticalOperation" }, { n1 -> - val loggingQuery = + val loggingQueryForward = executionPath(n1, { (it as? CallExpression)?.fqn == "Logger.log" }) - val allCalls = - loggingQuery.children.map { (it.value as List<*>).last() as CallExpression } - // Problem: n1.arguments[0] is the method call. But we care about sc.a + val loggingQueryBackwards = + executionPathBackwards(n1, { (it as? CallExpression)?.fqn == "Logger.log" }) + val allChildren = loggingQueryForward.children + allChildren.addAll(loggingQueryBackwards.children) + val allPaths = + allChildren + .map { (it.value as? List<*>) } + .filter { it != null && it.last() is CallExpression } + val allCalls = allPaths.map { it?.last() as CallExpression } val dataFlowPaths = - allCalls.map { allNonLiteralsFromFlowTo(n1.arguments[0], it.arguments[1]) } + allCalls.map { + allNonLiteralsFromFlowTo( + n1.arguments[0], + it.arguments[1], + allPaths as List> + ) + } + val dataFlowQuery = + QueryTree(dataFlowPaths.all { it.value }, dataFlowPaths.toMutableList()) + + return@forallExtended (loggingQueryForward or loggingQueryBackwards) and + dataFlowQuery + } + ) + + println(queryTreeResult.printNicely()) + assertTrue(queryTreeResult.value) + assertEquals(1, queryTreeResult.children.size) + } + + @Test + fun testClomplexDFGAndEOGRequirement2() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/query/ComplexDataflow2.java")) + .defaultPasses() + .defaultLanguages() + .registerPass(EdgeCachePass()) + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.forallExtended( + { it.name == "highlyCriticalOperation" }, + { n1 -> + val loggingQueryForward = + executionPath(n1, { (it as? CallExpression)?.fqn == "Logger.log" }) + val loggingQueryBackwards = + executionPathBackwards(n1, { (it as? CallExpression)?.fqn == "Logger.log" }) + val allChildren = loggingQueryForward.children + allChildren.addAll(loggingQueryBackwards.children) + val allPaths = + allChildren + .map { (it.value as? List<*>) } + .filter { it != null && it.last() is CallExpression } + val allCalls = allPaths.map { it?.last() as CallExpression } + val dataFlowPaths = + allCalls.map { + allNonLiteralsFromFlowTo( + n1.arguments[0], + it.arguments[1], + allPaths as List> + ) + } val dataFlowQuery = QueryTree(dataFlowPaths.all { it.value }, dataFlowPaths.toMutableList()) - return@forallExtended loggingQuery and dataFlowQuery + + return@forallExtended (loggingQueryForward or loggingQueryBackwards) and + dataFlowQuery } ) @@ -838,4 +901,53 @@ class QueryTest { assertTrue(queryTreeResult.value) assertEquals(1, queryTreeResult.children.size) } + + @Test + fun testClomplexDFGAndEOGRequirement3() { + val config = + TranslationConfiguration.builder() + .sourceLocations(File("src/test/resources/query/ComplexDataflow3.java")) + .defaultPasses() + .defaultLanguages() + .registerPass(EdgeCachePass()) + .build() + + val analyzer = TranslationManager.builder().config(config).build() + val result = analyzer.analyze().get() + + val queryTreeResult = + result.forallExtended( + { it.name == "highlyCriticalOperation" }, + { n1 -> + val loggingQueryForward = + executionPath(n1, { (it as? CallExpression)?.fqn == "Logger.log" }) + val loggingQueryBackwards = + executionPathBackwards(n1, { (it as? CallExpression)?.fqn == "Logger.log" }) + val allChildren = loggingQueryForward.children + allChildren.addAll(loggingQueryBackwards.children) + val allPaths = + allChildren + .map { (it.value as? List<*>) } + .filter { it != null && it.last() is CallExpression } + val allCalls = allPaths.map { it?.last() as CallExpression } + val dataFlowPaths = + allCalls.map { + allNonLiteralsFromFlowTo( + n1.arguments[0], + it.arguments[1], + allPaths as List> + ) + } + val dataFlowQuery = + QueryTree(dataFlowPaths.all { it.value }, dataFlowPaths.toMutableList()) + + return@forallExtended (loggingQueryForward or loggingQueryBackwards) and + dataFlowQuery + } + ) + + println(queryTreeResult.printNicely()) + assertFalse(queryTreeResult.value) + assertEquals(1, queryTreeResult.children.size) + } } diff --git a/cpg-analysis/src/test/resources/query/ComplexDataflow2.java b/cpg-analysis/src/test/resources/query/ComplexDataflow2.java new file mode 100644 index 0000000000..9ea961f7f9 --- /dev/null +++ b/cpg-analysis/src/test/resources/query/ComplexDataflow2.java @@ -0,0 +1,17 @@ +public class Dataflow { + static Logger logger = Logger.getLogger("DataflowLogger"); + + public int a; + + public static void highlyCriticalOperation(String s) { + System.out.println(s); + } + + + public static void main(String[] args) { + Dataflow sc = new Dataflow(); + sc.a = 5; + logger.log(Level.INFO, "put " + sc.a + " into highlyCriticalOperation()"); + Dataflow.highlyCriticalOperation(Integer.toString(sc.a)); + } +} \ No newline at end of file diff --git a/cpg-analysis/src/test/resources/query/ComplexDataflow3.java b/cpg-analysis/src/test/resources/query/ComplexDataflow3.java new file mode 100644 index 0000000000..6600d5d654 --- /dev/null +++ b/cpg-analysis/src/test/resources/query/ComplexDataflow3.java @@ -0,0 +1,18 @@ +public class Dataflow { + static Logger logger = Logger.getLogger("DataflowLogger"); + + public int a; + + public static void highlyCriticalOperation(String s) { + System.out.println(s); + } + + + public static void main(String[] args) { + Dataflow sc = new Dataflow(); + sc.a = 5; + logger.log(Level.INFO, "put " + sc.a + " into highlyCriticalOperation()"); + sc.a = 3; + Dataflow.highlyCriticalOperation(Integer.toString(sc.a)); + } +} \ No newline at end of file diff --git a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt index b142c18e8d..21c7ac45bc 100644 --- a/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/java/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -293,6 +293,57 @@ fun Node.followNextEOGEdgesUntilHit(predicate: (Node) -> Boolean): FulfilledAndF return FulfilledAndFailedPaths(fulfilledPaths, failedPaths) } +/** + * Returns an instance of [FulfilledAndFailedPaths] where [FulfilledAndFailedPaths.fulfilled] + * contains all possible shortest evaluation paths between the end node [this] and the start node + * fulfilling [predicate]. The paths are represented as lists of nodes. Paths which do not end at + * such a node are included in [FulfilledAndFailedPaths.failed]. + * + * Hence, if "fulfilled" is a non-empty list, the execution of a statement fulfilling the predicate + * is possible after executing [this] **possible but not mandatory**. If the list "failed" is empty, + * such a statement is always executed. + */ +fun Node.followPrevEOGEdgesUntilHit(predicate: (Node) -> Boolean): FulfilledAndFailedPaths { + // Looks complicated but at least it's not recursive... + // result: List of paths (between from and to) + val fulfilledPaths = mutableListOf>() + // failedPaths: All the paths which do not satisfy "predicate" + val failedPaths = mutableListOf>() + // The list of paths where we're not done yet. + val worklist = mutableListOf>() + worklist.add(listOf(this)) // We start only with the "from" node (=this) + + while (worklist.isNotEmpty()) { + val currentPath = worklist.removeFirst() + // The last node of the path is where we continue. We get all of its outgoing DFG edges and + // follow them + if (currentPath.last().prevEOG.isEmpty()) { + // No further nodes in the path and the path criteria are not satisfied. + failedPaths.add(currentPath) + continue // Don't add this path any more. The requirement is satisfied. + } + + for (next in currentPath.last().prevEOG) { + // Copy the path for each outgoing DFG edge and add the next node + val nextPath = mutableListOf() + nextPath.addAll(currentPath) + nextPath.add(next) + if (predicate(next)) { + // We ended up in the node "to", so we're done. Add the path to the results. + fulfilledPaths.add(nextPath) + continue // Don't add this path anymore. The requirement is satisfied. + } + // The next node is new in the current path (i.e., there's no loop), so we add the path + // with the next step to the worklist. + if (!currentPath.contains(next)) { + worklist.add(nextPath) + } + } + } + + return FulfilledAndFailedPaths(fulfilledPaths, failedPaths) +} + /** * Returns a list of edges which are form the evaluation order between the starting node [this] and * an edge fulfilling [predicate]. If the return value is not `null`, a path from [this] to such an From 9fa779091020646711fdeb13b4bbfb1d36208741 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 18 Aug 2022 08:37:17 +0200 Subject: [PATCH 66/67] Review --- .../src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md index b601ee4377..9c25867dd7 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/README.md @@ -92,13 +92,13 @@ val memcpyTooLargeQuery = { node: CallExpression -> } ``` -After assembling a query of the respective operators and functions, we want to run it for a subset of nodes in the graph. We therefore provide two operators: `forall` (or `forallExtended` for the detailed output) and `exists` (or `existsExtended` for the detailed output). Both are used in a similar way. +After assembling a query of the respective operators and functions, we want to run it for a subset of nodes in the graph. We therefore provide two operators: `all` (or `allExtended` for the detailed output) and `exists` (or `existsExtended` for the detailed output). Both are used in a similar way. They enable the user to optionally specify conditions to determine on which nodes we want to run a query (e.g., only on `CallExpression`s which call a function called "memcpy"). The following snippets use the queries from above to run them on all calls of the function "memcpy" contained in the `TranslationResult` `result`: ```kotlin val queryTreeResult = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) } ) @@ -106,7 +106,7 @@ val queryTreeResult = Less detailled: ```kotlin val queryTreeResult = - result.forall( + result.all( { it.name == "memcpy" }, { sizeof(it.arguments[0]) > sizeof(it.arguments[1]) } ) From 57a91e22d8b113f62aef36762eb018d021c799b6 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 18 Aug 2022 14:45:58 +0200 Subject: [PATCH 67/67] forall -> for --- .../de/fraunhofer/aisec/cpg/query/Query.kt | 4 +- .../fraunhofer/aisec/cpg/query/QueryTest.kt | 122 +++++++++--------- 2 files changed, 62 insertions(+), 64 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt index da6b03fcf1..8a5e77c47f 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/query/Query.kt @@ -47,7 +47,7 @@ import de.fraunhofer.aisec.cpg.graph.types.Type * This method can be used similar to the logical implication to test "sel => mustSatisfy". */ @ExperimentalGraph -inline fun Node.forallExtended( +inline fun Node.allExtended( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> QueryTree ): QueryTree { @@ -79,7 +79,7 @@ inline fun Node.forallExtended( * This method can be used similar to the logical implication to test "sel => mustSatisfy". */ @ExperimentalGraph -inline fun Node.forall( +inline fun Node.all( noinline sel: ((T) -> Boolean)? = null, noinline mustSatisfy: (T) -> Boolean ): Pair> { diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt index 4ee16e43b5..0f9dde0b1a 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/query/QueryTest.kt @@ -56,7 +56,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall( + result.all( { it.name == "memcpy" }, { sizeof(it.arguments[0]) > sizeof(it.arguments[1]) } ) @@ -64,7 +64,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2: QueryTree = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { sizeof(it.arguments[0]) gt sizeof(it.arguments[1]) } ) @@ -88,13 +88,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "memcpy" }) { + result.all({ it.name == "memcpy" }) { it.arguments[0].size > it.arguments[1].size } assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[0].size gt it.arguments[1].size } ) @@ -115,7 +115,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forallExtended( + result.allExtended( mustSatisfy = { (const("memcpy") eq it.name) implies (lazy { it.arguments[0].size gt it.arguments[1].size }) @@ -138,7 +138,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "free" }) { outer -> + result.all({ it.name == "free" }) { outer -> !executionPath(outer) { (it as? DeclaredReferenceExpression)?.refersTo == (outer.arguments[0] as? DeclaredReferenceExpression)?.refersTo @@ -149,7 +149,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "free" }, { outer -> not( @@ -177,7 +177,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "free" }) { outer -> + result.all({ it.name == "free" }) { outer -> !executionPath(outer) { (it as? CallExpression)?.name == "free" && ((it as? CallExpression)?.arguments?.getOrNull(0) @@ -191,7 +191,7 @@ class QueryTest { println(queryTreeResult.second) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "free" }, { outer -> not( @@ -223,13 +223,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "memcpy" }) { + result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!! >= const(11) } assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! ge 11 } ) @@ -237,7 +237,7 @@ class QueryTest { assertTrue(queryTreeResult2.value) val queryTreeResult3 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! ge const(11) } ) @@ -258,13 +258,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "memcpy" }) { + result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!! > const(11) } assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! gt 11 } ) @@ -272,7 +272,7 @@ class QueryTest { assertFalse(queryTreeResult2.value) val queryTreeResult3 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! gt const(11) } ) @@ -293,13 +293,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "memcpy" }) { + result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!! <= const(11) } assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! le 11 } ) @@ -307,7 +307,7 @@ class QueryTest { assertTrue(queryTreeResult2.value) val queryTreeResult3 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! le const(11) } ) @@ -328,13 +328,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "memcpy" }) { + result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!! == const(11) } assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! eq 11 } ) @@ -342,7 +342,7 @@ class QueryTest { assertTrue(queryTreeResult2.value) val queryTreeResult3 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! eq const(11) } ) @@ -363,13 +363,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "memcpy" }) { + result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!! < const(11) } assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! lt 11 } ) @@ -377,7 +377,7 @@ class QueryTest { assertFalse(queryTreeResult2.value) val queryTreeResult3 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! lt const(11) } ) @@ -398,13 +398,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "memcpy" }) { + result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!! != const(11) } assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! ne 11 } ) @@ -412,7 +412,7 @@ class QueryTest { assertFalse(queryTreeResult2.value) val queryTreeResult3 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! ne const(11) } ) @@ -433,13 +433,13 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall({ it.name == "memcpy" }) { + result.all({ it.name == "memcpy" }) { it.arguments[2].intValue!!.value in listOf(11, 2, 3) } assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! IN listOf(11, 2, 3) } ) @@ -447,7 +447,7 @@ class QueryTest { assertTrue(queryTreeResult2.value) val queryTreeResult3 = - result.forallExtended( + result.allExtended( { it.name == "memcpy" }, { it.arguments[2].intValue!! IN const(listOf(11, 2, 3)) } ) @@ -468,13 +468,11 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall( - mustSatisfy = { (it.value.invoke() as QueryTree) < 5 } - ) + result.all(mustSatisfy = { (it.value.invoke() as QueryTree) < 5 }) assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( mustSatisfy = { it.value.invoke() as QueryTree lt 5 } ) @@ -494,7 +492,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall( + result.all( mustSatisfy = { max(it.subscriptExpression) < min(it.arraySize) && min(it.subscriptExpression) >= 0 @@ -503,7 +501,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( mustSatisfy = { (max(it.subscriptExpression) lt min(it.arraySize)) and (min(it.subscriptExpression) ge 0) @@ -559,7 +557,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall( + result.all( mustSatisfy = { max(it.subscriptExpression) < min(it.arraySize) && min(it.subscriptExpression) >= 0 @@ -568,7 +566,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( mustSatisfy = { (max(it.subscriptExpression) lt min(it.arraySize)) and (min(it.subscriptExpression) ge 0) @@ -592,7 +590,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall( + result.all( mustSatisfy = { max(it.subscriptExpression) < min( @@ -611,7 +609,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( mustSatisfy = { (max(it.subscriptExpression) lt min( @@ -645,23 +643,23 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall( + result.all( mustSatisfy = { val max_sub = max(it.subscriptExpression) val min_dim = min(it.arraySize) val min_sub = min(it.subscriptExpression) - return@forall max_sub < min_dim && min_sub >= 0 + return@all max_sub < min_dim && min_sub >= 0 } ) assertTrue(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( mustSatisfy = { val max_sub = max(it.subscriptExpression) val min_dim = min(it.arraySize) val min_sub = min(it.subscriptExpression) - return@forallExtended (max_sub lt min_dim) and (min_sub ge 0) + return@allExtended (max_sub lt min_dim) and (min_sub ge 0) } ) assertTrue(queryTreeResult2.value) @@ -681,14 +679,14 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall( + result.all( { it.operatorCode == "/" }, { !(it.rhs.evaluate(MultiValueEvaluator()) as NumberSet).maybe(0) } ) assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.operatorCode == "/" }, { not((it.rhs.evaluate(MultiValueEvaluator()) as NumberSet).maybe(0)) } ) @@ -709,7 +707,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall( + result.all( { it.target?.type?.isPrimitive == true }, { max(it.value) <= maxSizeOfType(it.target!!.type) && @@ -719,7 +717,7 @@ class QueryTest { assertFalse(queryTreeResult.first) val queryTreeResult2 = - result.forallExtended( + result.allExtended( { it.target?.type?.isPrimitive == true }, { (max(it.value) le maxSizeOfType(it.target!!.type)) and @@ -744,11 +742,11 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forall( + result.all( { it.name == "toString" }, { n1 -> result - .forall( + .all( { it.name == "print" }, { n2 -> dataFlow(n1, n2.parameters[0]).value } ) @@ -760,10 +758,10 @@ class QueryTest { assertEquals(0, queryTreeResult.second.size) val queryTreeResultExtended = - result.forallExtended( + result.allExtended( { it.name == "toString" }, { n1 -> - result.forallExtended( + result.allExtended( { it.name == "print" }, { n2 -> dataFlow(n1, n2.parameters[0]) } ) @@ -774,11 +772,11 @@ class QueryTest { assertEquals(1, queryTreeResultExtended.children.size) val queryTreeResult2 = - result.forall( + result.all( { it.name == "test" }, { n1 -> result - .forall( + .all( { it.name == "print" }, { n2 -> dataFlow(n1 as Node, n2.parameters[0]).value } ) @@ -790,10 +788,10 @@ class QueryTest { assertEquals(0, queryTreeResult2.second.size) val queryTreeResult2Extended = - result.forallExtended( + result.allExtended( { it.name == "test" }, { n1 -> - result.forallExtended( + result.allExtended( { it.name == "print" }, { n2 -> dataFlow(n1 as Node, n2.parameters[0]) } ) @@ -818,7 +816,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forallExtended( + result.allExtended( { it.name == "highlyCriticalOperation" }, { n1 -> val loggingQueryForward = @@ -843,7 +841,7 @@ class QueryTest { val dataFlowQuery = QueryTree(dataFlowPaths.all { it.value }, dataFlowPaths.toMutableList()) - return@forallExtended (loggingQueryForward or loggingQueryBackwards) and + return@allExtended (loggingQueryForward or loggingQueryBackwards) and dataFlowQuery } ) @@ -867,7 +865,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forallExtended( + result.allExtended( { it.name == "highlyCriticalOperation" }, { n1 -> val loggingQueryForward = @@ -892,7 +890,7 @@ class QueryTest { val dataFlowQuery = QueryTree(dataFlowPaths.all { it.value }, dataFlowPaths.toMutableList()) - return@forallExtended (loggingQueryForward or loggingQueryBackwards) and + return@allExtended (loggingQueryForward or loggingQueryBackwards) and dataFlowQuery } ) @@ -916,7 +914,7 @@ class QueryTest { val result = analyzer.analyze().get() val queryTreeResult = - result.forallExtended( + result.allExtended( { it.name == "highlyCriticalOperation" }, { n1 -> val loggingQueryForward = @@ -941,7 +939,7 @@ class QueryTest { val dataFlowQuery = QueryTree(dataFlowPaths.all { it.value }, dataFlowPaths.toMutableList()) - return@forallExtended (loggingQueryForward or loggingQueryBackwards) and + return@allExtended (loggingQueryForward or loggingQueryBackwards) and dataFlowQuery } )