-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Propagate uncertainty information to Evaluators #886
Changes from 17 commits
31acd51
0369e26
d81132e
bb82ebd
46b86ea
1cb9af5
aee5df3
e2371a4
945768e
3001542
f9f02e9
54f2d74
9c03a5b
893f827
5985bf9
44ef976
64b0f21
bcf9437
5cc9ce4
14aa02c
15818cb
7768611
55c99b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 -> [email protected]() | ||
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? | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 @@ | |
* [Definition]s. | ||
*/ | ||
context(CokoBackend) | ||
fun Op.cpgGetNodes(): Collection<CallExpression> = | ||
fun Op.cpgGetNodes(): Map<CallExpression, Result> = | ||
when (this@Op) { | ||
is FunctionOp -> | ||
[email protected] | ||
.flatMap { def -> | ||
[email protected](def.fqn) { | ||
def.signatures.any { sig -> | ||
cpgSignature(*sig.parameters.toTypedArray()) && | ||
sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } | ||
is FunctionOp -> { | ||
val results = mutableListOf<Result>() | ||
val fqn = [email protected] { def -> | ||
[email protected](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 -> | ||
[email protected] | ||
.flatMap { sig -> | ||
[email protected]([email protected]) { | ||
cpgSignature(*sig.parameters.toTypedArray()) && | ||
sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } | ||
} | ||
fqn.zip(results).toMap() | ||
} | ||
is ConstructorOp -> { | ||
val results = mutableListOf<Result>() | ||
val fqn = [email protected] { sig -> | ||
[email protected]([email protected]) { | ||
val signature = cpgSignature(*sig.parameters.toTypedArray()) | ||
Check warning Code scanning / detekt In most cases using a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty. Warning
Used in this way a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty.
|
||
val flow = sig.unorderedParameters.allResult { it?.cpgFlowsTo(arguments) } | ||
if (signature != INVALID && flow != INVALID) { | ||
results.add(signature.and(flow)) | ||
} else { | ||
false | ||
} | ||
} | ||
is GroupingOp -> [email protected] { it.cpgGetNodes() } | ||
} | ||
fqn.zip(results).toMap() | ||
} | ||
is GroupingOp -> [email protected] { 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 @@ | |
* - 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 @@ | |
* - 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<Node>): Boolean = | ||
if (this is Wildcard) { | ||
true | ||
} else { | ||
infix fun Any.cpgFlowsTo(that: Collection<Node>): 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<Node>): 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() | ||
Check warning on line 216 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt#L214-L216
|
||
minValue > this.first && maxValue < this.last | ||
} | ||
} | ||
else -> throw IllegalArgumentException("Unexpected type") | ||
Check warning on line 220 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt#L220
|
||
} | ||
} | ||
|
||
context(CokoBackend) | ||
// TODO: better description | ||
|
@@ -195,21 +240,20 @@ | |
* 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 @@ | |
else -> parameter cpgFlowsTo arguments[i] | ||
} | ||
} | ||
} | ||
return INVALID | ||
} | ||
|
||
/** Checks the [type] against the type of the argument at [index] for the Call Expression */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* 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 | ||
Check warning on line 35 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L35
|
||
} | ||
} | ||
} | ||
} | ||
|
||
/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ | ||
inline fun <T> Iterable<T>.allResult(predicate: (T) -> Result?): Result { | ||
var invalidFlag = false | ||
Check warning on line 43 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L43
|
||
for (element in this) { | ||
if (predicate(element) == OPEN) { | ||
return OPEN | ||
Check warning on line 46 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L46
|
||
} else if (predicate(element) == INVALID) { | ||
invalidFlag = true | ||
Check warning on line 48 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L48
|
||
} | ||
} | ||
return if (invalidFlag) INVALID else VALID | ||
} | ||
|
||
/** returns VALID if any Result is VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ | ||
inline fun <T> Iterable<T>.anyResult(predicate: (T) -> Result?): Result { | ||
var openFlag = false | ||
Check warning on line 56 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L56
|
||
for (element in this) { | ||
if (predicate(element) == VALID) { | ||
return VALID | ||
Check warning on line 59 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L59
|
||
} else if (predicate(element) == OPEN) { | ||
openFlag = true | ||
Check warning on line 61 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L61
|
||
} | ||
} | ||
return if (openFlag) OPEN else INVALID | ||
} | ||
|
||
/** returns VALID if all Results are VALID, otherwise returns OPEN if any result is OPEN, otherwise returns INVALID */ | ||
inline fun <T> Array<T>.allResult(predicate: (T) -> Result?): Result { | ||
var invalidFlag = false | ||
Check warning on line 69 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L69
|
||
for (element in this) { | ||
if (predicate(element) == OPEN) { | ||
return OPEN | ||
Check warning on line 72 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L72
|
||
} 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 */ | ||
inline fun <T> Array<T>.anyResult(predicate: (T) -> Result?): Result { | ||
var openFlag = false | ||
Check warning on line 80 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L80
|
||
for (element in this) { | ||
if (predicate(element) == VALID) { | ||
return VALID | ||
Check warning on line 83 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L83
|
||
} else if (predicate(element) == OPEN) { | ||
openFlag = true | ||
Check warning on line 85 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L85
|
||
} | ||
} | ||
return if (openFlag) OPEN else INVALID | ||
} | ||
|
||
/** precedence order for ternary and: OPEN > INVALID > VALID */ | ||
fun Result.and(other: Result): Result { | ||
return if (this == OPEN || other == OPEN) { | ||
OPEN | ||
Check warning on line 94 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L94
|
||
} else if (this == INVALID || other == INVALID) { | ||
INVALID | ||
Check warning on line 96 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt Codecov / codecov/patchcodyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/Result.kt#L96
|
||
} else { | ||
VALID | ||
} | ||
} |
Check warning
Code scanning / detekt
In most cases using a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty. Warning