diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt index bd7f362a5..7fb1af8fb 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/CokoCpgBackend.kt @@ -81,7 +81,7 @@ class CokoCpgBackend(config: BackendConfiguration) : */ override fun order(baseNodes: Op, block: Order.() -> Unit): OrderEvaluator = OrderEvaluator( - baseNodes = baseNodes.cpgGetNodes(), + baseNodes = baseNodes.cpgGetNodes().keys, order = Order().apply(block) ) diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt index 287ca5986..cc1f4d497 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt @@ -42,9 +42,9 @@ fun DataItem<*>.cpgGetAllNodes(): Nodes = context(CokoBackend) fun DataItem<*>.cpgGetNodes(): Nodes { return when (this@DataItem) { - is ReturnValueItem -> op.cpgGetNodes().flatMap { it.getVariableInNextDFGOrThis() } + is ReturnValueItem -> op.cpgGetNodes().flatMap { it.key.getVariableInNextDFGOrThis() } is Value -> this@DataItem.getNodes() - is ArgumentItem -> op.cpgGetNodes().map { it.arguments[index] } // TODO: Do we count starting at 0 or 1? + is ArgumentItem -> op.cpgGetNodes().map { it.key.arguments[index] } // TODO: Do we count starting at 0 or 1? } } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt index 02265e435..69749fcfd 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt @@ -18,16 +18,22 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Definition +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ParameterGroup +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Signature import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.query.dataFlow -import de.fraunhofer.aisec.cpg.query.executionPath +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.ConstructExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberExpression +import de.fraunhofer.aisec.cpg.query.* // // all functions/properties defined here must use CokoBackend @@ -60,34 +66,50 @@ fun Op.cpgGetAllNodes(): Collection = * [Definition]s. */ context(CokoBackend) -fun Op.cpgGetNodes(): Collection = +fun Op.cpgGetNodes(): Map = when (this@Op) { - is FunctionOp -> - this@Op.definitions - .flatMap { def -> - this@CokoBackend.cpgCallFqn(def.fqn) { - def.signatures.any { sig -> - cpgSignature(*sig.parameters.toTypedArray()) && - sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } + is FunctionOp -> { + val results = mutableListOf() + val fqn = this@Op.definitions.flatMap { def -> + this@CokoBackend.cpgCallFqn(def.fqn) { + def.signatures.any { sig -> + // We consider a result when both the signature and the flow are not invalid + // However, if at least one of them is OPEN we propagate this information to the caller + val signature = cpgSignature(*sig.parameters.toTypedArray()) + val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + if (signature != INVALID && flow != INVALID) { + results.add(signature.and(flow)) + } else { + false } } } - is ConstructorOp -> - this@Op.signatures - .flatMap { sig -> - this@CokoBackend.cpgConstructor(this@Op.classFqn) { - cpgSignature(*sig.parameters.toTypedArray()) && - sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } + } + fqn.zip(results).toMap() + } + is ConstructorOp -> { + val results = mutableListOf() + val fqn = this@Op.signatures.flatMap { sig -> + this@CokoBackend.cpgConstructor(this@Op.classFqn) { + val signature = cpgSignature(*sig.parameters.toTypedArray()) + val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } + if (signature != INVALID && flow != INVALID) { + results.add(signature.and(flow)) + } else { + false } } - is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes() } + } + fqn.zip(results).toMap() + } + is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes().entries }.associate { it.toPair() } is ConditionalOp -> { val resultNodes = resultOp.cpgGetNodes() val conditionNodes = conditionOp.cpgGetNodes() resultNodes.filter { resultNode -> conditionNodes.any { conditionNode -> // TODO: Is it correct to use the EOG relationship here? - val result = executionPath(conditionNode, resultNode) + val result = executionPath(conditionNode.key, resultNode.key) result.value } } @@ -148,7 +170,7 @@ context(CallExpression) * - If this is a Collection, we check if at least one of the elements flows to [that] * - If this is a [Node], we use the DFG of the CPG. */ -infix fun Any.cpgFlowsTo(that: Node): Boolean = +infix fun Any.cpgFlowsTo(that: Node): Result = this.cpgFlowsTo(listOf(that)) // it should only be available in the context of a CallExpression @@ -159,22 +181,45 @@ context(CallExpression) * - If this is a Collection, we check if at least one of the elements flows to [that] * - If this is a [Node], we use the DFG of the CPG. */ -infix fun Any.cpgFlowsTo(that: Collection): Boolean = - if (this is Wildcard) { - true - } else { +infix fun Any.cpgFlowsTo(that: Collection): Result = + Result.convert( when (this) { + is Wildcard -> true is String -> that.any { val regex = Regex(this) regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty()) } - is Iterable<*> -> this.any { it?.cpgFlowsTo(that) ?: false } - is Array<*> -> this.any { it?.cpgFlowsTo(that) ?: false } + // Separate cases for IntRange and LongRange result in a huge performance boost for large ranges + is LongRange, is IntRange -> checkRange(that) + is Iterable<*> -> this.anyResult { it?.cpgFlowsTo(that) } + is Array<*> -> this.anyResult { it?.cpgFlowsTo(that) } is Node -> that.any { dataFlow(this, it).value } - is ParameterGroup -> this.parameters.all { it?.cpgFlowsTo(that) ?: false } + is ParameterGroup -> this.parameters.allResult { it?.cpgFlowsTo(that) } else -> this in that.map { (it as Expression).evaluate() } } + ) + +private fun Any.checkRange(that: Collection): Boolean { + when (this) { + // I would love to combine the following two cases, but any implementation loses the benefit of + // quickly reading the last value of the range, therefore making the whole distinction useless. + is IntRange -> { + return that.all { + val minValue = min(it).value.toInt() + val maxValue = max(it).value.toInt() + minValue > this.first && maxValue < this.last + } + } + is LongRange -> { + return that.all { + val minValue = min(it).value.toInt() + val maxValue = max(it).value.toInt() + minValue > this.first && maxValue < this.last + } + } + else -> throw IllegalArgumentException("Unexpected type") } +} context(CokoBackend) // TODO: better description @@ -195,21 +240,20 @@ context(CokoBackend) * are not important to the analysis */ @Suppress("UnsafeCallOnNullableType") -fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Boolean { +fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = false): Result { // checks if amount of parameters is the same as amount of arguments of this CallExpression - return cpgCheckArgsSize(parameters, hasVarargs) && + if (cpgCheckArgsSize(parameters, hasVarargs)) { // checks if the CallExpression matches with the parameters - parameters.withIndex().all { (i: Int, parameter: Any?) -> + return parameters.withIndex().allResult { (i: Int, parameter: Any?) -> when (parameter) { // if any parameter is null, signature returns false - null -> false + null -> INVALID is ParamWithType -> // if `parameter` is a `ParamWithType` object we want to check the type and // if there is dataflow - cpgCheckType(parameter.type, i) && - parameter.param cpgFlowsTo arguments[i] + if (cpgCheckType(parameter.type, i)) parameter.param cpgFlowsTo arguments[i] else INVALID // checks if the type of the argument is the same - is Type -> cpgCheckType(parameter, i) + is Type -> Result.convert(cpgCheckType(parameter, i)) // check if any of the Nodes of the Op flow to the argument is Op -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] // check if any of the Nodes of the DataItem flow to the argument @@ -218,6 +262,8 @@ fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = f else -> parameter cpgFlowsTo arguments[i] } } + } + return INVALID } /** Checks the [type] against the type of the argument at [index] for the Call Expression */ diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt new file mode 100644 index 000000000..6d9b22108 --- /dev/null +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl + +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result.* + +/** + * A data class that serves as a ternary value for the analysis result. + * + * OPEN is used where we cannot deduce either VALID or INVALID results because of lack of information. + */ +enum class Result { + VALID, + INVALID, + OPEN; + + companion object { + fun convert(from: Any?): Result { + return when (from) { + is Result -> from + is Boolean -> if (from) VALID else INVALID + else -> OPEN + } + } + } +} + +/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ +fun Iterable.allResult(predicate: (T) -> Result?): Result { + var invalidFlag = false + for (element in this) { + if (predicate(element) == OPEN) { + return OPEN + } else if (predicate(element) == INVALID) { + invalidFlag = true + } + } + return if (invalidFlag) INVALID else VALID +} + +/** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ +fun Iterable.anyResult(predicate: (T) -> Result?): Result { + var openFlag = false + for (element in this) { + if (predicate(element) == VALID) { + return VALID + } else if (predicate(element) == OPEN) { + openFlag = true + } + } + return if (openFlag) OPEN else INVALID +} + +/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ +fun Array.allResult(predicate: (T) -> Result?): Result { + return this.asIterable().allResult(predicate) +} + +/** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ +fun Array.anyResult(predicate: (T) -> Result?): Result { + return this.asIterable().anyResult(predicate) +} + +/** precedence order for ternary and: OPEN > INVALID > VALID */ +fun Result.and(other: Result): Result { + return if (this == OPEN || other == OPEN) { + OPEN + } else if (this == INVALID || other == INVALID) { + INVALID + } else { + VALID + } +} diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt index c6595faa0..088e5eb1e 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt @@ -331,7 +331,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem callConditionComponent: CallConditionComponent, premiseNode: Node? = null ): EvaluationResult { - val callNodes = callConditionComponent.op.cpgGetNodes().filterWithDistanceToPremise(premiseNode) + val callNodes = callConditionComponent.op.cpgGetNodes().keys.filterWithDistanceToPremise(premiseNode) return EvaluationResult(callNodes, emptyList(), Problems()) } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt index 898ef1e45..659c49725 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluator.kt @@ -38,10 +38,10 @@ class FollowsEvaluator(val ifOp: Op, val thenOp: Op) : Evaluator { override fun evaluate(context: EvaluationContext): List { val (unreachableThisNodes, thisNodes) = - with(this@CokoCpgBackend) { ifOp.cpgGetNodes().toSet() } + with(this@CokoCpgBackend) { ifOp.cpgGetNodes().keys } .partition { it.isUnreachable() } - val thatNodes = with(this@CokoCpgBackend) { thenOp.cpgGetNodes().toSet() } + val thatNodes = with(this@CokoCpgBackend) { thenOp.cpgGetNodes().keys } val findings = mutableListOf() diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyNeverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyNeverEvaluator.kt index a8278dfa1..ffdb217bf 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyNeverEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyNeverEvaluator.kt @@ -17,6 +17,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetAllNodes import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.findUsages @@ -25,11 +26,16 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Rule +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import kotlin.reflect.full.findAnnotation context(CokoCpgBackend) class OnlyNeverEvaluator(private val ops: List, private val functionality: Functionality) : Evaluator { + var interestingNodes = with(this@CokoCpgBackend) { + ops.flatMap { it.cpgGetNodes().entries } + }.associate { it.toPair() } + /** Default message if a violation is found */ private val defaultFailMessage: String by lazy { // Try to model what the allowed calls look like with `toString` call of `Op` @@ -40,6 +46,12 @@ class OnlyNeverEvaluator(private val ops: List, private val functionality: F private val defaultPassMessage = "Call is in compliance with rule" override fun evaluate(context: EvaluationContext): List { + val (violatingNodes, correctAndOpenNodes) = getNodes() + val (failMessage, passMessage) = getMessages(context) + return createFindings(violatingNodes, correctAndOpenNodes, failMessage, passMessage) + } + + private fun getNodes(): Pair, Set> { val distinctOps = ops.toSet() val allNodes = with(this@CokoCpgBackend) { distinctOps.flatMap { it.cpgGetAllNodes() } } @@ -49,18 +61,29 @@ class OnlyNeverEvaluator(private val ops: List, private val functionality: F // `matchingNodes` is a subset of `allNodes` // we want to find nodes in `allNodes` that are not contained in `matchingNodes` // since they are contrary Findings - val matchingNodes = - with(this@CokoCpgBackend) { ops.flatMap { it.cpgGetNodes() } } - .toSet() + val matchingNodes = interestingNodes.keys.toSet() val differingNodes = allNodes.minus(matchingNodes) + // define what violations and passes are, depending on selected functionality + val correctAndOpenNodes = if (functionality == Functionality.NEVER) differingNodes else matchingNodes + val violatingNodes = if (functionality == Functionality.NEVER) matchingNodes else differingNodes + + return violatingNodes to correctAndOpenNodes + } + + private fun getMessages(context: EvaluationContext): Pair { val ruleAnnotation = context.rule.findAnnotation() val failMessage = ruleAnnotation?.failMessage?.takeIf { it.isNotEmpty() } ?: defaultFailMessage val passMessage = ruleAnnotation?.passMessage?.takeIf { it.isNotEmpty() } ?: defaultPassMessage + return failMessage to passMessage + } - // define what violations and passes are, depending on selected functionality - val correctNodes = if (functionality == Functionality.NEVER) differingNodes else matchingNodes - val violatingNodes = if (functionality == Functionality.NEVER) matchingNodes else differingNodes + fun createFindings( + violatingNodes: Set, + correctAndOpenNodes: Set, + failMessage: String, + passMessage: String + ): List { val findings = mutableListOf() for (node in violatingNodes) { @@ -73,15 +96,27 @@ class OnlyNeverEvaluator(private val ops: List, private val functionality: F ) ) } - for (node in correctNodes) { - findings.add( - CpgFinding( - message = "Complies with rule: \"${node.code}\". $passMessage", - kind = Finding.Kind.Pass, - node = node, - relatedNodes = node.findUsages() + + for (node in correctAndOpenNodes) { + if (interestingNodes[node] == Result.OPEN) { + findings.add( + CpgFinding( + message = "Not enough information to evaluate \"${node.code}\"", + kind = Finding.Kind.Open, + node = node, + relatedNodes = node.findUsages() + ) ) - ) + } else { + findings.add( + CpgFinding( + message = "Complies with rule: \"${node.code}\". $passMessage", + kind = Finding.Kind.Pass, + node = node, + relatedNodes = node.findUsages() + ) + ) + } } return findings } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt index ef4132ce0..8de639bb5 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluator.kt @@ -153,7 +153,7 @@ class OrderEvaluator(val baseNodes: Collection, val order: Order) : Evalua // the nodes from +testObj.start(123) <- userDefined Ops are used // only allow the start nodes that take '123' as argument for ((_, op) in order.userDefinedOps.entries) { - val nodes = op.cpgGetNodes() + val nodes = op.cpgGetNodes().keys registerOpAndNodes(op, nodes, hashToMethod, nodesToOp) } diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/PrecedesEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/PrecedesEvaluator.kt index 3707d9982..15bef31ba 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/PrecedesEvaluator.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/PrecedesEvaluator.kt @@ -38,10 +38,10 @@ class PrecedesEvaluator(val prevOp: Op, val thisOp: Op) : Evaluator { override fun evaluate(context: EvaluationContext): List { val (unreachableThisNodes, thisNodes) = - with(this@CokoCpgBackend) { thisOp.cpgGetNodes().toSet() } + with(this@CokoCpgBackend) { thisOp.cpgGetNodes().keys } .partition { it.isUnreachable() } - val prevNodes = with(this@CokoCpgBackend) { prevOp.cpgGetNodes().toSet() } + val prevNodes = with(this@CokoCpgBackend) { prevOp.cpgGetNodes().keys } val findings = mutableListOf() diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OnlyEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OnlyEvaluationTest.kt deleted file mode 100644 index 5277615c2..000000000 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OnlyEvaluationTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2023, Fraunhofer AISEC. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.fraunhofer.aisec.codyze.backends.cpg - -import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.op -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.signature -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Test -import java.nio.file.Path -import kotlin.io.path.* -import kotlin.reflect.full.valueParameters -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class OnlyEvaluationTest { - - @Suppress("UNUSED") - class FooModel { - fun first(i: Any) = op { - definition("Foo.fun") { - signature(i) - } - } - } - - @Test - fun `test simple only`() { - val fooInstance = FooModel() - - val backend = CokoCpgBackend(config = createCpgConfiguration(testFile)) - - with(backend) { - val evaluator = only(fooInstance.first(0..10)) - val findings = evaluator.evaluate( - EvaluationContext( - rule = ::dummyRule, - parameterMap = ::dummyRule.valueParameters.associateWith { fooInstance } - ) - ) - - assertTrue("There were no findings which is unexpected") { findings.isNotEmpty() } - - val failFindings = findings.filter { it.kind == Finding.Kind.Fail } - assertEquals(2, failFindings.size, "Found ${failFindings.size} violation(s) instead of two violations") - } - } - - companion object { - - lateinit var testFile: Path - - @BeforeAll - @JvmStatic - fun startup() { - val classLoader = OnlyEvaluationTest::class.java.classLoader - - val testFileResource = classLoader.getResource("OnlyEvaluationTest/SimpleOnly.java") - assertNotNull(testFileResource) - testFile = testFileResource.toURI().toPath() - } - } -} diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt index ce1508e7e..de1362529 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/SignatureTest.kt @@ -43,14 +43,14 @@ class SignatureTest { fun `test signature with wrong number of params`() { every { node.arguments } returns listOf() - assertFalse { with(backend) { with(node) { cpgSignature("test") } } } + assertFalse { with(backend) { with(node) { cpgSignature("test") } } == Result.VALID } } @Test fun `test signature with null`() { every { node.arguments } returns listOf(mockk()) - assertFalse { with(backend) { with(node) { cpgSignature(null) } } } + assertFalse { with(backend) { with(node) { cpgSignature(null) } } == Result.VALID } } @Test @@ -58,7 +58,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } } } + assertTrue { with(backend) { with(node) { cpgSignature(Type("kotlin.String")) } } == Result.VALID } } @Test @@ -66,7 +66,7 @@ class SignatureTest { every { node.arguments } returns listOf(stringArgument) every { stringArgument.type.typeName } returns "kotlin.String" - assertFalse { with(backend) { with(node) { cpgSignature(Type("kotlin.Int")) } } } + assertFalse { with(backend) { with(node) { cpgSignature(Type("kotlin.Int")) } } == Result.VALID } } @Test @@ -75,7 +75,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertTrue { with(backend) { with(node) { cpgSignature("test" withType "kotlin.String") } } } + assertTrue { with(backend) { with(node) { cpgSignature("test" withType "kotlin.String") } } == Result.VALID } } @Test @@ -84,7 +84,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertFalse { with(backend) { with(node) { cpgSignature("test" withType "kotlin.Int") } } } + assertFalse { with(backend) { with(node) { cpgSignature("test" withType "kotlin.Int") } } == Result.VALID } } @Test @@ -93,7 +93,7 @@ class SignatureTest { every { stringArgument.type.typeName } returns "kotlin.String" every { stringArgument.value } returns "test" - assertFalse { with(backend) { with(node) { cpgSignature(1 withType "kotlin.String") } } } + assertFalse { with(backend) { with(node) { cpgSignature(1 withType "kotlin.String") } } == Result.VALID } } @Test @@ -104,7 +104,7 @@ class SignatureTest { every { pairArgument.value } returns (1 to "one") mockkStatic("de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.ImplementationDslKt") - every { with(node) { param.cpgFlowsTo(pairArgument) } } returns true + every { with(node) { param.cpgFlowsTo(pairArgument) } } returns Result.VALID // tests that with normal pair only flowsTo is called with(backend) { with(node) { cpgSignature(param) } } @@ -118,7 +118,7 @@ class SignatureTest { every { stringArgument.value } returns "test" mockkStatic("de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.ImplementationDslKt") - every { with(node) { param.cpgFlowsTo(stringArgument) } } returns true + every { with(node) { param.cpgFlowsTo(stringArgument) } } returns Result.VALID // assert that signature checks the dataflow from the parameter to the argument with(backend) { with(node) { cpgSignature(param) } } @@ -134,7 +134,7 @@ class SignatureTest { val a = mockk>() args.add(a) every { a.value } returns "test" - every { with(node) { p.cpgFlowsTo(a) } } returns true + every { with(node) { p.cpgFlowsTo(a) } } returns Result.VALID } every { node.arguments } returns args diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ImplementationDslTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt similarity index 76% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ImplementationDslTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt index 0dece26f6..6577b4c8a 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ImplementationDslTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDslTest.kt @@ -13,14 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetAllNodes -import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Wildcard -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.op -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.signature +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import kotlin.io.path.toPath @@ -36,7 +33,7 @@ class ImplementationDslTest { signature(2) } } - with(backend) { + with(simpleBackend) { val allNodes = op.cpgGetAllNodes() assertEquals( 5, @@ -53,7 +50,7 @@ class ImplementationDslTest { signature(1..10) } } - with(backend) { + with(simpleBackend) { val nodes = op.cpgGetNodes() assertEquals( 2, @@ -73,7 +70,7 @@ class ImplementationDslTest { } } } - with(backend) { + with(simpleBackend) { val nodes = op.cpgGetNodes() assertEquals( 3, @@ -85,17 +82,20 @@ class ImplementationDslTest { companion object { - lateinit var backend: CokoCpgBackend + lateinit var simpleBackend: CokoCpgBackend @BeforeAll @JvmStatic fun startup() { val classLoader = ImplementationDslTest::class.java.classLoader - val testFileResource = classLoader.getResource("ImplementationDslTest/SimpleJavaFile.java") - assertNotNull(testFileResource) - val testFile = testFileResource.toURI().toPath() - backend = CokoCpgBackend(config = createCpgConfiguration(testFile)) + val simpleFileResource = classLoader.getResource("ImplementationDslTest/SimpleJavaFile.java") + + assertNotNull(simpleFileResource) + + val simpleFile = simpleFileResource.toURI().toPath() + + simpleBackend = CokoCpgBackend(config = createCpgConfiguration(simpleFile)) } } } diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt new file mode 100644 index 000000000..1c5ea2679 --- /dev/null +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ResultTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class ResultTest { + @Test + fun testIterableAll() { + val allValid = listOf(Result.VALID, Result.VALID, Result.VALID) + val oneInvalid = listOf(Result.VALID, Result.VALID, Result.INVALID) + val oneOpen = listOf(Result.VALID, Result.VALID, Result.OPEN) + val oneInvalidOneOpen = listOf(Result.VALID, Result.INVALID, Result.OPEN) + + val resultA = allValid.allResult { it } + val resultB = oneInvalid.allResult { it } + val resultC = oneOpen.allResult { it } + val resultD = oneInvalidOneOpen.allResult { it } + + assertEquals(Result.VALID, resultA) + assertEquals(Result.INVALID, resultB) + assertEquals(Result.OPEN, resultC) + assertEquals(Result.OPEN, resultD) + } + + @Test + fun testIterableAny() { + val allValid = listOf(Result.VALID, Result.VALID, Result.VALID) + val oneValid = listOf(Result.VALID, Result.OPEN, Result.INVALID) + val oneOpen = listOf(Result.OPEN, Result.INVALID, Result.INVALID) + val onlyInvalid = listOf(Result.INVALID, Result.INVALID, Result.INVALID) + + val resultA = allValid.anyResult { it } + val resultB = oneValid.anyResult { it } + val resultC = oneOpen.anyResult { it } + val resultD = onlyInvalid.anyResult { it } + + assertEquals(Result.VALID, resultA) + assertEquals(Result.VALID, resultB) + assertEquals(Result.OPEN, resultC) + assertEquals(Result.INVALID, resultD) + } + + @Test + fun testArrayAll() { + val allValid = arrayOf(Result.VALID, Result.VALID, Result.VALID) + val oneInvalid = arrayOf(Result.VALID, Result.VALID, Result.INVALID) + val oneOpen = arrayOf(Result.VALID, Result.VALID, Result.OPEN) + val oneInvalidOneOpen = arrayOf(Result.VALID, Result.INVALID, Result.OPEN) + + val resultA = allValid.allResult { it } + val resultB = oneInvalid.allResult { it } + val resultC = oneOpen.allResult { it } + val resultD = oneInvalidOneOpen.allResult { it } + + assertEquals(Result.VALID, resultA) + assertEquals(Result.INVALID, resultB) + assertEquals(Result.OPEN, resultC) + assertEquals(Result.OPEN, resultD) + } + + @Test + fun testArrayAny() { + val allValid = listOf(Result.VALID, Result.VALID, Result.VALID) + val oneValid = listOf(Result.VALID, Result.OPEN, Result.INVALID) + val oneOpen = listOf(Result.OPEN, Result.INVALID, Result.INVALID) + val onlyInvalid = listOf(Result.INVALID, Result.INVALID, Result.INVALID) + + val resultA = allValid.anyResult { it } + val resultB = oneValid.anyResult { it } + val resultC = oneOpen.anyResult { it } + val resultD = onlyInvalid.anyResult { it } + + assertEquals(Result.VALID, resultA) + assertEquals(Result.VALID, resultB) + assertEquals(Result.OPEN, resultC) + assertEquals(Result.INVALID, resultD) + } + + @Test + fun testResultAnd() { + assertEquals(Result.VALID, Result.VALID.and(Result.VALID)) + assertEquals(Result.INVALID, Result.INVALID.and(Result.VALID)) + assertEquals(Result.INVALID, Result.VALID.and(Result.INVALID)) + assertEquals(Result.OPEN, Result.OPEN.and(Result.VALID)) + assertEquals(Result.OPEN, Result.VALID.and(Result.OPEN)) + assertEquals(Result.OPEN, Result.OPEN.and(Result.INVALID)) + } + + @Test + fun testConvert() { + assertEquals(Result.VALID, Result.convert(Result.VALID)) + assertEquals(Result.INVALID, Result.convert(Result.INVALID)) + assertEquals(Result.OPEN, Result.convert(Result.OPEN)) + assertEquals(Result.VALID, Result.convert(true)) + assertEquals(Result.INVALID, Result.convert(false)) + assertEquals(Result.OPEN, Result.convert("123")) + } +} diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ArgumentEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/ArgumentEvaluationTest.kt similarity index 90% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ArgumentEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/ArgumentEvaluationTest.kt index 93df712c2..d3429637c 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/ArgumentEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/ArgumentEvaluationTest.kt @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition @@ -59,7 +61,7 @@ class ArgumentEvaluationTest { @Test fun `test simple argument pass`() { - val okFindings = ArgumentEvaluationTest.findings.filter { it.kind == Finding.Kind.Pass } + val okFindings = findings.filter { it.kind == Finding.Kind.Pass } for (finding in okFindings) { // pass finding has to be in function that has "ok" in its name assertTrue("Found PASS finding that was from function ${finding.node?.getFunction()} -> false negative") { @@ -70,7 +72,7 @@ class ArgumentEvaluationTest { @Test fun `test simple argument fail`() { - val failFindings = ArgumentEvaluationTest.findings.filter { it.kind == Finding.Kind.Fail } + val failFindings = findings.filter { it.kind == Finding.Kind.Fail } for (finding in failFindings) { // fail finding should not be in function that has "ok" in its name assertFalse("Found FAIL finding that was from function ${finding.node?.getFunction()} -> false positive") { @@ -86,7 +88,7 @@ class ArgumentEvaluationTest { @Test fun `test simple argument not applicable`() { - val notApplicableFindings = ArgumentEvaluationTest.findings.filter { it.kind == Finding.Kind.NotApplicable } + val notApplicableFindings = findings.filter { it.kind == Finding.Kind.NotApplicable } for (finding in notApplicableFindings) { // notApplicable finding has to be in function that has "notApplicable" in its name assertTrue( @@ -122,8 +124,8 @@ class ArgumentEvaluationTest { assertNotNull(testFileResource) testFile = testFileResource.toURI().toPath() - val fooInstance = ArgumentEvaluationTest.FooModel() - val barInstance = ArgumentEvaluationTest.BarModel() + val fooInstance = FooModel() + val barInstance = BarModel() val backend = CokoCpgBackend(config = createCpgConfiguration(testFile)) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/FollowsEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt similarity index 96% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/FollowsEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt index 463ed59b9..8e64480e5 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/FollowsEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/FollowsEvaluationTest.kt @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NeverEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt similarity index 55% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NeverEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt index 38b2ae4b4..758419db1 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NeverEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/NeverEvaluationTest.kt @@ -13,16 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.signature +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +import de.fraunhofer.aisec.cpg.sarif.Region import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test +import java.net.URI import java.nio.file.Path import kotlin.io.path.* import kotlin.reflect.full.valueParameters @@ -92,7 +99,70 @@ class NeverEvaluationTest { findings.all { it.kind == Finding.Kind.Pass } } - assertEquals(4, findings.size, "Found ${findings.size} finding(s) instead of one pass finding") + assertEquals(4, findings.size, "Found ${findings.size} finding(s) instead of four pass findings") + } + } + + @Test + fun `test finding creation`() { + val backend = CokoCpgBackend(config = createCpgConfiguration(violationFile)) + with(backend) { + val evaluator = OnlyNeverEvaluator(listOf(), OnlyNeverEvaluator.Functionality.NEVER) + // Set violating Regions to 0, 1, 2 as Line and Column + val violating = listOf(CallExpression(), CallExpression(), CallExpression()) + violating.forEachIndexed { + index, expression -> + expression.location = PhysicalLocation(URI("uri"), Region(index, index, index, index)) + } + // Set correct and open Regions to 3, 4, 5 as Line and Column + val correctAndOpen = listOf(CallExpression(), CallExpression(), CallExpression()) + correctAndOpen.forEachIndexed { + index, expression -> + run { + val i = index + 3 + expression.location = PhysicalLocation(URI("uri"), Region(i, i, i, i)) + } + } + + // Associate INVALID to violating expressions, VALID to correct result with index 3 and OPEN to the others + evaluator.interestingNodes = violating.associateWith { Result.INVALID } + correctAndOpen.associateWith { + if (it.location!!.region.startLine < 4) { + Result.VALID + } else { + Result.OPEN + } + } + + val findings = evaluator.createFindings(violating.toSet(), correctAndOpen.toSet(), "", "") + val failFindings = findings.filter { it.kind == Finding.Kind.Fail } + val passFindings = findings.filter { it.kind == Finding.Kind.Pass } + val openFindings = findings.filter { it.kind == Finding.Kind.Open } + // Assert the right number of findings + assertEquals(3, failFindings.size) + assertEquals(1, passFindings.size) + assertEquals(2, openFindings.size) + // Assert the correct location of findings + assertEquals( + setOf( + Region(0, 0, 0, 0), + Region(1, 1, 1, 1), + Region(2, 2, 2, 2) + ), + failFindings.map { it.node!!.location!!.region }.toSet() + ) + assertEquals( + setOf( + Region(3, 3, 3, 3) + ), + passFindings.map { it.node!!.location!!.region }.toSet() + ) + assertEquals( + setOf( + Region(4, 4, 4, 4), + Region(5, 5, 5, 5) + ), + openFindings.map { it.node!!.location!!.region }.toSet() + ) } } @@ -104,7 +174,7 @@ class NeverEvaluationTest { @BeforeAll @JvmStatic fun startup() { - val classLoader = OnlyEvaluationTest::class.java.classLoader + val classLoader = NeverEvaluationTest::class.java.classLoader val violationFileResource = classLoader.getResource("NeverEvaluationTest/NeverViolation.java") assertNotNull(violationFileResource) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt new file mode 100644 index 000000000..0aff257ca --- /dev/null +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OnlyEvaluationTest.kt @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators + +import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.Result +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.op +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.signature +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +import de.fraunhofer.aisec.cpg.sarif.Region +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import java.net.URI +import java.nio.file.Path +import kotlin.io.path.* +import kotlin.reflect.full.valueParameters +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class OnlyEvaluationTest { + + @Suppress("UNUSED") + class FooModel { + fun first(i: Any) = op { + definition("Foo.fun") { + signature(i) + } + } + } + + @Test + fun `test simple only`() { + val fooInstance = FooModel() + + val backend = CokoCpgBackend(config = createCpgConfiguration(testFile)) + + with(backend) { + val evaluator = only(fooInstance.first(0..10)) + val findings = evaluator.evaluate( + EvaluationContext( + rule = ::dummyRule, + parameterMap = ::dummyRule.valueParameters.associateWith { fooInstance } + ) + ) + + assertTrue("There were no findings which is unexpected") { findings.isNotEmpty() } + + val failFindings = findings.filter { it.kind == Finding.Kind.Fail } + assertEquals(2, failFindings.size, "Found ${failFindings.size} violation(s) instead of two violations") + } + } + + @Test + fun `test finding creation`() { + val backend = CokoCpgBackend(config = createCpgConfiguration(testFile)) + with(backend) { + val evaluator = OnlyNeverEvaluator(listOf(), OnlyNeverEvaluator.Functionality.ONLY) + // Set violating Regions to 0, 1, 2 as Line and Column + val violating = listOf(CallExpression(), CallExpression(), CallExpression()) + violating.forEachIndexed { + index, expression -> + expression.location = PhysicalLocation(URI("uri"), Region(index, index, index, index)) + } + // Set correct and open Regions to 3, 4, 5 as Line and Column + val correctAndOpen = listOf(CallExpression(), CallExpression(), CallExpression()) + correctAndOpen.forEachIndexed { + index, expression -> + run { + val i = index + 3 + expression.location = PhysicalLocation(URI("uri"), Region(i, i, i, i)) + } + } + + // Associate INVALID to violating expressions, VALID to correct result with index 3 and OPEN to the others + evaluator.interestingNodes = violating.associateWith { Result.INVALID } + correctAndOpen.associateWith { + if (it.location!!.region.startLine < 4) { + Result.VALID + } else { + Result.OPEN + } + } + + val findings = evaluator.createFindings(violating.toSet(), correctAndOpen.toSet(), "", "") + val failFindings = findings.filter { it.kind == Finding.Kind.Fail } + val passFindings = findings.filter { it.kind == Finding.Kind.Pass } + val openFindings = findings.filter { it.kind == Finding.Kind.Open } + // Assert the right number of findings + assertEquals(3, failFindings.size) + assertEquals(1, passFindings.size) + assertEquals(2, openFindings.size) + // Assert the correct location of findings + assertEquals( + setOf( + Region(0, 0, 0, 0), + Region(1, 1, 1, 1), + Region(2, 2, 2, 2) + ), + failFindings.map { it.node!!.location!!.region }.toSet() + ) + assertEquals( + setOf( + Region(3, 3, 3, 3) + ), + passFindings.map { it.node!!.location!!.region }.toSet() + ) + assertEquals( + setOf( + Region(4, 4, 4, 4), + Region(5, 5, 5, 5) + ), + openFindings.map { it.node!!.location!!.region }.toSet() + ) + } + } + + companion object { + + lateinit var testFile: Path + + @BeforeAll + @JvmStatic + fun startup() { + val classLoader = OnlyEvaluationTest::class.java.classLoader + + val testFileResource = classLoader.getResource("OnlyEvaluationTest/SimpleOnly.java") + assertNotNull(testFileResource) + testFile = testFileResource.toURI().toPath() + } + } +} diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OrderEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt similarity index 98% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OrderEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt index 49a2b0390..9c7a9c766 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/OrderEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/OrderEvaluationTest.kt @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/PrecedesEvaluationTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/PrecedesEvaluationTest.kt similarity index 95% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/PrecedesEvaluationTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/PrecedesEvaluationTest.kt index 6c590b79d..aaab1f220 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/PrecedesEvaluationTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/PrecedesEvaluationTest.kt @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding +import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition @@ -114,7 +116,7 @@ class PrecedesEvaluationTest { @BeforeAll @JvmStatic fun startup() { - val classLoader = FollowsEvaluationTest::class.java.classLoader + val classLoader = PrecedesEvaluationTest::class.java.classLoader val testFileResource = classLoader.getResource("PrecedesEvaluationTest/SimplePrecedes.java") assertNotNull(testFileResource) diff --git a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NfaDfaConstructionTest.kt b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt similarity index 99% rename from codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NfaDfaConstructionTest.kt rename to codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt index fabb881a0..b8baf492f 100644 --- a/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/NfaDfaConstructionTest.kt +++ b/codyze-backends/cpg/src/test/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/ordering/NfaDfaConstructionTest.kt @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.fraunhofer.aisec.codyze.backends.cpg +package de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend -import de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering.toNfa import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.* import de.fraunhofer.aisec.cpg.analysis.fsm.DFA @@ -48,7 +47,7 @@ class NfaDfaConstructionTest { } private val baseName = - "de.fraunhofer.aisec.codyze.backends.cpg.NfaDfaConstructionTest\$TestClass" + "de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering.NfaDfaConstructionTest\$TestClass" private fun orderExpressionToNfa(block: Order.() -> Unit): NFA { val order = Order().apply(block)