From 504966ec05e3505241ced09cc4f246b15075b512 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 9 Aug 2023 18:55:27 +0200 Subject: [PATCH 01/25] add new Op type GroupingOp --- .../aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt | 2 ++ .../aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt | 5 +++++ 2 files changed, 7 insertions(+) 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 8423a9758..a445b33b1 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 @@ -47,6 +47,7 @@ fun Op.cpgGetAllNodes(): Nodes = is FunctionOp -> this@Op.definitions.flatMap { def -> this@CokoBackend.cpgCallFqn(def.fqn) } is ConstructorOp -> this@CokoBackend.cpgConstructor(this.classFqn) + is GroupingOp -> this@Op.ops.flatMap { it.cpgGetAllNodes() } } /** @@ -74,6 +75,7 @@ fun Op.cpgGetNodes(): Nodes = sig.unorderedParameters.all { it?.cpgFlowsTo(arguments) ?: false } } } + is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes() } } /** Returns a list of [ValueDeclaration]s with the matching name. */ diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt index 8be75c30f..6b462b91f 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt @@ -110,6 +110,8 @@ class ConstructorOp internal constructor( } } +data class GroupingOp(val ops: Set, override val ownerClassFqn: String = ""): Op + context(Any) // This is needed to have access to the owner of this function // -> in which class is the function defined that created this [OP] /** @@ -159,6 +161,9 @@ fun constructor(classFqn: String, block: ConstructorOp.() -> Unit): ConstructorO return ConstructorOp(classFqn, this@Any::class.java.name).apply(block) } +context(Any) +fun opGroup(vararg ops: Op): GroupingOp = GroupingOp(ops.toSet(), this@Any::class.java.name) + /** * Create a [Definition] which can be added to the [FunctionOp]. * From e597e473bdae209ebf00144184106e77ef953b0d Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 9 Aug 2023 18:56:55 +0200 Subject: [PATCH 02/25] first draft for translating `concepts` files into coko scripts --- .../coko/dsl/cli/Validation.kt | 2 +- .../coko/dsl/host/CokoExecutor.kt | 127 +++++++++++++++++- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/cli/Validation.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/cli/Validation.kt index df27d81bd..f9be875cd 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/cli/Validation.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/cli/Validation.kt @@ -23,7 +23,7 @@ val Path.fileNameString: String fun validateSpec(spec: List): List { require(spec.all { it.isRegularFile() }) { "All given spec paths must be files." } - require(spec.all { it.fileNameString.endsWith(".codyze.kts") }) { + require(spec.all { it.fileNameString.endsWith(".codyze.kts") || it.fileNameString.endsWith(".concepts") }) { "All given specification files must be coko specification files (*.codyze.kts)." } return spec diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt index 4be8c3db2..36b06586b 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt @@ -25,9 +25,11 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.CokoScript import io.github.detekt.sarif4k.Run import io.github.oshai.kotlinlogging.KotlinLogging import java.nio.file.Path +import kotlin.io.path.extension import kotlin.script.experimental.api.* import kotlin.script.experimental.api.SourceCode import kotlin.script.experimental.host.FileScriptSource +import kotlin.script.experimental.host.StringScriptSource import kotlin.script.experimental.host.toScriptSource import kotlin.script.experimental.jvm.baseClassLoader import kotlin.script.experimental.jvm.jvm @@ -128,10 +130,16 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac var baseClassLoader: ClassLoader? = null val specEvaluator = SpecEvaluator() for (specFile in specFiles) { + val scriptSource = if (specFile.extension == "concepts") { + transformConceptFile(specFile) + } else + FileScriptSource(specFile.toFile()) + + // compile the script val result = eval( - FileScriptSource(specFile.toFile()), + scriptSource, backend = backend, baseClassLoader = baseClassLoader ) @@ -185,5 +193,122 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac } return specEvaluator } + + fun transformConceptFile(specFile: Path): StringScriptSource { + val fileScriptSource = FileScriptSource(specFile.toFile()) + + val fileText = fileScriptSource.text + val preliminaryScriptText = fileText + // Remove all comments + // TODO: is there a better way? + .replace(Regex("//.*\\n"), "\n") + // Handle all `op` keywords that represent an operationPointer (`'op' '=' ('|' )*`) + .replace(Regex("\\s+op\\s+.+=.+\\s")) { opPointerMatchResult -> + val preliminaryResult = opPointerMatchResult.value + // remove the whitespace character at the end of the matchResult + .dropLast(1) + // Replace all `op` keywords with `fun` + .replace("op", "fun") + + // The index of the '{' character that starts the body of the concept that the "op" resides in + val conceptBodyStart = fileText.lastIndexOf('{', opPointerMatchResult.range.first) + // The index of the '}' character that ends the body of the concept that the "op" resides in + val conceptBodyEnd = fileText.indexOf('}', opPointerMatchResult.range.last) + + val conceptBody = fileText.subSequence(conceptBodyStart, conceptBodyEnd) + + val (firstHalf, secondHalf) = preliminaryResult.split(Regex("\\s*=\\s*"), limit = 2) + val opNames = secondHalf.split(Regex("\\s*\\|\\s*")) + + // Find the definitions of the ops that are used for this opPointer + val opDefinitions = opNames.map { (Regex("\\s+op\\s+$it\\(.*\\)").find(conceptBody)?.value ?: "") } + + /* From this line on to the end of this block the code is written to replace all opNames with calls to their actual functions */ + val sb = StringBuilder(firstHalf) + + // Find out all needed parameters + val functionParameters = opDefinitions.flatMap { opDefinition -> + // find the parameters that are used for the op + val opParameters = Regex("\\(.*\\)").find(opDefinition)?.value ?: "" + // remove the `(` and `)` and then split the parameters + removeFirstAndLastChar(opParameters).split(Regex("\\s*,\\s*")) + } + .filter { it.isNotEmpty() } + .toSet() + // add types to the parameters + .map { + if(it.endsWith("...")) + // translate vararg parameters to the correct Kotlin syntax + "vararg ${it.substring(0, it.length - 3)}: Any?" + else "$it: Any?" + } + + sb.append("(") + sb.append(functionParameters.joinToString()) + sb.append("): Op = opGroup(") + + // Append calls to ops + val functionCalls = opDefinitions.map { opDefinition -> + // remove the `op` keyword and all `...` + opDefinition.replace(Regex("(\\s+op\\s+)|(\\.\\.\\.)"), "") + } + + sb.append(functionCalls.joinToString()) + + sb.append(")\n") + sb.toString() + } + + val scriptText = preliminaryScriptText + // Replace all `concept` keywords with `interface` and ensure that the name is capitalized + .replace(Regex("concept\\s+.")) { + makeLastCharUpperCase( + it.value.replace("concept", "interface") + ) + } + // Replace all `enum` keywords with `enum class` and ensure that the name is capitalized + .replace(Regex("enum\\s+.")){ + makeLastCharUpperCase( + it.value.replace("enum", "enum class") + ) + } + // Ensure that all type names are capitalized + .replace(Regex(":\\s*[a-zA-Z]")) { + it.value.uppercase() + } + // Replace all `op` keywords that represent an operation with `fun` and add types for the parameters + .replace(Regex("\\s+op\\s.+\\)\\s?")) { opMatchResult -> + opMatchResult.value + .replace("op", "fun") + .replace(Regex(",( )?.*\\.\\.\\.")) { + it.value.dropLast(3).replace(Regex(",( )?",), ", vararg ") + } + .replace(",", ": Any?,") + .replace(Regex("[^(]\\)")) { + "${it.value.dropLast(1)}: Any?): Op" + } + .plus("\n") + } + .replace(Regex("\\s+var\\s+.+\\s")) { varMatchResult -> + val property = varMatchResult.value.replace("var", "val").dropLast(1) + if(property.contains(':')) + "$property\n" + else + "${property}: Any?\n" + } + + return StringScriptSource(scriptText, fileScriptSource.name) + } + + fun makeLastCharUpperCase(string: String): String { + val lastCharAsUpper = string.last().uppercaseChar() + return string.dropLast(1) + lastCharAsUpper + } + + fun removeFirstAndLastChar(string: String): String = + if(string.isEmpty()) + string + else + string.substring(1, string.length - 1) } } From bf2939cd73d24fa9e2292aa2b7a88d74f42fb04c Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 10 Aug 2023 11:24:31 +0200 Subject: [PATCH 03/25] formatting and add comments --- .../coko/core/dsl/Op.kt | 3 +- .../coko/dsl/host/CokoExecutor.kt | 165 +++++++++++------- 2 files changed, 102 insertions(+), 66 deletions(-) diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt index 6b462b91f..3146c0e61 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt @@ -110,7 +110,8 @@ class ConstructorOp internal constructor( } } -data class GroupingOp(val ops: Set, override val ownerClassFqn: String = ""): Op +/** An [Op] that contains other [Op]s */ +data class GroupingOp(val ops: Set, override val ownerClassFqn: String = "") : Op context(Any) // This is needed to have access to the owner of this function // -> in which class is the function defined that created this [OP] diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt index 36b06586b..9a2ed1f9d 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt @@ -19,6 +19,7 @@ import de.fraunhofer.aisec.codyze.core.executor.Executor import de.fraunhofer.aisec.codyze.core.timed import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.GroupingOp import de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.CokoConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.CokoSarifBuilder import de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.CokoScript @@ -132,9 +133,9 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac for (specFile in specFiles) { val scriptSource = if (specFile.extension == "concepts") { transformConceptFile(specFile) - } else + } else { FileScriptSource(specFile.toFile()) - + } // compile the script val result = @@ -194,69 +195,17 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac return specEvaluator } - fun transformConceptFile(specFile: Path): StringScriptSource { + private fun transformConceptFile(specFile: Path): StringScriptSource { val fileScriptSource = FileScriptSource(specFile.toFile()) val fileText = fileScriptSource.text val preliminaryScriptText = fileText // Remove all comments - // TODO: is there a better way? + // TODO: Is there a way to specify that all Regexes should not include matches found in comments? .replace(Regex("//.*\\n"), "\n") // Handle all `op` keywords that represent an operationPointer (`'op' '=' ('|' )*`) .replace(Regex("\\s+op\\s+.+=.+\\s")) { opPointerMatchResult -> - val preliminaryResult = opPointerMatchResult.value - // remove the whitespace character at the end of the matchResult - .dropLast(1) - // Replace all `op` keywords with `fun` - .replace("op", "fun") - - // The index of the '{' character that starts the body of the concept that the "op" resides in - val conceptBodyStart = fileText.lastIndexOf('{', opPointerMatchResult.range.first) - // The index of the '}' character that ends the body of the concept that the "op" resides in - val conceptBodyEnd = fileText.indexOf('}', opPointerMatchResult.range.last) - - val conceptBody = fileText.subSequence(conceptBodyStart, conceptBodyEnd) - - val (firstHalf, secondHalf) = preliminaryResult.split(Regex("\\s*=\\s*"), limit = 2) - val opNames = secondHalf.split(Regex("\\s*\\|\\s*")) - - // Find the definitions of the ops that are used for this opPointer - val opDefinitions = opNames.map { (Regex("\\s+op\\s+$it\\(.*\\)").find(conceptBody)?.value ?: "") } - - /* From this line on to the end of this block the code is written to replace all opNames with calls to their actual functions */ - val sb = StringBuilder(firstHalf) - - // Find out all needed parameters - val functionParameters = opDefinitions.flatMap { opDefinition -> - // find the parameters that are used for the op - val opParameters = Regex("\\(.*\\)").find(opDefinition)?.value ?: "" - // remove the `(` and `)` and then split the parameters - removeFirstAndLastChar(opParameters).split(Regex("\\s*,\\s*")) - } - .filter { it.isNotEmpty() } - .toSet() - // add types to the parameters - .map { - if(it.endsWith("...")) - // translate vararg parameters to the correct Kotlin syntax - "vararg ${it.substring(0, it.length - 3)}: Any?" - else "$it: Any?" - } - - sb.append("(") - sb.append(functionParameters.joinToString()) - sb.append("): Op = opGroup(") - - // Append calls to ops - val functionCalls = opDefinitions.map { opDefinition -> - // remove the `op` keyword and all `...` - opDefinition.replace(Regex("(\\s+op\\s+)|(\\.\\.\\.)"), "") - } - - sb.append(functionCalls.joinToString()) - - sb.append(")\n") - sb.toString() + handleOpPointer(opPointerMatchResult, fileText) } val scriptText = preliminaryScriptText @@ -267,7 +216,7 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac ) } // Replace all `enum` keywords with `enum class` and ensure that the name is capitalized - .replace(Regex("enum\\s+.")){ + .replace(Regex("enum\\s+.")) { makeLastCharUpperCase( it.value.replace("enum", "enum class") ) @@ -291,24 +240,110 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac } .replace(Regex("\\s+var\\s+.+\\s")) { varMatchResult -> val property = varMatchResult.value.replace("var", "val").dropLast(1) - if(property.contains(':')) + if (property.contains(':')) { "$property\n" - else - "${property}: Any?\n" + } else { + "$property: Any?\n" + } } return StringScriptSource(scriptText, fileScriptSource.name) } - fun makeLastCharUpperCase(string: String): String { + + /** + * This translates a match of a `op` keyword that represent an operationPointer (`'op' '=' ('|' )*`) + * into a Kotlin function that returns a [GroupingOp]. + * + * Example: + * ``` + * op log = info | warn + * op info(msg) + * op warn(msg) + * ``` + * is translated into + * ``` + * fun log(msg: Any?): GroupingOp = opGroup(info(msg), warn(msg)) + * ``` + */ + private fun handleOpPointer(opPointerMatchResult: MatchResult, fileText: String): String { + val preliminaryResult = opPointerMatchResult.value + // remove the whitespace character at the end of the matchResult + .dropLast(1) + // Replace all `op` keywords with `fun` + .replace("op", "fun") + + // The index of the '{' character that starts the body of the concept that the "op" resides in + val conceptBodyStart = fileText.lastIndexOf('{', opPointerMatchResult.range.first) + // The index of the '}' character that ends the body of the concept that the "op" resides in + val conceptBodyEnd = fileText.indexOf('}', opPointerMatchResult.range.last) + + // The body of the concept as string + val conceptBody = fileText.subSequence(conceptBodyStart, conceptBodyEnd) + + // Split the definition of the operationPointer at the `=` + val (firstHalf, secondHalf) = preliminaryResult.split(Regex("\\s*=\\s*"), limit = 2) + // Split the grouped ops into separate strings + val opNames = secondHalf.split(Regex("\\s*\\|\\s*")) + + val sb = StringBuilder(firstHalf) + + sb.append("(") + // Find the definitions of the ops that are used for this opPointer + val opDefinitions = opNames.map { (Regex("\\s+op\\s+$it\\(.*\\)").find(conceptBody)?.value ?: "") } + // Append parameters needed for the function which are built by combining + // the parameters of the grouped ops + sb.append(buildFunctionParameters(opDefinitions)) + sb.append("): Op = opGroup(") + + // Append calls to ops + val functionCalls = opDefinitions.map { opDefinition -> + // remove the `op` keyword and all `...` + opDefinition.replace(Regex("(\\s+op\\s+)|(\\.\\.\\.)"), "") + } + sb.append(functionCalls.joinToString()) + + sb.append(")\n") + return sb.toString() + } + + /** + * This combines all parameters of the ops in [opDefinitions] into a set of parameters + * that are needed to call all functions representing the ops and combines them into a string. + */ + private fun buildFunctionParameters(opDefinitions: List): String { + // Find out all needed parameters + val functionParameters = opDefinitions.flatMap { opDefinition -> + // find the parameters that are used for the op + val opParameters = Regex("\\(.*\\)").find(opDefinition)?.value ?: "" + // remove the `(` and `)` and then split the parameters + removeFirstAndLastChar(opParameters).split(Regex("\\s*,\\s*")) + } + .filter { it.isNotEmpty() } + .toSet() + // add types to the parameters + .map { + if (it.endsWith("...")) { + // translate vararg parameters to the correct Kotlin syntax + "vararg ${it.substring(0, it.length - 3)}: Any?" + } else { + "$it: Any?" + } + } + + return functionParameters.joinToString() + } + + private fun makeLastCharUpperCase(string: String): String { val lastCharAsUpper = string.last().uppercaseChar() return string.dropLast(1) + lastCharAsUpper } - fun removeFirstAndLastChar(string: String): String = - if(string.isEmpty()) + private fun removeFirstAndLastChar(string: String): String = + if (string.isEmpty()) { string - else + } else { string.substring(1, string.length - 1) + } } } From 711ec97c16ab21871d626bb8adfb1a9e1b504511 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 17 Aug 2023 12:15:41 +0200 Subject: [PATCH 04/25] move translation to own class and implement imports of concepts files --- .../coko/dsl/CokoScript.kt | 31 +-- .../coko/dsl/host/CokoExecutor.kt | 193 +++++------------- .../coko/dsl/host/ConceptTranslator.kt | 172 ++++++++++++++++ 3 files changed, 219 insertions(+), 177 deletions(-) create mode 100644 codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt index 6827a0f76..8934337d4 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt @@ -21,8 +21,6 @@ import io.github.oshai.kotlinlogging.KotlinLogging import java.io.File import kotlin.script.experimental.annotations.KotlinScript import kotlin.script.experimental.api.* -import kotlin.script.experimental.host.FileBasedScriptSource -import kotlin.script.experimental.host.FileScriptSource import kotlin.script.experimental.jvm.* import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext @@ -61,6 +59,7 @@ interface PluginDependenciesSpec { internal object ProjectScriptCompilationConfiguration : ScriptCompilationConfiguration({ + baseClass(CokoScript::class) jvm { dependenciesFromClassContext( // extract dependencies from the host environment @@ -109,12 +108,6 @@ internal object ProjectScriptCompilationConfiguration : context.compilationConfiguration.asSuccess() } } - - // the callback called than any of the listed file-level annotations are encountered in - // the compiled script - // the processing is defined by the `handler`, that may return refined configuration - // depending on the annotations - onAnnotations(Import::class, handler = ::configureImportDepsOnAnnotations) } ide { acceptedLocations(ScriptAcceptedLocation.Everywhere) } @@ -146,29 +139,7 @@ private fun resolveClasspathAndGenerateExtensionsFor(pluginsBlock: String?): Lis ) } -// The handler that is called during script compilation in order to reconfigure compilation on the -// fly -fun configureImportDepsOnAnnotations( - context: ScriptConfigurationRefinementContext -): ResultWithDiagnostics { - val annotations = - // If no action is performed, the original configuration should be returned - context.collectedData?.get(ScriptCollectedData.foundAnnotations)?.takeIf { it.isNotEmpty() } - ?: return context.compilationConfiguration.asSuccess() - - val scriptBaseDir = (context.script as? FileBasedScriptSource)?.file?.parentFile - val importedSources = - annotations.flatMap { - (it as? Import)?.paths?.map { sourceName -> - FileScriptSource(scriptBaseDir?.resolve(sourceName) ?: File(sourceName)) - }.orEmpty() - } - return ScriptCompilationConfiguration(context.compilationConfiguration) { - if (importedSources.isNotEmpty()) importScripts.append(importedSources) - } - .asSuccess() -} object ProjectScriptEvaluationConfiguration : ScriptEvaluationConfiguration({ diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt index 9a2ed1f9d..6e44fb687 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt @@ -19,16 +19,18 @@ import de.fraunhofer.aisec.codyze.core.executor.Executor import de.fraunhofer.aisec.codyze.core.timed import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.GroupingOp +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Import import de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.CokoConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.CokoSarifBuilder import de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.CokoScript import io.github.detekt.sarif4k.Run import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.File import java.nio.file.Path import kotlin.io.path.extension import kotlin.script.experimental.api.* import kotlin.script.experimental.api.SourceCode +import kotlin.script.experimental.host.FileBasedScriptSource import kotlin.script.experimental.host.FileScriptSource import kotlin.script.experimental.host.StringScriptSource import kotlin.script.experimental.host.toScriptSource @@ -81,6 +83,8 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac /** Contains the class loaders of all evaluated scripts */ private val classLoaders = mutableSetOf() + private val conceptTranslator = ConceptTranslator() + /** Evaluates the given project script [sourceCode] against the given [backend]. */ fun eval( sourceCode: String, @@ -96,7 +100,16 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac baseClassLoader: ClassLoader? = null ): ResultWithDiagnostics { val compilationConfiguration = - createJvmCompilationConfigurationFromTemplate() + createJvmCompilationConfigurationFromTemplate() { + refineConfiguration { + // the callback called if any of the listed file-level annotations are encountered in + // the compiled script + // the processing is defined by the `handler`, that may return refined configuration + // depending on the annotations + onAnnotations(Import::class, handler = ::configureImportDepsOnAnnotations) + } + + } val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate { implicitReceivers(backend) @@ -132,7 +145,7 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac val specEvaluator = SpecEvaluator() for (specFile in specFiles) { val scriptSource = if (specFile.extension == "concepts") { - transformConceptFile(specFile) + conceptTranslator.transformConceptFile(specFile) } else { FileScriptSource(specFile.toFile()) } @@ -195,155 +208,41 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac return specEvaluator } - private fun transformConceptFile(specFile: Path): StringScriptSource { - val fileScriptSource = FileScriptSource(specFile.toFile()) - - val fileText = fileScriptSource.text - val preliminaryScriptText = fileText - // Remove all comments - // TODO: Is there a way to specify that all Regexes should not include matches found in comments? - .replace(Regex("//.*\\n"), "\n") - // Handle all `op` keywords that represent an operationPointer (`'op' '=' ('|' )*`) - .replace(Regex("\\s+op\\s+.+=.+\\s")) { opPointerMatchResult -> - handleOpPointer(opPointerMatchResult, fileText) - } - - val scriptText = preliminaryScriptText - // Replace all `concept` keywords with `interface` and ensure that the name is capitalized - .replace(Regex("concept\\s+.")) { - makeLastCharUpperCase( - it.value.replace("concept", "interface") - ) - } - // Replace all `enum` keywords with `enum class` and ensure that the name is capitalized - .replace(Regex("enum\\s+.")) { - makeLastCharUpperCase( - it.value.replace("enum", "enum class") - ) + // The handler that is called during script compilation in order to reconfigure compilation on the + // fly + fun configureImportDepsOnAnnotations( + context: ScriptConfigurationRefinementContext + ): ResultWithDiagnostics { + val annotations = + // If no action is performed, the original configuration should be returned + context.collectedData?.get(ScriptCollectedData.foundAnnotations)?.takeIf { it.isNotEmpty() } + ?: return context.compilationConfiguration.asSuccess() + + val scriptBaseDir = (context.script as? FileBasedScriptSource)?.file?.parentFile + val importedSources = + annotations.flatMap { +// (it as? Import)?.paths?.map { sourceName -> +// FileScriptSource(scriptBaseDir?.resolve(sourceName) ?: File(sourceName)) +// }.orEmpty() + + (it as? Import)?.paths?.mapNotNull { sourceName -> + val file = scriptBaseDir?.resolve(sourceName) ?: File(sourceName).normalize() + val absoluteFile = file.absolutePath + if(sourceName.endsWith(".codyze.kts")) + FileScriptSource(file) + else if (sourceName.endsWith(".concepts")) + conceptTranslator.transformConceptFile(file.toPath()) + else null + }.orEmpty() } - // Ensure that all type names are capitalized - .replace(Regex(":\\s*[a-zA-Z]")) { - it.value.uppercase() - } - // Replace all `op` keywords that represent an operation with `fun` and add types for the parameters - .replace(Regex("\\s+op\\s.+\\)\\s?")) { opMatchResult -> - opMatchResult.value - .replace("op", "fun") - .replace(Regex(",( )?.*\\.\\.\\.")) { - it.value.dropLast(3).replace(Regex(",( )?",), ", vararg ") - } - .replace(",", ": Any?,") - .replace(Regex("[^(]\\)")) { - "${it.value.dropLast(1)}: Any?): Op" - } - .plus("\n") - } - .replace(Regex("\\s+var\\s+.+\\s")) { varMatchResult -> - val property = varMatchResult.value.replace("var", "val").dropLast(1) - if (property.contains(':')) { - "$property\n" - } else { - "$property: Any?\n" - } - } - - return StringScriptSource(scriptText, fileScriptSource.name) - } - - - /** - * This translates a match of a `op` keyword that represent an operationPointer (`'op' '=' ('|' )*`) - * into a Kotlin function that returns a [GroupingOp]. - * - * Example: - * ``` - * op log = info | warn - * op info(msg) - * op warn(msg) - * ``` - * is translated into - * ``` - * fun log(msg: Any?): GroupingOp = opGroup(info(msg), warn(msg)) - * ``` - */ - private fun handleOpPointer(opPointerMatchResult: MatchResult, fileText: String): String { - val preliminaryResult = opPointerMatchResult.value - // remove the whitespace character at the end of the matchResult - .dropLast(1) - // Replace all `op` keywords with `fun` - .replace("op", "fun") - - // The index of the '{' character that starts the body of the concept that the "op" resides in - val conceptBodyStart = fileText.lastIndexOf('{', opPointerMatchResult.range.first) - // The index of the '}' character that ends the body of the concept that the "op" resides in - val conceptBodyEnd = fileText.indexOf('}', opPointerMatchResult.range.last) - - // The body of the concept as string - val conceptBody = fileText.subSequence(conceptBodyStart, conceptBodyEnd) - // Split the definition of the operationPointer at the `=` - val (firstHalf, secondHalf) = preliminaryResult.split(Regex("\\s*=\\s*"), limit = 2) - // Split the grouped ops into separate strings - val opNames = secondHalf.split(Regex("\\s*\\|\\s*")) - - val sb = StringBuilder(firstHalf) - - sb.append("(") - // Find the definitions of the ops that are used for this opPointer - val opDefinitions = opNames.map { (Regex("\\s+op\\s+$it\\(.*\\)").find(conceptBody)?.value ?: "") } - // Append parameters needed for the function which are built by combining - // the parameters of the grouped ops - sb.append(buildFunctionParameters(opDefinitions)) - sb.append("): Op = opGroup(") - - // Append calls to ops - val functionCalls = opDefinitions.map { opDefinition -> - // remove the `op` keyword and all `...` - opDefinition.replace(Regex("(\\s+op\\s+)|(\\.\\.\\.)"), "") + return ScriptCompilationConfiguration(context.compilationConfiguration) { + if (importedSources.isNotEmpty()) importScripts.append(importedSources) } - sb.append(functionCalls.joinToString()) - - sb.append(")\n") - return sb.toString() + .asSuccess() } - /** - * This combines all parameters of the ops in [opDefinitions] into a set of parameters - * that are needed to call all functions representing the ops and combines them into a string. - */ - private fun buildFunctionParameters(opDefinitions: List): String { - // Find out all needed parameters - val functionParameters = opDefinitions.flatMap { opDefinition -> - // find the parameters that are used for the op - val opParameters = Regex("\\(.*\\)").find(opDefinition)?.value ?: "" - // remove the `(` and `)` and then split the parameters - removeFirstAndLastChar(opParameters).split(Regex("\\s*,\\s*")) - } - .filter { it.isNotEmpty() } - .toSet() - // add types to the parameters - .map { - if (it.endsWith("...")) { - // translate vararg parameters to the correct Kotlin syntax - "vararg ${it.substring(0, it.length - 3)}: Any?" - } else { - "$it: Any?" - } - } - - return functionParameters.joinToString() - } + } - private fun makeLastCharUpperCase(string: String): String { - val lastCharAsUpper = string.last().uppercaseChar() - return string.dropLast(1) + lastCharAsUpper - } - private fun removeFirstAndLastChar(string: String): String = - if (string.isEmpty()) { - string - } else { - string.substring(1, string.length - 1) - } - } } diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt new file mode 100644 index 000000000..f083c5262 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt @@ -0,0 +1,172 @@ +package de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.host + +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.GroupingOp +import java.nio.file.Path +import kotlin.io.path.absolute +import kotlin.io.path.name +import kotlin.script.experimental.host.FileScriptSource +import kotlin.script.experimental.host.StringScriptSource + +class ConceptTranslator() { + private val conceptTranslations: MutableMap = mutableMapOf() + + fun transformConceptFile(specFile: Path): StringScriptSource { + val absolutePath = specFile.normalize().absolute() + val cachedTranslation = conceptTranslations[absolutePath] + if(cachedTranslation != null) + return StringScriptSource(cachedTranslation, absolutePath.fileName.name) + + val fileScriptSource = FileScriptSource(specFile.toFile()) + + val fileText = fileScriptSource.text + val preliminaryScriptText = fileText + // Remove all comments + // TODO: Is there a way to specify that all Regexes should not include matches found in comments? + .replace(Regex("//.*\\n"), "\n") + // Handle all `op` keywords that represent an operationPointer (`'op' '=' ('|' )*`) + .replace(Regex("\\s+op\\s+.+=.+\\s")) { opPointerMatchResult -> + handleOpPointer(opPointerMatchResult, fileText) + } + + val scriptText = preliminaryScriptText + // Replace all `concept` keywords with `interface` and ensure that the name is capitalized + .replace(Regex("concept\\s+.")) { + makeLastCharUpperCase( + it.value.replace("concept", "interface") + ) + } + // Replace all `enum` keywords with `enum class` and ensure that the name is capitalized + .replace(Regex("enum\\s+.")) { + makeLastCharUpperCase( + it.value.replace("enum", "enum class") + ) + } + // Ensure that all type names are capitalized + .replace(Regex(":\\s*[a-zA-Z]")) { + it.value.uppercase() + } + // Replace all `op` keywords that represent an operation with `fun` and add types for the parameters + .replace(Regex("\\s+op\\s.+\\)\\s?")) { opMatchResult -> + opMatchResult.value + .replace("op", "fun") + .replace(Regex(",( )?.*\\.\\.\\.")) { + it.value.dropLast(3).replace(Regex(",( )?",), ", vararg ") + } + .replace(",", ": Any?,") + .replace(Regex("[^(]\\)")) { + "${it.value.dropLast(1)}: Any?): Op" + } + .plus("\n") + } + // Replace all `var` keywords with `val` and add a type if none is given + .replace(Regex("\\s+var\\s+.+\\s")) { varMatchResult -> + val property = varMatchResult.value.replace("var", "val").dropLast(1) + if (property.contains(':')) { + "$property\n" + } else { + "$property: Any\n" + } + } + + conceptTranslations[absolutePath] = scriptText + return StringScriptSource(scriptText, fileScriptSource.name) + } + + + /** + * This translates a match of a `op` keyword that represent an operationPointer (`'op' '=' ('|' )*`) + * into a Kotlin function that returns a [GroupingOp]. + * + * Example: + * ``` + * op log = info | warn + * op info(msg) + * op warn(msg) + * ``` + * is translated into + * ``` + * fun log(msg: Any?): GroupingOp = opGroup(info(msg), warn(msg)) + * ``` + */ + private fun handleOpPointer(opPointerMatchResult: MatchResult, fileText: String): String { + val preliminaryResult = opPointerMatchResult.value + // remove the whitespace character at the end of the matchResult + .dropLast(1) + // Replace all `op` keywords with `fun` + .replace("op", "fun") + + // The index of the '{' character that starts the body of the concept that the "op" resides in + val conceptBodyStart = fileText.lastIndexOf('{', opPointerMatchResult.range.first) + // The index of the '}' character that ends the body of the concept that the "op" resides in + val conceptBodyEnd = fileText.indexOf('}', opPointerMatchResult.range.last) + + // The body of the concept as string + val conceptBody = fileText.subSequence(conceptBodyStart, conceptBodyEnd) + + // Split the definition of the operationPointer at the `=` + val (firstHalf, secondHalf) = preliminaryResult.split(Regex("\\s*=\\s*"), limit = 2) + // Split the grouped ops into separate strings + val opNames = secondHalf.split(Regex("\\s*\\|\\s*")) + + val sb = StringBuilder(firstHalf) + + sb.append("(") + // Find the definitions of the ops that are used for this opPointer + val opDefinitions = opNames.map { (Regex("\\s+op\\s+$it\\(.*\\)").find(conceptBody)?.value ?: "") } + // Append parameters needed for the function which are built by combining + // the parameters of the grouped ops + sb.append(buildFunctionParameters(opDefinitions)) + sb.append("): Op = opGroup(") + + // Append calls to ops + val functionCalls = opDefinitions.map { opDefinition -> + // remove the `op` keyword and all `...` + opDefinition.replace(Regex("(\\s+op\\s+)|(\\.\\.\\.)"), "") + } + sb.append(functionCalls.joinToString()) + + sb.append(")\n") + return sb.toString() + } + + /** + * This combines all parameters of the ops in [opDefinitions] into a set of parameters + * that are needed to call all functions representing the ops and combines them into a string. + */ + private fun buildFunctionParameters(opDefinitions: List): String { + // Find out all needed parameters + val functionParameters = opDefinitions.flatMap { opDefinition -> + // find the parameters that are used for the op + val opParameters = Regex("\\(.*\\)").find(opDefinition)?.value ?: "" + // remove the `(` and `)` and then split the parameters + removeFirstAndLastChar(opParameters).split(Regex("\\s*,\\s*")) + } + .filter { it.isNotEmpty() } + .toSet() + // add types to the parameters + .map { + if (it.endsWith("...")) { + // translate vararg parameters to the correct Kotlin syntax + "vararg ${it.substring(0, it.length - 3)}: Any?" + } else { + "$it: Any?" + } + } + + return functionParameters.joinToString() + } + + private fun makeLastCharUpperCase(string: String): String { + val lastCharAsUpper = string.last().uppercaseChar() + return string.dropLast(1) + lastCharAsUpper + } + + private fun removeFirstAndLastChar(string: String): String = + if (string.isEmpty()) { + string + } else { + string.substring(1, string.length - 1) + } + +} + From 974a3baac25ed930d6e0dadd48d162156f78ceaf Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 17 Aug 2023 13:48:30 +0200 Subject: [PATCH 05/25] add the definition of the concepts files and implement some simple tests of the translation --- .../coko-dsl/src/main/resources/concept.g4 | 69 ++++++++ .../coko/dsl/ConceptTest.kt | 147 ++++++++++++++++++ .../test/resources/concept/bsi-tr.concepts | 66 ++++++++ .../resources/concept/followedBy.concepts | 11 ++ .../followedByImplementations.codyze.kts | 33 ++++ .../concept/followedByRule.codyze.kts | 6 + .../src/test/resources/concept/rpc.concepts | 14 ++ .../src/test/resources/concept/some.concepts | 25 +++ 8 files changed, 371 insertions(+) create mode 100644 codyze-specification-languages/coko/coko-dsl/src/main/resources/concept.g4 create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTest.kt create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr.concepts create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedBy.concepts create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedByImplementations.codyze.kts create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedByRule.codyze.kts create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/rpc.concepts create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/some.concepts diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/resources/concept.g4 b/codyze-specification-languages/coko/coko-dsl/src/main/resources/concept.g4 new file mode 100644 index 000000000..e41f11095 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/main/resources/concept.g4 @@ -0,0 +1,69 @@ +grammar concept; +file: (concept | ruleSet | rule | enum)* EOF; + +name: ID | EMPTY_ID; +type: ID; + +concept: 'concept' name conceptBody; +conceptBody: '{' conceptItem* '}'; +conceptItem: variable | operation | operationPointer; +variable: 'var' name (':' type)?; +operationPointer: 'op' name '=' name ('|' name)*; +operation: 'op' name parameterClause; +parameterClause: '(' parameterList? ')'; +parameterList: parameter (',' parameter)*; +parameter: name | name '...'; + +enum: 'enum' name enumBody; +enumBody: '{' name (',' name)* '}'; + +ruleSet: 'ruleset' name ruleSetBody; +ruleSetBody: '{' rule+ '}'; + +rule: 'rule' name ruleBody; +ruleBody: '{' ruleItem* '}'; +ruleItem: ruleVariable | when; + +ruleVariable: 'var' name ':' type; +when: 'when' condition '{' whenBody '}'; +whenBody: assert*; + +assert: assertCall | assertEnsure; + +assertCall: 'call' opReference assertCallLocation?; +assertCallLocation: eogDirection 'in' eogScope; +assertEnsure: 'ensure' booleanExpression; + +eogScope: 'function scope'; // currently, we only support function scope +eogDirection: 'afterwards' | 'before' | 'somewhere'; + +condition: booleanExpression; +expression: + name '.' name parameterClause #callExpression | + expression '.' name #memberExpression | + name #referenceExpression | + literal #literalExpression; +booleanExpression: + lhs op rhs #comparison | + lhs IN array #rangeExpression | + opReference #opExpression | + booleanExpression AND booleanExpression #andExpression; +array: '[' arrayElement (',' arrayElement)* ']'; +arrayElement: literal | name; +lhs: expression; +op: OPERATOR; +rhs: expression; +literal: LITERAL; + +opReference: name '::' (name | WILDCARD) parameterClause; + +AND: 'and'; +OR: 'or'; +IN: 'in'; +WILDCARD: '*'; +LITERAL: [0-9]+; +EMPTY_ID: '_'; // empty identifier +ID : [a-zA-Z]+; // identifier +WS : [ \t\r\n]+ -> skip; // skip spaces, tabs, newlines +OPERATOR: '==' | '<=' | '>=' | '!='; +LINE_COMMENT: '//' ~[\r\n]* -> skip; // skip comments \ No newline at end of file diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTest.kt b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTest.kt new file mode 100644 index 000000000..aab9dfdb9 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTest.kt @@ -0,0 +1,147 @@ +package de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl + +import de.fraunhofer.aisec.codyze.backends.cpg.CPGConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.host.CokoExecutor +import de.fraunhofer.aisec.cpg.passes.EdgeCachePass +import de.fraunhofer.aisec.cpg.passes.UnreachableEOGPass +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Test +import kotlin.io.path.Path +import kotlin.reflect.full.isSubtypeOf +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class ConceptTranslationTest { + + private val sourceFiles = listOfNotNull( + CokoCpgIntegrationTest::class.java.classLoader + .getResource("IntegrationTests/CokoCpg/Main.java"), + CokoCpgIntegrationTest::class.java.classLoader + .getResource("IntegrationTests/CokoCpg/SimpleOrder.java") + ).map { Path(it.path) }.also { assertEquals(2, it.size) } + + val cpgConfiguration = + CPGConfiguration( + source = sourceFiles, + useUnityBuild = false, + typeSystemActiveInFrontend = true, + debugParser = false, + disableCleanup = false, + codeInNodes = true, + matchCommentsToNodes = false, + processAnnotations = false, + failOnError = false, + useParallelFrontends = false, + defaultPasses = true, + additionalLanguages = setOf(), + symbols = mapOf(), + includeBlocklist = listOf(), + includePaths = listOf(), + includeAllowlist = listOf(), + loadIncludes = false, + passes = listOf(UnreachableEOGPass::class, EdgeCachePass::class), + ) + + @Test + fun `test simple concept translation`() { + val specFiles = listOfNotNull( + CokoCpgIntegrationTest::class.java.classLoader + .getResource("concept/bsi-tr.concepts"), + ).map { Path(it.path) } + + val cokoConfiguration = + CokoConfiguration( + goodFindings = true, + pedantic = false, + spec = specFiles, + disabledSpecRules = emptyList(), + ) + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend, specFiles) + + val expectedInterfaceToExpectedMembers = mapOf( + "Cypher" to listOf("algo", "mode", "keySize", "tagSize"), + "InitializationVector" to listOf("size"), + "Encryption" to listOf("cypher", "iv", "encrypt", "decrypt") + ) + + assertEquals(expectedInterfaceToExpectedMembers.size, specEvaluator.types.size) + + for((expectedInterface, members) in expectedInterfaceToExpectedMembers) { + val actualInterfaces = specEvaluator.types.filter {it.simpleName?.contains(expectedInterface) ?: false} + assertEquals(1, actualInterfaces.size, "Found none or more than one actual interface representing the concept \"$expectedInterface\"") + val actualInterface = actualInterfaces.first() + + assertTrue(actualInterface.members.map { it.name }.containsAll(members)) + } + } + + @Test + fun `test concept translation with op pointer`() { + val specFiles = listOfNotNull( + CokoCpgIntegrationTest::class.java.classLoader + .getResource("concept/some.concepts"), + ).map { Path(it.path) } + + val cokoConfiguration = + CokoConfiguration( + goodFindings = true, + pedantic = false, + spec = specFiles, + disabledSpecRules = emptyList(), + ) + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend, specFiles) + + val expectedInterfaceToExpectedMembers = mapOf( + "Logging" to listOf("log", "info", "warn", "error"), + "Database" to listOf("init", "insert"), + "UserContext" to listOf("user") + ) + + assertEquals(3, specEvaluator.types.size) + for((expectedInterface, members) in expectedInterfaceToExpectedMembers) { + val actualInterfaces = specEvaluator.types.filter {it.simpleName?.contains(expectedInterface) ?: false} + assertEquals(1, actualInterfaces.size, "Found none or more than one actual interface representing the concept \"$expectedInterface\"") + val actualInterface = actualInterfaces.first() + assertTrue(actualInterface.members.map { it.name }.containsAll(members)) + + if(expectedInterface == "Logging") { + val log = actualInterface.members.firstOrNull { it.name == "log" } + assertNotNull(log) + assertFalse(log.isAbstract) + } + } + } + + @Test + fun `test concept in combination with coko scripts`() { + val specFiles = listOfNotNull( + CokoCpgIntegrationTest::class.java.classLoader + .getResource("concept/followedBy.concepts"), + CokoCpgIntegrationTest::class.java.classLoader + .getResource("concept/followedByImplementations.codyze.kts"), + CokoCpgIntegrationTest::class.java.classLoader + .getResource("concept/followedByRule.codyze.kts"), + ).map { Path(it.path) } + + val cokoConfiguration = + CokoConfiguration( + goodFindings = true, + pedantic = false, + spec = specFiles, + disabledSpecRules = emptyList(), + ) + + val backend = CokoCpgBackend(cpgConfiguration) + val executor = CokoExecutor(cokoConfiguration, backend) + + val run = executor.evaluate() + assertEquals(1, run.results?.size) + } +} diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr.concepts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr.concepts new file mode 100644 index 000000000..0431eacaf --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr.concepts @@ -0,0 +1,66 @@ +// This concepts file provides a ruleset about "Good Crypto", which we should be able to +// reference in some other concept file + +enum Algorithm { + AES, DES, TripleDES +} + +enum Mode { + // Cipher Block Chaining Message Authentication + CCM, + // Galois/Counter Mode + GCM, + // Cipher Block Chaining + CBC, + // Counter Mode + CTR +} + +concept Cypher { + var algo: Algorithm + var mode: Mode + var keySize: int // in bits + var tagSize: int // in bits; only applicable for mode == CCM +} + +concept InitializationVector { + var size: int // in bits +} + +concept Encryption { + var cypher: Cypher + var iv: InitializationVector + + op encrypt(plaintext) + op decrypt(ciphertext) +} + +//ruleset GoodCrypto { +// // Relates to 2.1.1 Betriebsarten +// rule MustBeAES { +// var enc: Encryption +// +// when enc::encrypt(_) { +// ensure enc.cypher.algo == AES +// ensure enc.cypher.mode in [CCM, GCM, CTR] +// ensure enc.cypher.keySize in [128, 192, 256] +// } +// } +// +// // Relates to 2.1.2 Betriebsbedingungen - CCM +// rule ModesOfOperation { +// var enc: Encryption +// +// when enc::encrypt(_) and enc.cypher.mode == CCM { +// ensure enc.cypher.tagSize >= 96 +// } +// +// when enc::encrypt(_) and enc.cypher.mode == GCM { +// ensure enc.iv.size >= 96 +// } +// +// when enc::encrypt(_) and enc.cypher.mode in [CCM, GCM, CTR] { +// // TODO: iv should not repeat itself +// } +// } +//} \ No newline at end of file diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedBy.concepts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedBy.concepts new file mode 100644 index 000000000..d94727678 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedBy.concepts @@ -0,0 +1,11 @@ +concept LoggingForTest { + op log(msg, args...) +} + +concept ObjectRelationalMapperForTest { + op insert(obj) +} + +concept UserContextForTest { + var user +} diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedByImplementations.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedByImplementations.codyze.kts new file mode 100644 index 000000000..112ec3b1d --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedByImplementations.codyze.kts @@ -0,0 +1,33 @@ +@file:Import("followedBy.concepts") + +plugins { id("cpg") } + +class JavaLoggingTest : LoggingForTest { + override fun log(message: Any?, vararg args: Any?) = op { + definition("java.util.logging.Logger.info") { + signature { + group { + - message + args.forEach { - it } + } + } + } + } +} + +class JDBCTest : ObjectRelationalMapperForTest { + override fun insert(obj: Any?) = op { + definition("java.sql.Statement.executeUpdate") { + signature { + group { + - "INSERT.*" + - obj + } + } + } + } +} + +class JavalinJWTTest : UserContextForTest { + override val user = cpgCallFqn("javalinjwt.JavalinJWT.getTokenFromHeader") +} diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedByRule.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedByRule.codyze.kts new file mode 100644 index 000000000..c931cccac --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/followedByRule.codyze.kts @@ -0,0 +1,6 @@ +@file:Import("followedBy.concepts") + +// TODO: can we "assert" that the ctx.user is not null here? +@Rule("This is a dummy description.") +fun DBActionsAreAlwaysLogged(db: ObjectRelationalMapperForTest, log: LoggingForTest, ctx: UserContextForTest) = + db.insert(Wildcard) followedBy log.log(".*", ctx.user) \ No newline at end of file diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/rpc.concepts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/rpc.concepts new file mode 100644 index 000000000..f2d9a86c2 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/rpc.concepts @@ -0,0 +1,14 @@ +concept Request {} + +concept Validation { + op validate(obj) +} + +//rule AlwaysValidate { +// var req: Request +// var v: Validation +// +// when req::*() { +// call v::validate(req) before in function scope +// } +//} \ No newline at end of file diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/some.concepts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/some.concepts new file mode 100644 index 000000000..137351cf7 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/some.concepts @@ -0,0 +1,25 @@ +concept Logging { + op log = info | warn | error + op info(msg, args...) + op warn(msg, args...) + op error(msg, args...) +} + +concept Database { + op init() + op insert(obj) +} + +concept UserContext { + var user +} + +//rule AuditLog { +// var db: Database +// var ctx: UserContext +// var log: Logging + +// when db::insert(x) { +// call log::log(_, ctx) afterwards in function scope +// } +//} \ No newline at end of file From 7fedc5b7f0aaf9927cc51f4b5dd15e7019e12822 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 18 Aug 2023 14:45:28 +0200 Subject: [PATCH 06/25] first draft for an evaluator that looks syntactically similar to concept rules --- .../coko/core/CokoBackend.kt | 2 + .../coko/core/WheneverEvaluator.kt | 30 +++++++++++++ .../coko/core/dsl/Annotations.kt | 2 + .../resources/concept/bsi-tr-rules.codyze.kts | 43 +++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt index b58623014..c48e3e4f9 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt @@ -61,4 +61,6 @@ interface CokoBackend : Backend { /** Ensures that there are no calls to the [ops] which have arguments that fit the parameters specified in [ops] */ fun never(vararg ops: Op): Evaluator + + fun whenever(op: Op, assertionBlock: WheneverEvaluator.() -> Unit): Evaluator } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt new file mode 100644 index 000000000..19b183400 --- /dev/null +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt @@ -0,0 +1,30 @@ +package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core + +class WheneverEvaluator { + private val ensures: MutableList = mutableListOf() + + fun ensure(block: Ensure.() -> Unit){ + ensures.add(Ensure().apply(block)) + } + + internal fun getEnsures(): List = ensures + + +} + +class Ensure { + val list = SpecialListBuilder() + +} + +class SpecialList(elements: Collection): ArrayList(elements) { + override fun contains(element: Any): Boolean { + //TODO + + return false + } +} + +class SpecialListBuilder { + operator fun get(vararg things: Any): SpecialList = SpecialList(things.toList()) +} \ No newline at end of file diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Annotations.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Annotations.kt index 6d1e6d1bf..5ca84b526 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Annotations.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Annotations.kt @@ -42,6 +42,8 @@ annotation class Rule( val precision: Precision = Precision.UNKNOWN, ) +annotation class RuleSet() + enum class Severity { INFO, WARNING, diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts new file mode 100644 index 000000000..4d159d3cb --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts @@ -0,0 +1,43 @@ +@file:Import("bsi-tr.concepts") + +enum class Algorithm { + AES, DES, TripleDES +} + +enum class Mode{ + // Cipher Block Chaining Message Authentication + CCM, + // Galois/Counter Mode + GCM, + // Cipher Block Chaining + CBC, + // Counter Mode + CTR +} + +interface Encryption { + val cypher: Cypher + val iv: Op + + fun encrypt(plaintext: Any?): Op + fun decrypt(ciphertext: Any?): Op +} + +interface Cypher { + var algo: Algorithm + var mode: Mode + var keySize: Int // in bits + var tagSize: Int // in bits; only applicable for mode == CCM +} + +@RuleSet +class GoodCrypto { + @Rule + fun `must be AES`(enc: Encryption) = + whenever(enc.encrypt(Wildcard)) { + ensure { enc.cypher.algo == Algorithm.AES} + ensure { enc.cypher.mode in list[Mode.CCM, Mode.GCM, Mode.CTR] } + } + +} + From 7d01fe133f560443d2585a4793cf200ce6b722d0 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 9 Jun 2023 15:11:30 +0200 Subject: [PATCH 07/25] add concept of data items for modelling --- .../backends/cpg/coko/dsl/DataItemCpgDsl.kt | 55 +++++++++++++++++++ .../coko/core/modelling/DataItem.kt | 12 ++++ 2 files changed, 67 insertions(+) create mode 100644 codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt create mode 100644 codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt 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 new file mode 100644 index 000000000..80c1f7b05 --- /dev/null +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt @@ -0,0 +1,55 @@ +package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl + +import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ReturnValueItem +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Value +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.graph.evaluate +import de.fraunhofer.aisec.cpg.graph.literals +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.variables + +/** + * Get all [Nodes] that are associated with this [DataItem]. + */ +context(CokoBackend) +fun DataItem.cpgGetAllNodes(): Nodes = + when (this@DataItem) { + is ReturnValueItem -> op.cpgGetAllNodes().flatMap { it.getVariableInNextDFGOrThis() } + is Value -> this@DataItem.getNodes() + } + +/** + * Get all [Nodes] that are associated with this [DataItem]. + */ +context(CokoBackend) +fun DataItem.cpgGetNodes(): Nodes { + return when (this@DataItem) { + is ReturnValueItem -> op.cpgGetNodes().flatMap { it.getVariableInNextDFGOrThis() } + is Value -> this@DataItem.getNodes() + } +} + +context(CokoBackend) +private fun Value.getNodes(): Nodes = + cpg.literals.filter { + node -> + node.value == this.value + } + cpg.variables.filter { + node -> + node.evaluate() == this.value + } + +/** + * Returns all [VariableDeclaration]s and [DeclaredReferenceExpression]s that have a DFG edge from [this]. + * If there are none, returns [this]. + */ +private fun Node.getVariableInNextDFGOrThis(): Nodes = + this.nextDFG + .filter { + next -> + next is DeclaredReferenceExpression || next is VariableDeclaration + }.ifEmpty { listOf(this) } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt new file mode 100644 index 000000000..0e256da34 --- /dev/null +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt @@ -0,0 +1,12 @@ +package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling + +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op + +sealed interface DataItem + +data class ReturnValueItem(val op: Op) : DataItem { + override fun toString(): String = "Return value of $op" +} + +data class Value(val value: Any) : DataItem + From 06486571842a314f144f41c97a5fb2b08cf40178 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 24 Aug 2023 13:17:49 +0200 Subject: [PATCH 08/25] rename file to have same name as class --- ...nceptTest.kt => ConceptTranslationTest.kt} | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) rename codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/{ConceptTest.kt => ConceptTranslationTest.kt} (85%) diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTest.kt b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt similarity index 85% rename from codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTest.kt rename to codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt index aab9dfdb9..6bf629107 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTest.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt @@ -8,8 +8,6 @@ import de.fraunhofer.aisec.cpg.passes.UnreachableEOGPass import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Test import kotlin.io.path.Path -import kotlin.reflect.full.isSubtypeOf -import kotlin.test.assertContains import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -71,9 +69,13 @@ class ConceptTranslationTest { assertEquals(expectedInterfaceToExpectedMembers.size, specEvaluator.types.size) - for((expectedInterface, members) in expectedInterfaceToExpectedMembers) { - val actualInterfaces = specEvaluator.types.filter {it.simpleName?.contains(expectedInterface) ?: false} - assertEquals(1, actualInterfaces.size, "Found none or more than one actual interface representing the concept \"$expectedInterface\"") + for ((expectedInterface, members) in expectedInterfaceToExpectedMembers) { + val actualInterfaces = specEvaluator.types.filter { it.simpleName?.contains(expectedInterface) ?: false } + assertEquals( + 1, + actualInterfaces.size, + "Found none or more than one actual interface representing the concept \"$expectedInterface\"" + ) val actualInterface = actualInterfaces.first() assertTrue(actualInterface.members.map { it.name }.containsAll(members)) @@ -105,13 +107,17 @@ class ConceptTranslationTest { ) assertEquals(3, specEvaluator.types.size) - for((expectedInterface, members) in expectedInterfaceToExpectedMembers) { - val actualInterfaces = specEvaluator.types.filter {it.simpleName?.contains(expectedInterface) ?: false} - assertEquals(1, actualInterfaces.size, "Found none or more than one actual interface representing the concept \"$expectedInterface\"") + for ((expectedInterface, members) in expectedInterfaceToExpectedMembers) { + val actualInterfaces = specEvaluator.types.filter { it.simpleName?.contains(expectedInterface) ?: false } + assertEquals( + 1, + actualInterfaces.size, + "Found none or more than one actual interface representing the concept \"$expectedInterface\"" + ) val actualInterface = actualInterfaces.first() assertTrue(actualInterface.members.map { it.name }.containsAll(members)) - if(expectedInterface == "Logging") { + if (expectedInterface == "Logging") { val log = actualInterface.members.firstOrNull { it.name == "log" } assertNotNull(log) assertFalse(log.isAbstract) From c5a1ff495234bdd498fe597caebdf6800fd13adf Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 24 Aug 2023 13:44:11 +0200 Subject: [PATCH 09/25] continue development of whenever evaluator --- .../backends/cpg/coko/dsl/DataItemCpgDsl.kt | 16 ++++ .../cpg/coko/dsl/ImplementationDsl.kt | 13 ++-- .../coko/core/CokoBackend.kt | 9 ++- .../coko/core/WheneverEvaluator.kt | 57 +++++++++----- .../coko/core/dsl/Condition.kt | 69 +++++++++++++++++ .../coko/core/dsl/Op.kt | 23 ++++-- .../coko/core/dsl/TransformationResult.kt | 54 +++++++++++++ .../coko/core/modelling/BackendDataItem.kt | 28 +++++++ .../core/modelling/ConditionComponents.kt | 56 ++++++++++++++ .../coko/core/modelling/DataItem.kt | 75 ++++++++++++++++++- .../coko/dsl/CokoScript.kt | 2 - .../coko/dsl/host/CokoExecutor.kt | 16 ++-- .../coko/dsl/host/ConceptTranslator.kt | 29 +++++-- .../coko/dsl/ConceptTranslationTest.kt | 16 ++++ .../resources/concept/bsi-tr-rules.codyze.kts | 31 +++++--- 15 files changed, 432 insertions(+), 62 deletions(-) create mode 100644 codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt create mode 100644 codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt create mode 100644 codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/BackendDataItem.kt create mode 100644 codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt 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 80c1f7b05..c292e8b3c 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 @@ -1,3 +1,19 @@ +/* + * 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.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes 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 a445b33b1..8b8d1c661 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 @@ -21,16 +21,11 @@ import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes 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.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.codyze.specificationLanguages.coko.core.modelling.* import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration -import de.fraunhofer.aisec.cpg.graph.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.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.query.dataFlow // @@ -191,8 +186,10 @@ fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = f parameter.param cpgFlowsTo arguments[i] // checks if the type of the argument is the same is Type -> cpgCheckType(parameter, i) - // check if any of the Nodes from the Op flow to the argument + // 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 + is DataItem -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] // checks if there is dataflow from the parameter to the argument in the same position else -> parameter cpgFlowsTo arguments[i] } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt index c48e3e4f9..79c0ca3e2 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt @@ -16,8 +16,10 @@ package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core import de.fraunhofer.aisec.codyze.core.backend.Backend +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Condition import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Order +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ConditionComponent import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.OrderToken import kotlin.reflect.KFunction @@ -62,5 +64,10 @@ interface CokoBackend : Backend { /** Ensures that there are no calls to the [ops] which have arguments that fit the parameters specified in [ops] */ fun never(vararg ops: Op): Evaluator - fun whenever(op: Op, assertionBlock: WheneverEvaluator.() -> Unit): Evaluator + fun whenever( + premise: Condition.() -> ConditionComponent, + assertionBlock: WheneverEvaluator.() -> Unit + ): WheneverEvaluator + + fun whenever(premise: ConditionComponent, assertionBlock: WheneverEvaluator.() -> Unit): WheneverEvaluator } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt index 19b183400..12b037d92 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt @@ -1,30 +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.codyze.specificationLanguages.coko.core -class WheneverEvaluator { - private val ensures: MutableList = mutableListOf() +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Condition +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ConditionComponent - fun ensure(block: Ensure.() -> Unit){ - ensures.add(Ensure().apply(block)) - } +abstract class WheneverEvaluator(protected val premise: ConditionComponent) : Evaluator { + protected val ensures: MutableList = mutableListOf() + protected val callAssertions: MutableList = mutableListOf() - internal fun getEnsures(): List = ensures + fun ensure(block: Condition.() -> ConditionComponent) { + ensures.add(Condition().run(block)) + } + fun call(op: Op, location: CallLocationBuilder.() -> CallLocation? = { null }) { + callAssertions.add(CallAssertion(op, CallLocationBuilder().run(location))) + } +} +enum class Direction { + afterwards, before, somewhere } -class Ensure { - val list = SpecialListBuilder() +enum class Scope { + Function, Block +} +class CallLocationBuilder { + infix fun Direction.within(scope: Scope) = CallLocation(this@Direction, scope) } +data class CallLocation(val direction: Direction, val scope: Scope) -class SpecialList(elements: Collection): ArrayList(elements) { - override fun contains(element: Any): Boolean { - //TODO +data class CallAssertion(val op: Op, val location: CallLocation? = null) - return false - } +class ListBuilder { + operator fun get(vararg things: E): List = things.toList() } - -class SpecialListBuilder { - operator fun get(vararg things: Any): SpecialList = SpecialList(things.toList()) -} \ No newline at end of file diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt new file mode 100644 index 000000000..1e1f1ea23 --- /dev/null +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt @@ -0,0 +1,69 @@ +/* + * 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.codyze.specificationLanguages.coko.core.dsl + +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ListBuilder +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* + +fun condition(block: Condition.() -> ConditionComponent) = Condition().run(block) + +class Condition { + val list by lazy { ListBuilder() } + + infix fun Any.within(collection: Collection<*>) = + ContainsConditionComponent(value(this), collection) + + infix fun DataItem.gt(other: DataItem) = + BinaryConditionComponent(this, other, BinaryOperatorName.GT) + + infix fun Any.gt(other: Any) = value(this) gt value(other) + + infix fun DataItem.geq(other: DataItem) = + BinaryConditionComponent(this, other, BinaryOperatorName.GEQ) + + infix fun Any.geq(other: Any) = value(this) geq value(other) + + infix fun DataItem.lt(other: DataItem) = + BinaryConditionComponent(this, other, BinaryOperatorName.LT) + + infix fun Any.lt(other: Any) = value(this) lt value(other) + + infix fun DataItem.leq(other: DataItem) = + BinaryConditionComponent(this, other, BinaryOperatorName.LEQ) + + infix fun Any.leq(other: Any) = value(this) leq value(other) + + infix fun DataItem.eq(other: DataItem) = + BinaryConditionComponent(this, other, BinaryOperatorName.EQ) + + infix fun Any.eq(other: Any) = value(this) eq value(other) + + infix fun DataItem.neq(other: DataItem) = + BinaryConditionComponent(this, other, BinaryOperatorName.NEQ) + + infix fun Any.neq(other: Any) = value(this) neq value(other) + + fun call(op: Op) = CallConditionComponent(op) + + infix fun ConditionComponent.and(other: ConditionComponent) = + BinaryConditionComponent(this, other, BinaryOperatorName.AND) + + infix fun ConditionComponent.or(other: ConditionComponent) = + BinaryConditionComponent(this, other, BinaryOperatorName.OR) + + fun not(conditionComponent: ConditionComponent) = UnaryConditionComponent(conditionComponent, UnaryOperatorName.NOT) +} diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt index 3146c0e61..5210fb4b5 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt @@ -16,22 +16,29 @@ package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Definition -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Parameter -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ParameterGroup -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Signature +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.OrderFragment import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.TerminalOrderNode -@CokoMarker sealed interface Op : OrderFragment { +@CokoMarker sealed interface Op : OrderFragment, ConditionComponent { val ownerClassFqn: String + val returnValue: ReturnValueItem + get() = ReturnValueItem(this) + + val arguments: Arguments + get() = Arguments(this) + override fun toNode(): TerminalOrderNode = TerminalOrderNode( baseName = ownerClassFqn, opName = hashCode().toString() ) } +class Arguments(val op: Op) { + operator fun get(index: Int): ArgumentItem = ArgumentItem(op, index) +} + /** * Represents a group of functions that serve the same purpose in the API. * @@ -111,7 +118,11 @@ class ConstructorOp internal constructor( } /** An [Op] that contains other [Op]s */ -data class GroupingOp(val ops: Set, override val ownerClassFqn: String = "") : Op +data class GroupingOp(val ops: Set, override val ownerClassFqn: String = "") : Op { + override fun toString(): String { + return ops.joinToString() + } +} context(Any) // This is needed to have access to the owner of this function // -> in which class is the function defined that created this [OP] diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt new file mode 100644 index 000000000..532e588ef --- /dev/null +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt @@ -0,0 +1,54 @@ +/* + * 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.codyze.specificationLanguages.coko.core.dsl + +sealed interface TransformationResult { + val isFailure: Boolean + val isSuccess: Boolean + + fun getValueOrNull(): E? + fun getMessageOrNull(): String? + override fun toString(): String + + companion object { + fun failure(message: String): TransformationResult = TransformationFailure(message) + + fun success(value: E): TransformationResult = TransformationSuccess(value) + } +} + +private data class TransformationSuccess(val value: E) : TransformationResult { + override val isFailure: Boolean + get() = false + override val isSuccess: Boolean + get() = true + + override fun getValueOrNull(): E? = value + + override fun getMessageOrNull(): String? = null +} + +private data class TransformationFailure(val message: String) : TransformationResult { + override val isFailure: Boolean + get() = true + override val isSuccess: Boolean + get() = false + + override fun getValueOrNull(): E? = null + + override fun getMessageOrNull(): String = message +} diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/BackendDataItem.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/BackendDataItem.kt new file mode 100644 index 000000000..56b0a78b2 --- /dev/null +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/BackendDataItem.kt @@ -0,0 +1,28 @@ +/* + * 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.codyze.specificationLanguages.coko.core.modelling + +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend + +/** + * Interface for accessing info about a DataItem from the [CokoBackend] + */ +interface BackendDataItem { + val name: String? + val value: Any? + val type: String? +} diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt new file mode 100644 index 000000000..b1c52b099 --- /dev/null +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt @@ -0,0 +1,56 @@ +/* + * 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.codyze.specificationLanguages.coko.core.modelling + +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op + +interface ConditionComponent + +class BinaryConditionComponent( + val left: ConditionComponent, + val right: ConditionComponent, + val operator: BinaryOperatorName +) : ConditionComponent + +class UnaryConditionComponent( + val conditionComponent: ConditionComponent, + val operator: UnaryOperatorName +) : ConditionComponent + +class CallConditionComponent(val op: Op) : ConditionComponent + +class ContainsConditionComponent(val item: DataItem, val collection: Collection) : ConditionComponent + +enum class BinaryOperatorName(val operatorCodes: List) { + GEQ(listOf(">=")), + GT(listOf(">")), + LEQ(listOf("<=")), + LT(listOf("<")), + EQ(listOf("==")), // TODO: python `==` vs `is` + NEQ(listOf("!=")), + AND(listOf("&&", "and")), + OR(listOf("||", "or")) +} + +enum class UnaryOperatorName(val operatorCodes: List) { + NOT(listOf("!", "not")) +} + +// idea: +// isChecked(doStuff(Wildcard).returnValue) { +// (it lessThan 0) or (it greaterThan 1) +// } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt index 0e256da34..fc5c06074 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt @@ -1,12 +1,81 @@ +/* + * 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.codyze.specificationLanguages.coko.core.modelling import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.TransformationResult + +sealed interface DataItem : ConditionComponent -sealed interface DataItem +sealed interface HasTransformation : DataItem { + val transformation: (BackendDataItem) -> TransformationResult + infix fun withTransformation( + newTransformation: (BackendDataItem) -> TransformationResult + ): HasTransformation +} -data class ReturnValueItem(val op: Op) : DataItem { +data class ReturnValueItem( + val op: Op, + override val transformation: (BackendDataItem) -> TransformationResult = { + TransformationResult.failure("No transformation given from user") + } +) : HasTransformation { override fun toString(): String = "Return value of $op" + + override infix fun withTransformation( + newTransformation: (BackendDataItem) -> TransformationResult + ): ReturnValueItem = + ReturnValueItem(op = op, transformation = newTransformation) +} + +data class ArgumentItem( + val op: Op, + val number: Int, + override val transformation: (BackendDataItem) -> TransformationResult = { + TransformationResult.failure("No transformation given from user") + } +) : HasTransformation { + init { + require(number >= 0) { "The number of the argument cannot be negative" } + } + + override fun withTransformation( + newTransformation: (BackendDataItem) -> TransformationResult + ): ArgumentItem = + ArgumentItem(op = op, number = number, transformation = newTransformation) + + override fun toString(): String = "${number.ordinal()} argument of $op" + + @Suppress("MagicNumber") + fun Int.ordinal(): String { + return "$this" + when { + (this % 100 in 11..13) -> "th" + (this % 10) == 1 -> "st" + (this % 10) == 2 -> "nd" + (this % 10) == 3 -> "rd" + else -> "th" + } + } } -data class Value(val value: Any) : DataItem +data class Value internal constructor(val value: Any) : DataItem +fun value(value: Any): DataItem = + when (value) { + is DataItem -> value + else -> Value(value) + } diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt index 8934337d4..b5c60d8da 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt @@ -139,8 +139,6 @@ private fun resolveClasspathAndGenerateExtensionsFor(pluginsBlock: String?): Lis ) } - - object ProjectScriptEvaluationConfiguration : ScriptEvaluationConfiguration({ // if a script is imported multiple times in the import hierarchy, use a single copy diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt index 6e44fb687..1b0a3c1cc 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt @@ -32,7 +32,6 @@ import kotlin.script.experimental.api.* import kotlin.script.experimental.api.SourceCode import kotlin.script.experimental.host.FileBasedScriptSource import kotlin.script.experimental.host.FileScriptSource -import kotlin.script.experimental.host.StringScriptSource import kotlin.script.experimental.host.toScriptSource import kotlin.script.experimental.jvm.baseClassLoader import kotlin.script.experimental.jvm.jvm @@ -100,7 +99,7 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac baseClassLoader: ClassLoader? = null ): ResultWithDiagnostics { val compilationConfiguration = - createJvmCompilationConfigurationFromTemplate() { + createJvmCompilationConfigurationFromTemplate { refineConfiguration { // the callback called if any of the listed file-level annotations are encountered in // the compiled script @@ -108,7 +107,6 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac // depending on the annotations onAnnotations(Import::class, handler = ::configureImportDepsOnAnnotations) } - } val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate { @@ -227,12 +225,13 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac (it as? Import)?.paths?.mapNotNull { sourceName -> val file = scriptBaseDir?.resolve(sourceName) ?: File(sourceName).normalize() - val absoluteFile = file.absolutePath - if(sourceName.endsWith(".codyze.kts")) + if (sourceName.endsWith(".codyze.kts")) { FileScriptSource(file) - else if (sourceName.endsWith(".concepts")) + } else if (sourceName.endsWith(".concepts")) { conceptTranslator.transformConceptFile(file.toPath()) - else null + } else { + null + } }.orEmpty() } @@ -241,8 +240,5 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac } .asSuccess() } - } - - } diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt index f083c5262..be726a3b0 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt @@ -1,3 +1,19 @@ +/* + * 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.codyze.specificationLanguages.coko.dsl.host import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.GroupingOp @@ -7,14 +23,16 @@ import kotlin.io.path.name import kotlin.script.experimental.host.FileScriptSource import kotlin.script.experimental.host.StringScriptSource -class ConceptTranslator() { +class ConceptTranslator { private val conceptTranslations: MutableMap = mutableMapOf() + @Suppress("MagicNumber") fun transformConceptFile(specFile: Path): StringScriptSource { val absolutePath = specFile.normalize().absolute() val cachedTranslation = conceptTranslations[absolutePath] - if(cachedTranslation != null) + if (cachedTranslation != null) { return StringScriptSource(cachedTranslation, absolutePath.fileName.name) + } val fileScriptSource = FileScriptSource(specFile.toFile()) @@ -72,9 +90,9 @@ class ConceptTranslator() { return StringScriptSource(scriptText, fileScriptSource.name) } - /** - * This translates a match of a `op` keyword that represent an operationPointer (`'op' '=' ('|' )*`) + * This translates a match of a `op` keyword that represent an + * operationPointer (`'op' '=' ('|' )*`) * into a Kotlin function that returns a [GroupingOp]. * * Example: @@ -133,6 +151,7 @@ class ConceptTranslator() { * This combines all parameters of the ops in [opDefinitions] into a set of parameters * that are needed to call all functions representing the ops and combines them into a string. */ + @Suppress("MagicNumber") private fun buildFunctionParameters(opDefinitions: List): String { // Find out all needed parameters val functionParameters = opDefinitions.flatMap { opDefinition -> @@ -167,6 +186,4 @@ class ConceptTranslator() { } else { string.substring(1, string.length - 1) } - } - diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt index 6bf629107..f6e5e2db6 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt @@ -1,3 +1,19 @@ +/* + * 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.codyze.specificationLanguages.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.CPGConfiguration diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts index 4d159d3cb..a0676876b 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts @@ -1,5 +1,7 @@ @file:Import("bsi-tr.concepts") +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.HasTransformation + enum class Algorithm { AES, DES, TripleDES } @@ -15,29 +17,40 @@ enum class Mode{ CTR } +interface Cypher { + val algo: HasTransformation + val mode: HasTransformation + val keySize: HasTransformation // in bits + val tagSize: HasTransformation // in bits; only applicable for mode == CCM +} + +interface InitializationVector { + val size: Int // in bits +} + interface Encryption { val cypher: Cypher - val iv: Op + val iv: InitializationVector fun encrypt(plaintext: Any?): Op fun decrypt(ciphertext: Any?): Op } -interface Cypher { - var algo: Algorithm - var mode: Mode - var keySize: Int // in bits - var tagSize: Int // in bits; only applicable for mode == CCM -} @RuleSet class GoodCrypto { @Rule fun `must be AES`(enc: Encryption) = whenever(enc.encrypt(Wildcard)) { - ensure { enc.cypher.algo == Algorithm.AES} - ensure { enc.cypher.mode in list[Mode.CCM, Mode.GCM, Mode.CTR] } + ensure { enc.cypher.algo eq Algorithm.AES } + ensure { enc.cypher.mode within list[Mode.CCM, Mode.GCM, Mode.CTR] } + ensure { enc.cypher.keySize within list[128, 192, 256]} } + @Rule + fun `modes of operation`(enc: Encryption) = + whenever({ enc.encrypt(Wildcard) and (enc.cypher.mode eq Mode.CCM) }) { + ensure { enc.cypher.tagSize geq 96 } + } } From 4fb65e6eb5013dd229b8599f3895034d3402fc48 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 4 Sep 2023 14:16:47 +0200 Subject: [PATCH 10/25] change the interface structure of DataItem, ConditionComponents and TransformationResult --- .../backends/cpg/coko/dsl/DataItemCpgDsl.kt | 30 +++++++----- .../cpg/coko/dsl/ImplementationDsl.kt | 2 +- .../coko/core/dsl/Condition.kt | 48 +++++++++++-------- .../coko/core/dsl/Op.kt | 2 +- .../coko/core/dsl/TransformationResult.kt | 29 +++++++---- .../core/modelling/ConditionComponents.kt | 45 +++++++++++------ .../coko/core/modelling/DataItem.kt | 35 +++++++------- 7 files changed, 115 insertions(+), 76 deletions(-) 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 c292e8b3c..55f6b66d1 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 @@ -18,46 +18,50 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ArgumentItem import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ReturnValueItem import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Value -import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.evaluate -import de.fraunhofer.aisec.cpg.graph.literals import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression -import de.fraunhofer.aisec.cpg.graph.variables /** * Get all [Nodes] that are associated with this [DataItem]. */ context(CokoBackend) -fun DataItem.cpgGetAllNodes(): Nodes = +fun DataItem<*>.cpgGetAllNodes(): Nodes = when (this@DataItem) { is ReturnValueItem -> op.cpgGetAllNodes().flatMap { it.getVariableInNextDFGOrThis() } is Value -> this@DataItem.getNodes() + is ArgumentItem -> TODO() } /** * Get all [Nodes] that are associated with this [DataItem]. */ context(CokoBackend) -fun DataItem.cpgGetNodes(): Nodes { +fun DataItem<*>.cpgGetNodes(): Nodes { return when (this@DataItem) { is ReturnValueItem -> op.cpgGetNodes().flatMap { it.getVariableInNextDFGOrThis() } is Value -> this@DataItem.getNodes() + is ArgumentItem -> TODO() } } context(CokoBackend) -private fun Value.getNodes(): Nodes = - cpg.literals.filter { - node -> - node.value == this.value - } + cpg.variables.filter { - node -> - node.evaluate() == this.value +private fun Value<*>.getNodes(): Nodes { + val value = this.value + return if (value is Node) { + listOf(value) + } else { + cpg.literals.filter { node -> + node.value == value + } + cpg.variables.filter { node -> + node.evaluate() == value + } } +} /** * Returns all [VariableDeclaration]s and [DeclaredReferenceExpression]s that have a DFG edge from [this]. 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 8b8d1c661..a7d8919a6 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 @@ -189,7 +189,7 @@ fun CallExpression.cpgSignature(vararg parameters: Any?, hasVarargs: Boolean = f // 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 - is DataItem -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] + is DataItem<*> -> parameter.cpgGetNodes() cpgFlowsTo arguments[i] // checks if there is dataflow from the parameter to the argument in the same position else -> parameter cpgFlowsTo arguments[i] } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt index 1e1f1ea23..6f06733f9 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt @@ -25,45 +25,51 @@ class Condition { val list by lazy { ListBuilder() } infix fun Any.within(collection: Collection<*>) = - ContainsConditionComponent(value(this), collection) + ContainsConditionComponent(toValue(this), collection) - infix fun DataItem.gt(other: DataItem) = - BinaryConditionComponent(this, other, BinaryOperatorName.GT) + infix fun DataItem<*>.gt(other: DataItem<*>) = + ComparisonConditionComponent(this, other, ComparisonOperatorName.GT) - infix fun Any.gt(other: Any) = value(this) gt value(other) + infix fun Any.gt(other: Any) = toValue(this) gt toValue(other) - infix fun DataItem.geq(other: DataItem) = - BinaryConditionComponent(this, other, BinaryOperatorName.GEQ) + infix fun DataItem<*>.geq(other: DataItem<*>) = + ComparisonConditionComponent(this, other, ComparisonOperatorName.GEQ) - infix fun Any.geq(other: Any) = value(this) geq value(other) + infix fun Any.geq(other: Any) = toValue(this) geq toValue(other) - infix fun DataItem.lt(other: DataItem) = - BinaryConditionComponent(this, other, BinaryOperatorName.LT) + infix fun DataItem<*>.lt(other: DataItem<*>) = + ComparisonConditionComponent(this, other, ComparisonOperatorName.LT) - infix fun Any.lt(other: Any) = value(this) lt value(other) + infix fun Any.lt(other: Any) = toValue(this) lt toValue(other) - infix fun DataItem.leq(other: DataItem) = - BinaryConditionComponent(this, other, BinaryOperatorName.LEQ) + infix fun DataItem<*>.leq(other: DataItem<*>) = + ComparisonConditionComponent(this, other, ComparisonOperatorName.LEQ) - infix fun Any.leq(other: Any) = value(this) leq value(other) + infix fun Any.leq(other: Any) = toValue(this) leq toValue(other) - infix fun DataItem.eq(other: DataItem) = - BinaryConditionComponent(this, other, BinaryOperatorName.EQ) + infix fun DataItem<*>.eq(other: DataItem<*>) = + ComparisonConditionComponent(this, other, ComparisonOperatorName.EQ) - infix fun Any.eq(other: Any) = value(this) eq value(other) + infix fun Any.eq(other: Any) = toValue(this) eq toValue(other) - infix fun DataItem.neq(other: DataItem) = - BinaryConditionComponent(this, other, BinaryOperatorName.NEQ) + infix fun DataItem<*>.neq(other: DataItem<*>) = + ComparisonConditionComponent(this, other, ComparisonOperatorName.NEQ) - infix fun Any.neq(other: Any) = value(this) neq value(other) + infix fun Any.neq(other: Any) = toValue(this) neq toValue(other) fun call(op: Op) = CallConditionComponent(op) infix fun ConditionComponent.and(other: ConditionComponent) = - BinaryConditionComponent(this, other, BinaryOperatorName.AND) + BinaryLogicalConditionComponent(this, other, BinaryLogicalOperatorName.AND) infix fun ConditionComponent.or(other: ConditionComponent) = - BinaryConditionComponent(this, other, BinaryOperatorName.OR) + BinaryLogicalConditionComponent(this, other, BinaryLogicalOperatorName.OR) fun not(conditionComponent: ConditionComponent) = UnaryConditionComponent(conditionComponent, UnaryOperatorName.NOT) + + private fun toValue(value: Any): DataItem<*> = + when(value) { + is DataItem<*> -> value + else -> Value(value) + } } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt index 5210fb4b5..38fe817a3 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt @@ -20,7 +20,7 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.OrderFragment import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.TerminalOrderNode -@CokoMarker sealed interface Op : OrderFragment, ConditionComponent { +@CokoMarker sealed interface Op : OrderFragment { val ownerClassFqn: String val returnValue: ReturnValueItem diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt index 532e588ef..da6773ced 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt @@ -16,22 +16,32 @@ package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl -sealed interface TransformationResult { +/** + * This class works similar to the [Result] class in the Kotlin standard library. + * The difference is that the [TransformationFailure] can store information other than [Throwable]s to be able to + * describe the reasons for failure in more detail. + */ +sealed interface TransformationResult { val isFailure: Boolean val isSuccess: Boolean fun getValueOrNull(): E? - fun getMessageOrNull(): String? + fun getFailureReasonOrNull(): T? override fun toString(): String companion object { - fun failure(message: String): TransformationResult = TransformationFailure(message) + /** Creates a [TransformationFailure] object with the given [reason]. */ + fun failure(reason: T): TransformationResult = TransformationFailure(reason) - fun success(value: E): TransformationResult = TransformationSuccess(value) + /** Creates a [TransformationSuccess] object with the given [value]. */ + fun success(value: E): TransformationResult = TransformationSuccess(value) } } -private data class TransformationSuccess(val value: E) : TransformationResult { +/** + * This class indicates that the operation was a success and the result is stored in [value]. + */ +private data class TransformationSuccess(val value: E) : TransformationResult { override val isFailure: Boolean get() = false override val isSuccess: Boolean @@ -39,10 +49,13 @@ private data class TransformationSuccess(val value: E) : TransformationResult override fun getValueOrNull(): E? = value - override fun getMessageOrNull(): String? = null + override fun getFailureReasonOrNull(): T? = null } -private data class TransformationFailure(val message: String) : TransformationResult { +/** + * This class indicates that the operation was not successful and the reasons are stored in [reason]. + */ +private data class TransformationFailure(val reason: T) : TransformationResult { override val isFailure: Boolean get() = true override val isSuccess: Boolean @@ -50,5 +63,5 @@ private data class TransformationFailure(val message: String) : Transformatio override fun getValueOrNull(): E? = null - override fun getMessageOrNull(): String = message + override fun getFailureReasonOrNull(): T = reason } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt index b1c52b099..4901b074c 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt @@ -18,39 +18,54 @@ package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op -interface ConditionComponent +sealed interface ConditionNode +sealed interface ConditionComponent : ConditionNode +sealed interface ConditionLeaf : ConditionNode -class BinaryConditionComponent( - val left: ConditionComponent, - val right: ConditionComponent, +sealed interface BinaryConditionComponent : ConditionComponent { + val left: ConditionNode + val right: ConditionNode val operator: BinaryOperatorName -) : ConditionComponent +} + +class ComparisonConditionComponent( + override val left: DataItem<*>, + override val right: DataItem<*>, + override val operator: ComparisonOperatorName +) : BinaryConditionComponent + +class BinaryLogicalConditionComponent( + override val left: ConditionComponent, + override val right: ConditionComponent, + override val operator: BinaryLogicalOperatorName +) : BinaryConditionComponent class UnaryConditionComponent( - val conditionComponent: ConditionComponent, + val conditionNode: ConditionNode, val operator: UnaryOperatorName ) : ConditionComponent class CallConditionComponent(val op: Op) : ConditionComponent -class ContainsConditionComponent(val item: DataItem, val collection: Collection) : ConditionComponent +class ContainsConditionComponent(val item: DataItem, val collection: Collection) : ConditionComponent + +sealed interface BinaryOperatorName { + val operatorCodes: List +} -enum class BinaryOperatorName(val operatorCodes: List) { +enum class BinaryLogicalOperatorName(override val operatorCodes: List) : BinaryOperatorName { + AND(listOf("&&", "and")), + OR(listOf("||", "or")) +} +enum class ComparisonOperatorName(override val operatorCodes: List) : BinaryOperatorName { GEQ(listOf(">=")), GT(listOf(">")), LEQ(listOf("<=")), LT(listOf("<")), EQ(listOf("==")), // TODO: python `==` vs `is` NEQ(listOf("!=")), - AND(listOf("&&", "and")), - OR(listOf("||", "or")) } enum class UnaryOperatorName(val operatorCodes: List) { NOT(listOf("!", "not")) } - -// idea: -// isChecked(doStuff(Wildcard).returnValue) { -// (it lessThan 0) or (it greaterThan 1) -// } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt index fc5c06074..1402c1b18 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt @@ -19,25 +19,26 @@ package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.TransformationResult -sealed interface DataItem : ConditionComponent +sealed interface DataItem : ConditionLeaf { + val transformation: (BackendDataItem) -> TransformationResult +} -sealed interface HasTransformation : DataItem { - val transformation: (BackendDataItem) -> TransformationResult +sealed interface CanChangeTransformation : DataItem { infix fun withTransformation( - newTransformation: (BackendDataItem) -> TransformationResult - ): HasTransformation + newTransformation: (BackendDataItem) -> TransformationResult + ): DataItem } data class ReturnValueItem( val op: Op, - override val transformation: (BackendDataItem) -> TransformationResult = { + override val transformation: (BackendDataItem) -> TransformationResult = { TransformationResult.failure("No transformation given from user") } -) : HasTransformation { +) : CanChangeTransformation { override fun toString(): String = "Return value of $op" override infix fun withTransformation( - newTransformation: (BackendDataItem) -> TransformationResult + newTransformation: (BackendDataItem) -> TransformationResult ): ReturnValueItem = ReturnValueItem(op = op, transformation = newTransformation) } @@ -45,16 +46,16 @@ data class ReturnValueItem( data class ArgumentItem( val op: Op, val number: Int, - override val transformation: (BackendDataItem) -> TransformationResult = { + override val transformation: (BackendDataItem) -> TransformationResult = { TransformationResult.failure("No transformation given from user") } -) : HasTransformation { +) : CanChangeTransformation { init { require(number >= 0) { "The number of the argument cannot be negative" } } override fun withTransformation( - newTransformation: (BackendDataItem) -> TransformationResult + newTransformation: (BackendDataItem) -> TransformationResult ): ArgumentItem = ArgumentItem(op = op, number = number, transformation = newTransformation) @@ -72,10 +73,10 @@ data class ArgumentItem( } } -data class Value internal constructor(val value: Any) : DataItem +data class Value internal constructor(val value: E) : DataItem { + override val transformation: (BackendDataItem) -> TransformationResult + get() = { TransformationResult.success(value) } +} -fun value(value: Any): DataItem = - when (value) { - is DataItem -> value - else -> Value(value) - } +fun value(value: DataItem): DataItem = value +fun value(value: E): DataItem = Value(value) From 0670594e17c2c34f58ff63e914d25b7eca403c9b Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 4 Sep 2023 14:17:24 +0200 Subject: [PATCH 11/25] start implementing the WheneverEvaluator for the cpg backend --- .../backends/cpg/coko/CokoCpgBackend.kt | 17 ++- .../coko/evaluators/CpgWheneverEvaluator.kt | 125 ++++++++++++++++++ .../cpg/coko/modelling/CpgBackendDataItem.kt | 37 ++++++ 3 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt create mode 100644 codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt 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 a49fb370b..a9533e0a8 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 @@ -18,15 +18,15 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko import de.fraunhofer.aisec.codyze.backends.cpg.CPGBackend import de.fraunhofer.aisec.codyze.backends.cpg.CPGConfiguration import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.* -import de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators.FollowsEvaluator -import de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators.NeverEvaluator -import de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators.OnlyEvaluator -import de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators.OrderEvaluator +import de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators.* import de.fraunhofer.aisec.codyze.core.VersionProvider import de.fraunhofer.aisec.codyze.core.backend.BackendConfiguration import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.WheneverEvaluator +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Condition import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Order +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ConditionComponent import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.OrderToken import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.getOp import de.fraunhofer.aisec.cpg.graph.Node @@ -86,4 +86,13 @@ class CokoCpgBackend(config: BackendConfiguration) : */ override fun only(vararg ops: Op): OnlyEvaluator = OnlyEvaluator(ops.toList()) override fun never(vararg ops: Op): NeverEvaluator = NeverEvaluator(ops.toList()) + override fun whenever( + premise: Condition.() -> ConditionComponent, + assertionBlock: WheneverEvaluator.() -> Unit + ): WheneverEvaluator = CpgWheneverEvaluator(Condition().premise()).apply(assertionBlock) + + override fun whenever( + premise: ConditionComponent, + assertionBlock: WheneverEvaluator.() -> Unit + ): WheneverEvaluator = CpgWheneverEvaluator(premise).apply(assertionBlock) } 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 new file mode 100644 index 000000000..4b7d3cfdf --- /dev/null +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt @@ -0,0 +1,125 @@ +/* + * 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.Nodes +import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes +import de.fraunhofer.aisec.codyze.backends.cpg.coko.modelling.toBackendDataItem +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.WheneverEvaluator +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* +import de.fraunhofer.aisec.cpg.graph.Node + +context(de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend) +class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(premise) { + override fun evaluate(context: EvaluationContext): Collection { + val (premiseNodes, premiseProblems) = evaluateConditionNodeToCpgNodes(premise) + + + + + TODO("Not yet implemented") + } + + private fun evaluateConditionNodeToCpgNodes(conditionNode: ConditionNode): Pair { + return when (conditionNode) { + is ConditionComponent -> evaluateConditionComponentToCpgNodes(conditionNode) + is ArgumentItem<*> -> TODO() + is ReturnValueItem<*> -> TODO() + is Value<*> -> evaluateValue(conditionNode) + } + + } + + private fun evaluateValue(value: Value<*>): Pair { + return listOf(DummyNode()) to Problems() + } + + private fun evaluateConditionComponentToCpgNodes(conditionComponent: ConditionComponent): Pair { + return when (conditionComponent) { + is BinaryLogicalConditionComponent -> evaluateBinaryLogicalConditionComponent(conditionComponent) + is ComparisonConditionComponent -> evaluateComparisonConditionComponent(conditionComponent) + is UnaryConditionComponent -> TODO() + is CallConditionComponent -> evaluateCallConditionComponent(conditionComponent) + is ContainsConditionComponent<*, *> -> TODO() + } + } + + private fun evaluateCallConditionComponent(callConditionComponent: CallConditionComponent): Pair { + return callConditionComponent.op.cpgGetNodes() to Problems() + } + + private fun evaluateBinaryLogicalConditionComponent( + binaryLogicalConditionComponent: BinaryLogicalConditionComponent + ): Pair { + val (leftNodes, leftProblems) = evaluateConditionComponentToCpgNodes(binaryLogicalConditionComponent.left) + val (rightNodes, rightProblems) = evaluateConditionComponentToCpgNodes(binaryLogicalConditionComponent.right) + return when (binaryLogicalConditionComponent.operator) { + BinaryLogicalOperatorName.AND -> (leftNodes intersect rightNodes) to (leftProblems + rightProblems) + BinaryLogicalOperatorName.OR -> (leftNodes union rightNodes) to (leftProblems + rightProblems) + } + } + + private fun evaluateComparisonConditionComponent( + comparisonConditionComponent: ComparisonConditionComponent + ): Pair { + val (leftNodes, leftProblems) = evaluateConditionNodeToCpgNodes(comparisonConditionComponent.left) + val (rightNodes, rightProblems) = evaluateConditionNodeToCpgNodes(comparisonConditionComponent.right) + + val leftNodesToValues = leftNodes.associateWith { + comparisonConditionComponent.left.transformation( + it.toBackendDataItem() + ) + } + val rightNodesToValues = rightNodes.associateWith { + comparisonConditionComponent.right.transformation( + it.toBackendDataItem() + ) + } + + return when (comparisonConditionComponent.operator) { + ComparisonOperatorName.GEQ -> TODO() + ComparisonOperatorName.GT -> TODO() + ComparisonOperatorName.LEQ -> TODO() + ComparisonOperatorName.LT -> TODO() + ComparisonOperatorName.EQ -> + (leftNodesToValues + .filter { (_, value) -> rightNodesToValues.containsValue(value) } + .keys) to (leftProblems + rightProblems) + ComparisonOperatorName.NEQ -> + (leftNodesToValues + .filterNot { (_, value) -> rightNodesToValues.containsValue(value) } + .keys) to (leftProblems + rightProblems) + } + } + + class Problems { + val map: MutableMap = mutableMapOf() + + fun add(reason: String, value: Any) { + map[value] = reason + } + + operator fun plus(other: Problems): Problems { + map.putAll(other.map) + return this + } + } + + class DummyNode: Node() +} diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt new file mode 100644 index 000000000..d45638eb0 --- /dev/null +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt @@ -0,0 +1,37 @@ +/* + * 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.codyze.backends.cpg.coko.modelling + +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.BackendDataItem +import de.fraunhofer.aisec.cpg.graph.HasType +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.evaluate +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression + +class CpgBackendDataItem( + override val name: String?, + override val value: Any?, + override val type: String? +) : BackendDataItem + +fun Node.toBackendDataItem(): CpgBackendDataItem { + val value = (this as? Expression)?.evaluate() ?: (this as? Declaration)?.evaluate() + val type = (this as? HasType)?.type?.typeName + + return CpgBackendDataItem(name = name.toString(), value = value, type = type) +} From c9d8cd11d69c42feaa72af0e4797ea73c238e423 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 4 Sep 2023 14:18:33 +0200 Subject: [PATCH 12/25] add new type of Op that represents a dependency between two Ops --- .../cpg/coko/dsl/ImplementationDsl.kt | 24 ++++++++++++++++++- .../coko/core/dsl/Op.kt | 16 +++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) 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 a7d8919a6..5f3e781d4 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 @@ -43,6 +43,15 @@ fun Op.cpgGetAllNodes(): Nodes = this@Op.definitions.flatMap { def -> this@CokoBackend.cpgCallFqn(def.fqn) } is ConstructorOp -> this@CokoBackend.cpgConstructor(this.classFqn) is GroupingOp -> this@Op.ops.flatMap { it.cpgGetAllNodes() } + is ConditionalOp -> { + val resultNodes = resultOp.cpgGetAllNodes() + val conditionNodes = conditionOp.cpgGetAllNodes() + resultNodes.filter { resultNode -> + conditionNodes.any { conditionNode -> + dataFlow(conditionNode, resultNode).value + } + } + } } /** @@ -71,6 +80,16 @@ fun Op.cpgGetNodes(): Nodes = } } is GroupingOp -> this@Op.ops.flatMap { it.cpgGetNodes() } + is ConditionalOp -> { + val resultNodes = resultOp.cpgGetNodes() + val conditionNodes = conditionOp.cpgGetNodes() + resultNodes.filter { resultNode -> + conditionNodes.any { conditionNode -> + val result = dataFlow(conditionNode, resultNode) // TODO + result.value + } + } + } } /** Returns a list of [ValueDeclaration]s with the matching name. */ @@ -143,7 +162,10 @@ infix fun Any.cpgFlowsTo(that: Collection): Boolean = true } else { when (this) { - is String -> that.any { Regex(this).matches((it as? Expression)?.evaluate()?.toString().orEmpty()) } + 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 } is Node -> that.any { dataFlow(this, it).value } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt index 38fe817a3..cfeedfa24 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt @@ -124,6 +124,15 @@ data class GroupingOp(val ops: Set, override val ownerClassFqn: String = "") } } +/** + * An [Op] that describes that the function calls found with [resultOp] depend on the function calls found with the [conditionOp] + */ +data class ConditionalOp(val resultOp: Op, val conditionOp: Op, override val ownerClassFqn: String = "") : Op { + override fun toString(): String { + return "$resultOp with $conditionOp" + } +} + context(Any) // This is needed to have access to the owner of this function // -> in which class is the function defined that created this [OP] /** @@ -173,9 +182,16 @@ fun constructor(classFqn: String, block: ConstructorOp.() -> Unit): ConstructorO return ConstructorOp(classFqn, this@Any::class.java.name).apply(block) } +/** Create a [GroupingOp] containing the given [ops]. */ context(Any) fun opGroup(vararg ops: Op): GroupingOp = GroupingOp(ops.toSet(), this@Any::class.java.name) +/** + * Create a [ConditionalOp] with [this] as the [ConditionalOp.resultOp] and [conditionOp] as the [ConditionalOp.conditionOp] + */ +context(Any) +infix fun Op.with(conditionOp: Op): ConditionalOp = ConditionalOp(this, conditionOp, this@Any::class.java.name) + /** * Create a [Definition] which can be added to the [FunctionOp]. * From 06ef4f1a5ab4a129f5720d88703b25a4602e5223 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 4 Sep 2023 14:21:28 +0200 Subject: [PATCH 13/25] add a implementation of the bsi-tr-rules interfaces for jca and a test java file --- .../resources/concept/CipherTestFile.java | 39 ++++++++ .../resources/concept/bsi-tr-rules.codyze.kts | 25 +++--- .../resources/concept/jca-cipher.codyze.kts | 88 +++++++++++++++++++ 3 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/CipherTestFile.java create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/jca-cipher.codyze.kts diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/CipherTestFile.java b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/CipherTestFile.java new file mode 100644 index 000000000..41e212c97 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/CipherTestFile.java @@ -0,0 +1,39 @@ +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +public class CipherTestFile { + + public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeySpecException, InvalidKeyException, InvalidAlgorithmParameterException { + Security.addProvider(new BouncyCastleProvider()); + + String transform = "AES/CBC/NoPadding"; + Cipher c = Cipher.getInstance(transform, "BC"); + + SecureRandom sr = new SecureRandom(); + byte[] iv = sr.generateSeed(16); + IvParameterSpec ivParamSpec = new IvParameterSpec(iv); + + byte[] rawKey = sr.generateSeed(32); + SecretKeyFactory skf = SecretKeyFactory.getInstance("AES"); + Key k = skf.generateSecret(new SecretKeySpec(rawKey, "AES")); + + c.init(Cipher.ENCRYPT_MODE, k, ivParamSpec); + c.doFinal(); + System.out.println(c); + } + +} diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts index a0676876b..60a2cb51d 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts @@ -1,12 +1,13 @@ -@file:Import("bsi-tr.concepts") +//@file:Import("bsi-tr.concepts") -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.HasTransformation +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.value enum class Algorithm { AES, DES, TripleDES } -enum class Mode{ +enum class Mode { // Cipher Block Chaining Message Authentication CCM, // Galois/Counter Mode @@ -18,10 +19,10 @@ enum class Mode{ } interface Cypher { - val algo: HasTransformation - val mode: HasTransformation - val keySize: HasTransformation // in bits - val tagSize: HasTransformation // in bits; only applicable for mode == CCM + val algo: DataItem + val mode: DataItem + val keySize: DataItem // in bits + val tagSize: DataItem // in bits; only applicable for mode == CCM } interface InitializationVector { @@ -37,11 +38,11 @@ interface Encryption { } -@RuleSet -class GoodCrypto { +//@RuleSet +//class GoodCrypto { @Rule fun `must be AES`(enc: Encryption) = - whenever(enc.encrypt(Wildcard)) { + whenever({ call(enc.encrypt(Wildcard)) }) { ensure { enc.cypher.algo eq Algorithm.AES } ensure { enc.cypher.mode within list[Mode.CCM, Mode.GCM, Mode.CTR] } ensure { enc.cypher.keySize within list[128, 192, 256]} @@ -49,8 +50,8 @@ class GoodCrypto { @Rule fun `modes of operation`(enc: Encryption) = - whenever({ enc.encrypt(Wildcard) and (enc.cypher.mode eq Mode.CCM) }) { + whenever({ call(enc.encrypt(Wildcard)) and (enc.cypher.mode eq Mode.CCM) }) { ensure { enc.cypher.tagSize geq 96 } } -} +//} diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/jca-cipher.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/jca-cipher.codyze.kts new file mode 100644 index 000000000..b9b8bb179 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/jca-cipher.codyze.kts @@ -0,0 +1,88 @@ +@file:Import("bsi-tr-rules.codyze.kts") + +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.value + +class JCACipher { + fun getInstance(transformation: Any, provider: Any? = null) = op { + "javax.crypto.Cipher.getInstance" { + signature(transformation) + signature(transformation, provider) + } + } + + fun init(opmode: Any, certificate: Any?, random: Any?, key: Any?, params: Any?) = op { + "javax.crypto.Cipher.init" { + signature(opmode, certificate) + signature(opmode, certificate, random) + signature(opmode, key) + signature(opmode, key, params) + signature(opmode, key, params, random) + signature(opmode, key, random) + + } + } + + fun doFinal(input: Any?, output: Any?, outputOffset: Any?, inputOffset: Any?, inputLen: Any?) = op { + "javax.crypto.Cipher.doFinal" { + signature() + signature(input) + signature(output, outputOffset) + signature(input, inputOffset, inputLen) + signature(input, inputOffset, inputLen, output) + signature(input, inputOffset, inputLen, output, outputOffset) + signature(input, output) + } + } +} + +class JCAEncryptionImpl: Bsi_tr_rules_codyze.Encryption { + private val jcaCipher = JCACipher() + override val cypher: Bsi_tr_rules_codyze.Cypher + get() = JCACipherImpl() + override val iv: Bsi_tr_rules_codyze.InitializationVector + get() = TODO("Not yet implemented") + + override fun encrypt(plaintext: Any?): Op = + jcaCipher.doFinal(plaintext, Wildcard, Wildcard, Wildcard, Wildcard) with + jcaCipher.init(".*ENCRYPT_MODE", Wildcard, Wildcard, Wildcard, Wildcard) + + override fun decrypt(ciphertext: Any?): Op { + TODO("Not yet implemented") + } + +} + +// "AES/CBC/Padding" +class JCACipherImpl: Bsi_tr_rules_codyze.Cypher { + val cipher = JCACipher() + + override val algo: DataItem + get() = cipher.getInstance(Wildcard, Wildcard).arguments[1] withTransformation { dataItem -> + val stringValue = dataItem.value as? String + if(dataItem.value == null) + TransformationResult.failure("Value of BackendDataItem is null.") + else if(stringValue == null) { + TransformationResult.failure("Value of BackendDataItem is not a string.") + } + else { + val (algo, _) = stringValue.partition { it == '/' } + val algoEnum = Bsi_tr_rules_codyze.Algorithm.values().firstOrNull { it.name == algo.uppercase() } + if(algoEnum == null) + TransformationResult.failure("Could not resolve the string value to an algorithm") + else + TransformationResult.success(algoEnum) + } + } + override val mode: DataItem + get() = value(Bsi_tr_rules_codyze.Mode.CBC) + override val keySize: DataItem + get() = value(1) + override val tagSize: DataItem + get() = value(1) + + +} + + + From fbe5bed1510ff26eab0160e44a8e2511be839241 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 8 Sep 2023 15:25:53 +0200 Subject: [PATCH 14/25] formatting --- .../specificationLanguages/coko/core/dsl/Condition.kt | 2 +- .../coko/core/dsl/TransformationResult.kt | 8 ++++---- .../coko/core/modelling/DataItem.kt | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt index 6f06733f9..ca428fdb2 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt @@ -68,7 +68,7 @@ class Condition { fun not(conditionComponent: ConditionComponent) = UnaryConditionComponent(conditionComponent, UnaryOperatorName.NOT) private fun toValue(value: Any): DataItem<*> = - when(value) { + when (value) { is DataItem<*> -> value else -> Value(value) } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt index da6773ced..83b371580 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt @@ -31,17 +31,17 @@ sealed interface TransformationResult { companion object { /** Creates a [TransformationFailure] object with the given [reason]. */ - fun failure(reason: T): TransformationResult = TransformationFailure(reason) + fun failure(reason: T): TransformationResult = TransformationFailure(reason) /** Creates a [TransformationSuccess] object with the given [value]. */ - fun success(value: E): TransformationResult = TransformationSuccess(value) + fun success(value: E): TransformationResult = TransformationSuccess(value) } } /** * This class indicates that the operation was a success and the result is stored in [value]. */ -private data class TransformationSuccess(val value: E) : TransformationResult { +private data class TransformationSuccess(val value: E) : TransformationResult { override val isFailure: Boolean get() = false override val isSuccess: Boolean @@ -55,7 +55,7 @@ private data class TransformationSuccess(val value: E) : TransformationResu /** * This class indicates that the operation was not successful and the reasons are stored in [reason]. */ -private data class TransformationFailure(val reason: T) : TransformationResult { +private data class TransformationFailure(val reason: T) : TransformationResult { override val isFailure: Boolean get() = true override val isSuccess: Boolean diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt index 1402c1b18..9f9ee8fdf 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt @@ -45,21 +45,22 @@ data class ReturnValueItem( data class ArgumentItem( val op: Op, - val number: Int, + val index: Int, override val transformation: (BackendDataItem) -> TransformationResult = { TransformationResult.failure("No transformation given from user") } ) : CanChangeTransformation { init { - require(number >= 0) { "The number of the argument cannot be negative" } + // TODO: Do we count starting at 0 or 1? + require(index >= 0) { "The number of the argument cannot be negative" } } override fun withTransformation( newTransformation: (BackendDataItem) -> TransformationResult ): ArgumentItem = - ArgumentItem(op = op, number = number, transformation = newTransformation) + ArgumentItem(op = op, index = index, transformation = newTransformation) - override fun toString(): String = "${number.ordinal()} argument of $op" + override fun toString(): String = "${(index + 1).ordinal()} argument of $op" @Suppress("MagicNumber") fun Int.ordinal(): String { From 06600c9eb97a290ec151664cee4c180002c99eea Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 8 Sep 2023 15:21:24 +0200 Subject: [PATCH 15/25] finish implementing cpgGetNodes for DataItems and change the return type for cpgGetNodes for Ops --- .../aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt | 4 ++-- .../codyze/backends/cpg/coko/dsl/ImplementationDsl.kt | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) 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 55f6b66d1..3c04594d6 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 @@ -34,7 +34,7 @@ fun DataItem<*>.cpgGetAllNodes(): Nodes = when (this@DataItem) { is ReturnValueItem -> op.cpgGetAllNodes().flatMap { it.getVariableInNextDFGOrThis() } is Value -> this@DataItem.getNodes() - is ArgumentItem -> TODO() + is ArgumentItem -> op.cpgGetAllNodes().map { it.arguments[index] } // TODO: Do we count starting at 0 or 1? } /** @@ -45,7 +45,7 @@ fun DataItem<*>.cpgGetNodes(): Nodes { return when (this@DataItem) { is ReturnValueItem -> op.cpgGetNodes().flatMap { it.getVariableInNextDFGOrThis() } is Value -> this@DataItem.getNodes() - is ArgumentItem -> TODO() + is ArgumentItem -> op.cpgGetNodes().map { it.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 5f3e781d4..9de960be9 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 @@ -27,6 +27,7 @@ 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 // // all functions/properties defined here must use CokoBackend @@ -37,7 +38,7 @@ val CokoBackend.cpg: TranslationResult /** Get all [Nodes] that are associated with this [Op]. */ context(CokoBackend) -fun Op.cpgGetAllNodes(): Nodes = +fun Op.cpgGetAllNodes(): Collection = when (this@Op) { is FunctionOp -> this@Op.definitions.flatMap { def -> this@CokoBackend.cpgCallFqn(def.fqn) } @@ -59,7 +60,7 @@ fun Op.cpgGetAllNodes(): Nodes = * [Definition]s. */ context(CokoBackend) -fun Op.cpgGetNodes(): Nodes = +fun Op.cpgGetNodes(): Collection = when (this@Op) { is FunctionOp -> this@Op.definitions @@ -85,7 +86,8 @@ fun Op.cpgGetNodes(): Nodes = val conditionNodes = conditionOp.cpgGetNodes() resultNodes.filter { resultNode -> conditionNodes.any { conditionNode -> - val result = dataFlow(conditionNode, resultNode) // TODO + // TODO: Is it correct to use the EOG relationship here? + val result = executionPath(conditionNode, resultNode) result.value } } From 32fd140aaaa41b9380659366c76f876582d3edbe Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 8 Sep 2023 15:23:46 +0200 Subject: [PATCH 16/25] change CallAssertion to inherit from CallConditionComponent --- .../coko/core/WheneverEvaluator.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt index 12b037d92..b12a181be 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt @@ -18,6 +18,7 @@ package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Condition import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.CallConditionComponent import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ConditionComponent abstract class WheneverEvaluator(protected val premise: ConditionComponent) : Evaluator { @@ -46,7 +47,13 @@ class CallLocationBuilder { } data class CallLocation(val direction: Direction, val scope: Scope) -data class CallAssertion(val op: Op, val location: CallLocation? = null) +class CallAssertion(op: Op, val location: CallLocation? = null): CallConditionComponent(op) { + override fun toString(): String = + "Call $op ${ + if(location != null) "${location.direction} in ${location.scope}." + else "" + }" +} class ListBuilder { operator fun get(vararg things: E): List = things.toList() From e759cd6967f39b06bf5fa0c9c9505bb99d4487a1 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 8 Sep 2023 15:24:43 +0200 Subject: [PATCH 17/25] implement toString functions for ConditionComponents --- .../core/modelling/ConditionComponents.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt index 4901b074c..4f2cc8503 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt @@ -18,7 +18,9 @@ package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op -sealed interface ConditionNode +sealed interface ConditionNode { + override fun toString(): String +} sealed interface ConditionComponent : ConditionNode sealed interface ConditionLeaf : ConditionNode @@ -32,22 +34,32 @@ class ComparisonConditionComponent( override val left: DataItem<*>, override val right: DataItem<*>, override val operator: ComparisonOperatorName -) : BinaryConditionComponent +) : BinaryConditionComponent { + override fun toString(): String = "$left ${operator.name} $right" +} class BinaryLogicalConditionComponent( override val left: ConditionComponent, override val right: ConditionComponent, override val operator: BinaryLogicalOperatorName -) : BinaryConditionComponent +) : BinaryConditionComponent { + override fun toString(): String = "$left ${operator.name} $right" +} class UnaryConditionComponent( val conditionNode: ConditionNode, val operator: UnaryOperatorName -) : ConditionComponent +) : ConditionComponent { + override fun toString(): String = "${operator.name} $conditionNode" +} -class CallConditionComponent(val op: Op) : ConditionComponent +open class CallConditionComponent(val op: Op) : ConditionComponent { + override fun toString(): String = op.toString() +} -class ContainsConditionComponent(val item: DataItem, val collection: Collection) : ConditionComponent +class ContainsConditionComponent(val item: DataItem, val collection: Collection) : ConditionComponent { + override fun toString(): String = "$item within $collection" +} sealed interface BinaryOperatorName { val operatorCodes: List From af9a6c79d1ab2474155ed038f9130201103cb488 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 8 Sep 2023 15:31:12 +0200 Subject: [PATCH 18/25] implement some parts of the CpgWheneverEvaluator --- .../coko/evaluators/CpgWheneverEvaluator.kt | 330 +++++++++++++++--- 1 file changed, 279 insertions(+), 51 deletions(-) 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 4b7d3cfdf..bd72c511d 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 @@ -16,59 +16,185 @@ package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators +import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding import de.fraunhofer.aisec.codyze.backends.cpg.coko.Nodes import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.cpgGetNodes import de.fraunhofer.aisec.codyze.backends.cpg.coko.modelling.toBackendDataItem -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.WheneverEvaluator +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.* +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Rule +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.TransformationResult import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.followNextEOG +import de.fraunhofer.aisec.cpg.graph.followPrevEOG +import de.fraunhofer.aisec.cpg.query.executionPath +import kotlin.reflect.full.findAnnotation context(de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend) class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(premise) { + + private val defaultFailMessage: String = "" + + private val defaultPassMessage: String = "" override fun evaluate(context: EvaluationContext): Collection { - val (premiseNodes, premiseProblems) = evaluateConditionNodeToCpgNodes(premise) + val ruleAnnotation = context.rule.findAnnotation() + val failMessage = ruleAnnotation?.failMessage?.takeIf { it.isNotEmpty() } ?: defaultFailMessage + val passMessage = ruleAnnotation?.passMessage?.takeIf { it.isNotEmpty() } ?: defaultPassMessage + val findings = mutableListOf() + val (premiseNodes, premiseProblems) = evaluateConditionNodeToCpgNodes(premise) + if (premiseNodes.isEmpty()) { + if (premiseProblems.isEmpty()) { + findings.add( + CpgFinding( + message = "$premise was not found in code.", + kind = Finding.Kind.NotApplicable, + ) + ) + } else { + findings.add( + CpgFinding( + message = "$premise could not be resolved correctly. $premiseProblems", + kind = Finding.Kind.Open, - TODO("Not yet implemented") + ) + ) + } + } else { + for (premiseNode in premiseNodes) { + val ensuresToNodes = ensures.associateWith { ensure -> + val (ensureNodes, problems) = evaluateConditionComponentToCpgNodes(ensure, premiseNode) + ensureNodes.filterNot { it is DummyNode } to problems + } + val callAssertionsToNodes = callAssertions.associateWith { callAssertion -> + val nodes = callAssertion.op.cpgGetNodes() + val location = callAssertion.location + if (location != null) { + when (location.scope) { + Scope.Function -> { + when (location.direction) { + Direction.afterwards -> TODO() + Direction.before -> TODO() + Direction.somewhere -> TODO() + } + } + Scope.Block -> TODO() + } + } + nodes.toList() to Problems() + } + findings.addAll( + generateFindings(premiseNode, ensuresToNodes + callAssertionsToNodes, passMessage, failMessage) + ) + } + } + + return findings } - private fun evaluateConditionNodeToCpgNodes(conditionNode: ConditionNode): Pair { - return when (conditionNode) { - is ConditionComponent -> evaluateConditionComponentToCpgNodes(conditionNode) - is ArgumentItem<*> -> TODO() - is ReturnValueItem<*> -> TODO() - is Value<*> -> evaluateValue(conditionNode) + private fun generateFindings( + premiseNode: Node, + conditionToNodes: Map, Problems>>, + passMessage: String, + failMessage: String + ): Collection { + val findings = mutableListOf() + + val unfulfilledConditions = conditionToNodes.filterValues { (nodes, _) -> nodes.isEmpty() } + + if (unfulfilledConditions.isNotEmpty()) { + for ((condition, nodesToProblems) in unfulfilledConditions) { + val (_, problems) = nodesToProblems + if (problems.isEmpty()) { + findings.add( + CpgFinding( + message = "No $condition found around $premise. $failMessage", + kind = Finding.Kind.Fail, + node = premiseNode + ) + ) + TODO("Better message") + } else { + findings.add( + CpgFinding( + message = "There were problems in resolving $condition. $problems", + kind = Finding.Kind.Open, + node = premiseNode, + ) + ) + } + } + } else { + findings.add( + CpgFinding( + message = passMessage, + kind = Finding.Kind.Pass, + node = premiseNode, + relatedNodes = conditionToNodes.values.flatMap { it.first } + ) + ) } + return findings } - private fun evaluateValue(value: Value<*>): Pair { + private fun evaluateConditionNodeToCpgNodes( + conditionNode: ConditionNode, + premiseNode: Node? = null + ): Pair { + TODO("change to EvaluationResult") + return when (conditionNode) { + is ConditionComponent -> evaluateConditionComponentToCpgNodes(conditionNode, premiseNode) + is ArgumentItem<*> -> evaluateArgumentItemToCpgNodes(conditionNode, premiseNode) + is ReturnValueItem<*> -> evaluateReturnValueItemToCpgNodes(conditionNode, premiseNode) + is Value<*> -> evaluateValueToCpgNodes(conditionNode, premiseNode) + } + } + + private fun evaluateValueToCpgNodes(value: Value<*>, premiseNode: Node? = null): Pair { return listOf(DummyNode()) to Problems() } - private fun evaluateConditionComponentToCpgNodes(conditionComponent: ConditionComponent): Pair { + private fun evaluateArgumentItemToCpgNodes( + argumentItem: ArgumentItem<*>, + premiseNode: Node? = null + ): Pair { + val nodes = argumentItem.cpgGetNodes().filterWithDistanceToPremise(premiseNode) + return nodes to Problems() + } + + private fun evaluateReturnValueItemToCpgNodes( + returnValueItem: ReturnValueItem<*>, + premiseNode: Node? = null + ): Pair { + val nodes = returnValueItem.cpgGetNodes().filterWithDistanceToPremise(premiseNode) + return nodes to Problems() + } + + private fun evaluateConditionComponentToCpgNodes( + conditionComponent: ConditionComponent, + premiseNode: Node? = null + ): Pair { return when (conditionComponent) { - is BinaryLogicalConditionComponent -> evaluateBinaryLogicalConditionComponent(conditionComponent) - is ComparisonConditionComponent -> evaluateComparisonConditionComponent(conditionComponent) + is BinaryLogicalConditionComponent -> + evaluateBinaryLogicalConditionComponent(conditionComponent, premiseNode) + is ComparisonConditionComponent -> evaluateComparisonConditionComponent(conditionComponent, premiseNode) is UnaryConditionComponent -> TODO() - is CallConditionComponent -> evaluateCallConditionComponent(conditionComponent) - is ContainsConditionComponent<*, *> -> TODO() + is CallConditionComponent -> evaluateCallConditionComponent(conditionComponent, premiseNode) + is ContainsConditionComponent<*, *> -> evaluateContainsConditionComponent(conditionComponent, premiseNode) } } - private fun evaluateCallConditionComponent(callConditionComponent: CallConditionComponent): Pair { - return callConditionComponent.op.cpgGetNodes() to Problems() - } - private fun evaluateBinaryLogicalConditionComponent( - binaryLogicalConditionComponent: BinaryLogicalConditionComponent + binaryLogicalConditionComponent: BinaryLogicalConditionComponent, + premiseNode: Node? = null ): Pair { - val (leftNodes, leftProblems) = evaluateConditionComponentToCpgNodes(binaryLogicalConditionComponent.left) - val (rightNodes, rightProblems) = evaluateConditionComponentToCpgNodes(binaryLogicalConditionComponent.right) + val (leftNodes, leftProblems) = + evaluateConditionComponentToCpgNodes(binaryLogicalConditionComponent.left, premiseNode) + val (rightNodes, rightProblems) = + evaluateConditionComponentToCpgNodes(binaryLogicalConditionComponent.right, premiseNode) return when (binaryLogicalConditionComponent.operator) { BinaryLogicalOperatorName.AND -> (leftNodes intersect rightNodes) to (leftProblems + rightProblems) BinaryLogicalOperatorName.OR -> (leftNodes union rightNodes) to (leftProblems + rightProblems) @@ -76,50 +202,152 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } private fun evaluateComparisonConditionComponent( - comparisonConditionComponent: ComparisonConditionComponent + comparisonConditionComponent: ComparisonConditionComponent, + premiseNode: Node? = null ): Pair { - val (leftNodes, leftProblems) = evaluateConditionNodeToCpgNodes(comparisonConditionComponent.left) - val (rightNodes, rightProblems) = evaluateConditionNodeToCpgNodes(comparisonConditionComponent.right) + val (leftNodes, leftProblems) = + evaluateConditionNodeToCpgNodes(comparisonConditionComponent.left, premiseNode) + val (rightNodes, rightProblems) = + evaluateConditionNodeToCpgNodes(comparisonConditionComponent.right, premiseNode) - val leftNodesToValues = leftNodes.associateWith { - comparisonConditionComponent.left.transformation( - it.toBackendDataItem() - ) + val newProblems = Problems() + val result = mutableSetOf() + + val leftNodesToValues = + leftNodes.toNodesToValues(newProblems, comparisonConditionComponent.left.transformation) + val rightNodesToValues = + rightNodes.toNodesToValues(newProblems, comparisonConditionComponent.right.transformation) + + for ((leftNode, leftValue) in leftNodesToValues) { + for ((rightNode, rightValue) in rightNodesToValues) + when (comparisonConditionComponent.operator) { + ComparisonOperatorName.GEQ -> TODO() + ComparisonOperatorName.GT -> TODO() + ComparisonOperatorName.LEQ -> TODO() + ComparisonOperatorName.LT -> TODO() + ComparisonOperatorName.EQ -> + if (leftValue == rightValue) { + result.add(leftNode) + result.add(rightNode) + } + ComparisonOperatorName.NEQ -> TODO() + } } - val rightNodesToValues = rightNodes.associateWith { - comparisonConditionComponent.right.transformation( - it.toBackendDataItem() - ) + + return result to (leftProblems + rightProblems + newProblems) + } + + private fun evaluateCallConditionComponent( + callConditionComponent: CallConditionComponent, + premiseNode: Node? = null + ): Pair { + val callNodes = callConditionComponent.op.cpgGetNodes().filterWithDistanceToPremise(premiseNode) + return callNodes to Problems() + } + + private fun evaluateContainsConditionComponent( + containsConditionComponent: ContainsConditionComponent<*, *>, + premiseNode: Node? + ): Pair { + val problems = Problems() + val nodes = containsConditionComponent.item.cpgGetNodes().filterWithDistanceToPremise(premiseNode) + val resultNodes = nodes.filter { node -> + val backendDataItem = node.toBackendDataItem() + val transformationResult = containsConditionComponent.item.transformation(backendDataItem) + if (transformationResult.isFailure) { + problems.add( + transformationResult.getFailureReasonOrNull() ?: "Transformation of $backendDataItem failed", + backendDataItem + ) + false + } else { + val value = transformationResult.getValueOrNull() + containsConditionComponent.collection.contains(value) + } + } + + return resultNodes to Problems() + } + + private fun Nodes.toNodesToValues( + problems: Problems, + transformation: (BackendDataItem) -> TransformationResult + ): Map { + val result = mutableMapOf() + + for (node in this) { + val backendDataItem = node.toBackendDataItem() + val transformationResult = transformation(backendDataItem) + val value = transformationResult.getValueOrNull() + if (value == null) { + problems.add(transformationResult.getFailureReasonOrNull() ?: "Value was null", backendDataItem) + } else { + result[node] = value + } + } + + return result + } + + private fun Nodes.filterWithDistanceToPremise(premiseNode: Node?): Nodes { + return if (premiseNode != null) { + this.filter { executionPath(it, premiseNode).value } + } else { + this + } + } + + private fun Node.eogDistance(other: Node): Int { + var to = 0 + this.followNextEOG { + to++ + it.end == other } - return when (comparisonConditionComponent.operator) { - ComparisonOperatorName.GEQ -> TODO() - ComparisonOperatorName.GT -> TODO() - ComparisonOperatorName.LEQ -> TODO() - ComparisonOperatorName.LT -> TODO() - ComparisonOperatorName.EQ -> - (leftNodesToValues - .filter { (_, value) -> rightNodesToValues.containsValue(value) } - .keys) to (leftProblems + rightProblems) - ComparisonOperatorName.NEQ -> - (leftNodesToValues - .filterNot { (_, value) -> rightNodesToValues.containsValue(value) } - .keys) to (leftProblems + rightProblems) + var from = 0 + this.followPrevEOG { + from++ + it.end == other } + + return if (to < from) to else from } class Problems { - val map: MutableMap = mutableMapOf() + private val map: MutableMap = mutableMapOf() - fun add(reason: String, value: Any) { + fun add(reason: String, value: BackendDataItem) { map[value] = reason } + fun getProblems(): Map = map + + fun isEmpty(): Boolean { + return map.isEmpty() + } + operator fun plus(other: Problems): Problems { map.putAll(other.map) return this } + + override fun toString(): String { + return map.toString() + } + } + + class EvaluationResult() { + val fulfillingNodes: MutableList = mutableListOf() + val unfulfillingNodes: MutableList = mutableListOf() + val problems: Problems = Problems() + + constructor(fulfillingNodes: Nodes, unfulfillingNodes: Nodes, problems: Problems): this() { + this.fulfillingNodes.addAll(fulfillingNodes) + this.unfulfillingNodes.addAll(unfulfillingNodes) + this.problems + problems + } + } - class DummyNode: Node() + class DummyNode : Node() } From d514843d11a14c676d53207f580625f2def9a118 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 8 Sep 2023 16:25:24 +0200 Subject: [PATCH 19/25] change CpgWheneverEvaluator to be able to give more info in findings --- .../coko/evaluators/CpgWheneverEvaluator.kt | 187 +++++++++++------- 1 file changed, 119 insertions(+), 68 deletions(-) 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 bd72c511d..6fb29234d 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 @@ -65,8 +65,10 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } else { for (premiseNode in premiseNodes) { val ensuresToNodes = ensures.associateWith { ensure -> - val (ensureNodes, problems) = evaluateConditionComponentToCpgNodes(ensure, premiseNode) - ensureNodes.filterNot { it is DummyNode } to problems + evaluateConditionComponentToCpgNodes( + ensure, + premiseNode + ).removeDummyNodes() } val callAssertionsToNodes = callAssertions.associateWith { callAssertion -> val nodes = callAssertion.op.cpgGetNodes() @@ -83,7 +85,8 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem Scope.Block -> TODO() } } - nodes.toList() to Problems() + TODO() + EvaluationResult(nodes, emptyList(), Problems()) } findings.addAll( generateFindings(premiseNode, ensuresToNodes + callAssertionsToNodes, passMessage, failMessage) @@ -96,32 +99,34 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem private fun generateFindings( premiseNode: Node, - conditionToNodes: Map, Problems>>, + conditionToNodes: Map, passMessage: String, failMessage: String ): Collection { val findings = mutableListOf() - val unfulfilledConditions = conditionToNodes.filterValues { (nodes, _) -> nodes.isEmpty() } + val unfulfilledConditions = conditionToNodes.filterValues { (fulfilled, _, _) -> fulfilled.isEmpty() } if (unfulfilledConditions.isNotEmpty()) { for ((condition, nodesToProblems) in unfulfilledConditions) { - val (_, problems) = nodesToProblems - if (problems.isEmpty()) { + val (_, unfulfilled, problems) = nodesToProblems + if (problems.isNotEmpty()) { findings.add( CpgFinding( - message = "No $condition found around $premise. $failMessage", - kind = Finding.Kind.Fail, - node = premiseNode + message = "There were problems in resolving $condition. $problems", + kind = Finding.Kind.Open, + node = premiseNode, + relatedNodes = problems.getNodes() ) ) - TODO("Better message") } else { + // TODO: Better message findings.add( CpgFinding( - message = "There were problems in resolving $condition. $problems", - kind = Finding.Kind.Open, + message = "No $condition found around $premise. $failMessage", + kind = Finding.Kind.Fail, node = premiseNode, + relatedNodes = unfulfilled ) ) } @@ -132,9 +137,21 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem message = passMessage, kind = Finding.Kind.Pass, node = premiseNode, - relatedNodes = conditionToNodes.values.flatMap { it.first } + relatedNodes = conditionToNodes.values.flatMap { it.fulfillingNodes } ) ) + + val conditionToProblems = conditionToNodes.mapValues { (_, result) -> result.problems } + for ((condition, problems) in conditionToProblems) { + if (problems.isNotEmpty()) { + CpgFinding( + message = "There were problems in resolving $condition. $problems", + kind = Finding.Kind.Informational, + node = premiseNode, + relatedNodes = problems.getNodes() + ) + } + } } return findings @@ -143,8 +160,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem private fun evaluateConditionNodeToCpgNodes( conditionNode: ConditionNode, premiseNode: Node? = null - ): Pair { - TODO("change to EvaluationResult") + ): EvaluationResult { return when (conditionNode) { is ConditionComponent -> evaluateConditionComponentToCpgNodes(conditionNode, premiseNode) is ArgumentItem<*> -> evaluateArgumentItemToCpgNodes(conditionNode, premiseNode) @@ -153,30 +169,30 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } } - private fun evaluateValueToCpgNodes(value: Value<*>, premiseNode: Node? = null): Pair { - return listOf(DummyNode()) to Problems() + private fun evaluateValueToCpgNodes(value: Value<*>, premiseNode: Node? = null): EvaluationResult { + return EvaluationResult(listOf(DummyNode()), emptyList(), Problems()) } private fun evaluateArgumentItemToCpgNodes( argumentItem: ArgumentItem<*>, premiseNode: Node? = null - ): Pair { + ): EvaluationResult { val nodes = argumentItem.cpgGetNodes().filterWithDistanceToPremise(premiseNode) - return nodes to Problems() + return EvaluationResult(nodes, emptyList(), Problems()) } private fun evaluateReturnValueItemToCpgNodes( returnValueItem: ReturnValueItem<*>, premiseNode: Node? = null - ): Pair { + ): EvaluationResult { val nodes = returnValueItem.cpgGetNodes().filterWithDistanceToPremise(premiseNode) - return nodes to Problems() + return EvaluationResult(nodes, emptyList(), Problems()) } private fun evaluateConditionComponentToCpgNodes( conditionComponent: ConditionComponent, premiseNode: Node? = null - ): Pair { + ): EvaluationResult { return when (conditionComponent) { is BinaryLogicalConditionComponent -> evaluateBinaryLogicalConditionComponent(conditionComponent, premiseNode) @@ -190,74 +206,97 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem private fun evaluateBinaryLogicalConditionComponent( binaryLogicalConditionComponent: BinaryLogicalConditionComponent, premiseNode: Node? = null - ): Pair { - val (leftNodes, leftProblems) = + ): EvaluationResult { + val (leftFulfillingNodes, leftUnfulfillingNodes, leftProblems) = evaluateConditionComponentToCpgNodes(binaryLogicalConditionComponent.left, premiseNode) - val (rightNodes, rightProblems) = + val (rightFulfillingNodes, rightUnfulfillingNodes, rightProblems) = evaluateConditionComponentToCpgNodes(binaryLogicalConditionComponent.right, premiseNode) return when (binaryLogicalConditionComponent.operator) { - BinaryLogicalOperatorName.AND -> (leftNodes intersect rightNodes) to (leftProblems + rightProblems) - BinaryLogicalOperatorName.OR -> (leftNodes union rightNodes) to (leftProblems + rightProblems) + BinaryLogicalOperatorName.AND -> + EvaluationResult( + leftFulfillingNodes intersect rightFulfillingNodes.toSet(), + leftUnfulfillingNodes union rightUnfulfillingNodes, + leftProblems + rightProblems + ) + BinaryLogicalOperatorName.OR -> + EvaluationResult( + leftFulfillingNodes union rightFulfillingNodes, + leftUnfulfillingNodes union rightUnfulfillingNodes, + leftProblems + rightProblems + ) } } private fun evaluateComparisonConditionComponent( comparisonConditionComponent: ComparisonConditionComponent, premiseNode: Node? = null - ): Pair { - val (leftNodes, leftProblems) = + ): EvaluationResult { + val (leftFulfillingNodes, leftUnfulfillingNodes, leftProblems) = evaluateConditionNodeToCpgNodes(comparisonConditionComponent.left, premiseNode) - val (rightNodes, rightProblems) = + val (rightFulfillingNodes, rightUnfulfillingNodes, rightProblems) = evaluateConditionNodeToCpgNodes(comparisonConditionComponent.right, premiseNode) val newProblems = Problems() - val result = mutableSetOf() + val newFulfillingNodes = mutableSetOf() + val newUnfulfillingNodes = mutableSetOf() val leftNodesToValues = - leftNodes.toNodesToValues(newProblems, comparisonConditionComponent.left.transformation) + leftFulfillingNodes.toNodesToValues(newProblems, comparisonConditionComponent.left.transformation) val rightNodesToValues = - rightNodes.toNodesToValues(newProblems, comparisonConditionComponent.right.transformation) + rightFulfillingNodes.toNodesToValues(newProblems, comparisonConditionComponent.right.transformation) for ((leftNode, leftValue) in leftNodesToValues) { - for ((rightNode, rightValue) in rightNodesToValues) - when (comparisonConditionComponent.operator) { - ComparisonOperatorName.GEQ -> TODO() - ComparisonOperatorName.GT -> TODO() - ComparisonOperatorName.LEQ -> TODO() - ComparisonOperatorName.LT -> TODO() - ComparisonOperatorName.EQ -> - if (leftValue == rightValue) { - result.add(leftNode) - result.add(rightNode) - } - ComparisonOperatorName.NEQ -> TODO() +// for ((rightNode, rightValue) in rightNodesToValues) + when (comparisonConditionComponent.operator) { + ComparisonOperatorName.GEQ -> TODO() + ComparisonOperatorName.GT -> TODO() + ComparisonOperatorName.LEQ -> TODO() + ComparisonOperatorName.LT -> TODO() + ComparisonOperatorName.EQ -> { + val (fulfilling, unfulfilling) = + rightNodesToValues.toList().partition { (_, rightValue) -> leftValue == rightValue } + newFulfillingNodes.addAll(fulfilling.map { it.first }) + newUnfulfillingNodes.addAll(unfulfilling.map { it.first }) + if (fulfilling.isEmpty()) { + newUnfulfillingNodes.add( + leftNode + ) + } else { + newFulfillingNodes.add(leftNode) + } } + ComparisonOperatorName.NEQ -> TODO() + } } - return result to (leftProblems + rightProblems + newProblems) + return EvaluationResult( + newFulfillingNodes, + leftUnfulfillingNodes + rightUnfulfillingNodes + newUnfulfillingNodes, + leftProblems + rightProblems + newProblems + ) } private fun evaluateCallConditionComponent( callConditionComponent: CallConditionComponent, premiseNode: Node? = null - ): Pair { + ): EvaluationResult { val callNodes = callConditionComponent.op.cpgGetNodes().filterWithDistanceToPremise(premiseNode) - return callNodes to Problems() + return EvaluationResult(callNodes, emptyList(), Problems()) } private fun evaluateContainsConditionComponent( containsConditionComponent: ContainsConditionComponent<*, *>, premiseNode: Node? - ): Pair { + ): EvaluationResult { val problems = Problems() val nodes = containsConditionComponent.item.cpgGetNodes().filterWithDistanceToPremise(premiseNode) - val resultNodes = nodes.filter { node -> + val (fulfilling, unfulfilling) = nodes.partition { node -> val backendDataItem = node.toBackendDataItem() val transformationResult = containsConditionComponent.item.transformation(backendDataItem) if (transformationResult.isFailure) { problems.add( transformationResult.getFailureReasonOrNull() ?: "Transformation of $backendDataItem failed", - backendDataItem + node ) false } else { @@ -266,7 +305,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } } - return resultNodes to Problems() + return EvaluationResult(fulfilling, unfulfilling, problems) } private fun Nodes.toNodesToValues( @@ -280,7 +319,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem val transformationResult = transformation(backendDataItem) val value = transformationResult.getValueOrNull() if (value == null) { - problems.add(transformationResult.getFailureReasonOrNull() ?: "Value was null", backendDataItem) + problems.add(transformationResult.getFailureReasonOrNull() ?: "Value was null", node) } else { result[node] = value } @@ -314,16 +353,16 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } class Problems { - private val map: MutableMap = mutableMapOf() + private val map: MutableMap = mutableMapOf() - fun add(reason: String, value: BackendDataItem) { + fun add(reason: String, value: Node) { map[value] = reason } - fun getProblems(): Map = map + fun getNodes(): Nodes = map.keys - fun isEmpty(): Boolean { - return map.isEmpty() + fun isNotEmpty(): Boolean { + return map.isNotEmpty() } operator fun plus(other: Problems): Problems { @@ -332,21 +371,33 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } override fun toString(): String { - return map.toString() + return map.mapKeys { (node, _) -> node.toBackendDataItem() }.toString() } } - class EvaluationResult() { - val fulfillingNodes: MutableList = mutableListOf() - val unfulfillingNodes: MutableList = mutableListOf() + data class EvaluationResult( + val fulfillingNodes: MutableList = mutableListOf(), + val unfulfillingNodes: MutableList = mutableListOf(), val problems: Problems = Problems() - - constructor(fulfillingNodes: Nodes, unfulfillingNodes: Nodes, problems: Problems): this() { - this.fulfillingNodes.addAll(fulfillingNodes) - this.unfulfillingNodes.addAll(unfulfillingNodes) - this.problems + problems + ) { + constructor( + fulfillingNodes: Nodes, + unfulfillingNodes: Nodes, + problems: Problems + ) : this(fulfillingNodes.toMutableList(), unfulfillingNodes.toMutableList(), problems) + + operator fun plus(other: EvaluationResult): EvaluationResult { + fulfillingNodes.addAll(other.fulfillingNodes) + unfulfillingNodes.addAll(other.unfulfillingNodes) + problems + other.problems + return this } + fun removeDummyNodes(): EvaluationResult { + fulfillingNodes.removeIf { it is DummyNode } + unfulfillingNodes.removeIf { it is DummyNode } + return this + } } class DummyNode : Node() From 201e11c16cffcb287bee2bc1d72691f8bf3cb4de Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 8 Sep 2023 18:22:29 +0200 Subject: [PATCH 20/25] implement mode for JCACipherImpl --- .../resources/concept/jca-cipher.codyze.kts | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/jca-cipher.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/jca-cipher.codyze.kts index b9b8bb179..05460b971 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/jca-cipher.codyze.kts +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/jca-cipher.codyze.kts @@ -1,11 +1,13 @@ @file:Import("bsi-tr-rules.codyze.kts") +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.BackendDataItem import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.value class JCACipher { fun getInstance(transformation: Any, provider: Any? = null) = op { - "javax.crypto.Cipher.getInstance" { + // TODO: Change back to "javax.crypto.Cipher.getInstance" when CPG resolves names correctly + "Cipher.getInstance" { signature(transformation) signature(transformation, provider) } @@ -58,30 +60,43 @@ class JCACipherImpl: Bsi_tr_rules_codyze.Cypher { val cipher = JCACipher() override val algo: DataItem - get() = cipher.getInstance(Wildcard, Wildcard).arguments[1] withTransformation { dataItem -> - val stringValue = dataItem.value as? String - if(dataItem.value == null) - TransformationResult.failure("Value of BackendDataItem is null.") - else if(stringValue == null) { - TransformationResult.failure("Value of BackendDataItem is not a string.") - } - else { - val (algo, _) = stringValue.partition { it == '/' } - val algoEnum = Bsi_tr_rules_codyze.Algorithm.values().firstOrNull { it.name == algo.uppercase() } - if(algoEnum == null) - TransformationResult.failure("Could not resolve the string value to an algorithm") - else - TransformationResult.success(algoEnum) + get() = cipher.getInstance(Wildcard, Wildcard).arguments[0] withTransformation { dataItem -> + transformTransformationString(dataItem, 0, "an algorithm") { string -> + Bsi_tr_rules_codyze.Algorithm.entries.firstOrNull { it.name == string.uppercase() } } } override val mode: DataItem - get() = value(Bsi_tr_rules_codyze.Mode.CBC) + get() = cipher.getInstance(Wildcard, Wildcard).arguments[0] withTransformation { dataItem -> + transformTransformationString(dataItem, 1, "a mode") { string -> + Bsi_tr_rules_codyze.Mode.entries.firstOrNull { it.name == string.uppercase() } + } + } override val keySize: DataItem get() = value(1) override val tagSize: DataItem get() = value(1) + fun transformTransformationString( + dataItem: BackendDataItem, + index: Int, + errorMessage: String, + stringToResult: (String) -> E?): TransformationResult { + val stringValue = dataItem.value as? String + return if(dataItem.value == null) + TransformationResult.failure("Value of BackendDataItem is null.") + else if(stringValue == null) { + TransformationResult.failure("Value of BackendDataItem is not a string.") + } + else { + val string = stringValue.split('/')[index] + val result = stringToResult(string) + if(result == null) + TransformationResult.failure("Could not resolve the string value to $errorMessage") + else + TransformationResult.success(result) + } + } } From 7717bc47d8125062ffb8711aaa04a5bdd9c6f549 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 8 Sep 2023 18:25:55 +0200 Subject: [PATCH 21/25] clean up and add comments/documentation --- .../backends/cpg/coko/dsl/DataItemCpgDsl.kt | 3 + .../coko/evaluators/CpgWheneverEvaluator.kt | 142 ++++++++++++------ .../cpg/coko/modelling/CpgBackendDataItem.kt | 2 + .../coko/core/CokoBackend.kt | 1 + .../coko/core/WheneverEvaluator.kt | 28 +++- .../coko/core/dsl/Annotations.kt | 2 - .../coko/core/dsl/Condition.kt | 79 +++++++++- .../coko/core/dsl/Op.kt | 4 + .../core/modelling/ConditionComponents.kt | 11 ++ .../coko/core/modelling/DataItem.kt | 22 +++ 10 files changed, 238 insertions(+), 56 deletions(-) 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 3c04594d6..dc8410962 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 @@ -49,6 +49,9 @@ fun DataItem<*>.cpgGetNodes(): Nodes { } } +/** + * Get all [Nodes] that evaluate to the same value as [Value.value]. + */ context(CokoBackend) private fun Value<*>.getNodes(): Nodes { val value = this.value diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt index 6fb29234d..a8c3b6e08 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 @@ -25,8 +25,6 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Rule import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.TransformationResult import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.followNextEOG -import de.fraunhofer.aisec.cpg.graph.followPrevEOG import de.fraunhofer.aisec.cpg.query.executionPath import kotlin.reflect.full.findAnnotation @@ -43,33 +41,39 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem val findings = mutableListOf() - val (premiseNodes, premiseProblems) = evaluateConditionNodeToCpgNodes(premise) + // First find the nodes corresponding to the premise + val (premiseNodes, _, premiseProblems) = evaluateConditionComponentToCpgNodes(premise) + // If there are no premiseNodes, we cannot evaluate further if (premiseNodes.isEmpty()) { - if (premiseProblems.isEmpty()) { + if (premiseProblems.isNotEmpty()) { + // If there were problems resolving the premise, we cannot be sure that the rule was followed findings.add( CpgFinding( - message = "$premise was not found in code.", - kind = Finding.Kind.NotApplicable, + message = "$premise could not be resolved correctly. $premiseProblems", + kind = Finding.Kind.Open, ) ) } else { + // Since there were no problems in resolving the premise, the rule must not be applicable findings.add( CpgFinding( - message = "$premise could not be resolved correctly. $premiseProblems", - kind = Finding.Kind.Open, - + message = "$premise was not found in code.", + kind = Finding.Kind.NotApplicable, ) ) } } else { + // check for each premiseNode if the ensures and callAssertions are fulfilled for (premiseNode in premiseNodes) { + // find nodes that fulfill the ensures conditions val ensuresToNodes = ensures.associateWith { ensure -> evaluateConditionComponentToCpgNodes( ensure, premiseNode ).removeDummyNodes() } + // find nodes that fulfill the call conditions val callAssertionsToNodes = callAssertions.associateWith { callAssertion -> val nodes = callAssertion.op.cpgGetNodes() val location = callAssertion.location @@ -97,6 +101,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem return findings } + /** Generates the findings based on what nodes were found for each condition in [conditionToNodes]. */ private fun generateFindings( premiseNode: Node, conditionToNodes: Map, @@ -107,10 +112,12 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem val unfulfilledConditions = conditionToNodes.filterValues { (fulfilled, _, _) -> fulfilled.isEmpty() } + // If there is a condition that was not fulfilled, the rule was not followed if (unfulfilledConditions.isNotEmpty()) { for ((condition, nodesToProblems) in unfulfilledConditions) { val (_, unfulfilled, problems) = nodesToProblems if (problems.isNotEmpty()) { + // If there were problems resolving the condition, we cannot be sure that the rule was broken or not findings.add( CpgFinding( message = "There were problems in resolving $condition. $problems", @@ -120,9 +127,11 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem ) ) } else { - // TODO: Better message + // Since there were no problems, this means that the condition was definitely not fulfilled and + // the rule was broken findings.add( CpgFinding( + // TODO: Better message message = "No $condition found around $premise. $failMessage", kind = Finding.Kind.Fail, node = premiseNode, @@ -132,6 +141,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } } } else { + // Since there are no unfulfilled conditions, the rule was followed for this premiseNode findings.add( CpgFinding( message = passMessage, @@ -141,6 +151,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem ) ) + // document any problems that occurred when resolving the condition val conditionToProblems = conditionToNodes.mapValues { (_, result) -> result.problems } for ((condition, problems) in conditionToProblems) { if (problems.isNotEmpty()) { @@ -157,6 +168,10 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem return findings } + /** + * Finds the nodes that are represented by the [conditionNode]. + * If [premiseNode] is given, only nodes that are in its vicinity are considered. + */ private fun evaluateConditionNodeToCpgNodes( conditionNode: ConditionNode, premiseNode: Node? = null @@ -169,10 +184,19 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } } + /** + * Finds the nodes that are represented by [value]. + */ private fun evaluateValueToCpgNodes(value: Value<*>, premiseNode: Node? = null): EvaluationResult { + // We return a [DummyNode] here because Value objects don't use the information from the backend + // for their transformation. return EvaluationResult(listOf(DummyNode()), emptyList(), Problems()) } + /** + * Finds the nodes that are represented by [argumentItem]. + * If [premiseNode] is given, only nodes that are in its vicinity are considered. + */ private fun evaluateArgumentItemToCpgNodes( argumentItem: ArgumentItem<*>, premiseNode: Node? = null @@ -181,6 +205,10 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem return EvaluationResult(nodes, emptyList(), Problems()) } + /** + * Finds the nodes that are represented by [returnValueItem]. + * If [premiseNode] is given, only nodes that are in its vicinity are considered. + */ private fun evaluateReturnValueItemToCpgNodes( returnValueItem: ReturnValueItem<*>, premiseNode: Node? = null @@ -189,6 +217,10 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem return EvaluationResult(nodes, emptyList(), Problems()) } + /** + * Finds the nodes that fulfill and don't fulfill the [conditionComponent]. + * If [premiseNode] is given, only nodes that are in its vicinity are considered. + */ private fun evaluateConditionComponentToCpgNodes( conditionComponent: ConditionComponent, premiseNode: Node? = null @@ -203,6 +235,10 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } } + /** + * Combines the nodes of the right and left side of the [binaryLogicalConditionComponent]. + * If [premiseNode] is given, only nodes that are in its vicinity are considered. + */ private fun evaluateBinaryLogicalConditionComponent( binaryLogicalConditionComponent: BinaryLogicalConditionComponent, premiseNode: Node? = null @@ -215,6 +251,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem BinaryLogicalOperatorName.AND -> EvaluationResult( leftFulfillingNodes intersect rightFulfillingNodes.toSet(), + // we return the union of the unfulfilled nodes since we want to collect them all leftUnfulfillingNodes union rightUnfulfillingNodes, leftProblems + rightProblems ) @@ -227,6 +264,10 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } } + /** + * Finds the nodes that fulfill and don't fulfill the [comparisonConditionComponent]. + * If [premiseNode] is given, only nodes that are in its vicinity are considered. + */ private fun evaluateComparisonConditionComponent( comparisonConditionComponent: ComparisonConditionComponent, premiseNode: Node? = null @@ -240,32 +281,37 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem val newFulfillingNodes = mutableSetOf() val newUnfulfillingNodes = mutableSetOf() + // These are the nodes where the transformation produced a value which can hopefully compared to one another val leftNodesToValues = leftFulfillingNodes.toNodesToValues(newProblems, comparisonConditionComponent.left.transformation) val rightNodesToValues = rightFulfillingNodes.toNodesToValues(newProblems, comparisonConditionComponent.right.transformation) for ((leftNode, leftValue) in leftNodesToValues) { -// for ((rightNode, rightValue) in rightNodesToValues) - when (comparisonConditionComponent.operator) { + // we partition the rightNodes into ones that fulfill and + // the ones that do not fulfill this condition when compared to the leftNode + val (fulfilling, unfulfilling) = when (comparisonConditionComponent.operator) { ComparisonOperatorName.GEQ -> TODO() ComparisonOperatorName.GT -> TODO() ComparisonOperatorName.LEQ -> TODO() - ComparisonOperatorName.LT -> TODO() - ComparisonOperatorName.EQ -> { - val (fulfilling, unfulfilling) = - rightNodesToValues.toList().partition { (_, rightValue) -> leftValue == rightValue } - newFulfillingNodes.addAll(fulfilling.map { it.first }) - newUnfulfillingNodes.addAll(unfulfilling.map { it.first }) - if (fulfilling.isEmpty()) { - newUnfulfillingNodes.add( - leftNode - ) - } else { - newFulfillingNodes.add(leftNode) - } - } - ComparisonOperatorName.NEQ -> TODO() + ComparisonOperatorName.LT -> TODO("How do we ensure that both values have an order?") + ComparisonOperatorName.EQ -> + rightNodesToValues.toList().partition { (_, rightValue) -> leftValue == rightValue } + ComparisonOperatorName.NEQ -> + rightNodesToValues.toList().partition { (_, rightValue) -> leftValue != rightValue } + } + newFulfillingNodes.addAll(fulfilling.map { it.first }) + newUnfulfillingNodes.addAll(unfulfilling.map { it.first }) + if (fulfilling.isEmpty()) { + // there was no rightNode where the comparison to this leftNode returned true, + // so it does not fulfill this condition + newUnfulfillingNodes.add( + leftNode + ) + } else { + // there was a rightNode where the comparison to this leftNode returned true, + // so it fulfills this condition + newFulfillingNodes.add(leftNode) } } @@ -276,6 +322,10 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem ) } + /** + * Finds the nodes of the [Op] of [callConditionComponent]. + * If [premiseNode] is given, only nodes that are in its vicinity are considered. + */ private fun evaluateCallConditionComponent( callConditionComponent: CallConditionComponent, premiseNode: Node? = null @@ -284,6 +334,11 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem return EvaluationResult(callNodes, emptyList(), Problems()) } + /** + * Finds the nodes of the [DataItem] of [containsConditionComponent] for which + * its transformed value is contained in [ContainsConditionComponent.collection]. + * If [premiseNode] is given, only nodes that are in its vicinity are considered. + */ private fun evaluateContainsConditionComponent( containsConditionComponent: ContainsConditionComponent<*, *>, premiseNode: Node? @@ -308,6 +363,10 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem return EvaluationResult(fulfilling, unfulfilling, problems) } + /** + * Maps the nodes in [Nodes] to their transformed values based on [transformation]. + * If the transformation was unsuccessful, the failure reason is added to [problems]. + */ private fun Nodes.toNodesToValues( problems: Problems, transformation: (BackendDataItem) -> TransformationResult @@ -328,6 +387,10 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem return result } + /** + * Filters out all nodes that are not in the vicinity of [premiseNode]. + */ + // TODO: Is this filter ok? Do we have to limit how far the nodes can be apart? private fun Nodes.filterWithDistanceToPremise(premiseNode: Node?): Nodes { return if (premiseNode != null) { this.filter { executionPath(it, premiseNode).value } @@ -336,22 +399,9 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } } - private fun Node.eogDistance(other: Node): Int { - var to = 0 - this.followNextEOG { - to++ - it.end == other - } - - var from = 0 - this.followPrevEOG { - from++ - it.end == other - } - - return if (to < from) to else from - } - + /** + * A class to collect the problems that are encountered in an evaluation. + */ class Problems { private val map: MutableMap = mutableMapOf() @@ -375,6 +425,12 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } } + /** + * A class to collect the results of the evaluation of a [ConditionNode]. + * [fulfillingNodes] contains all nodes that fulfill the [ConditionNode], + * [unfulfillingNodes] contains all nodes that do not fulfill the [ConditionNode], + * [problems] are the problems encountered in the evaluation. + */ data class EvaluationResult( val fulfillingNodes: MutableList = mutableListOf(), val unfulfillingNodes: MutableList = mutableListOf(), diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt index d45638eb0..c2b22240a 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt @@ -23,12 +23,14 @@ import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.evaluate import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +/** Implementation of the [BackendDataItem] interface for the CPG */ class CpgBackendDataItem( override val name: String?, override val value: Any?, override val type: String? ) : BackendDataItem +/** Returns the [CpgBackendDataItem] of the given [Node] */ fun Node.toBackendDataItem(): CpgBackendDataItem { val value = (this as? Expression)?.evaluate() ?: (this as? Declaration)?.evaluate() val type = (this as? HasType)?.type?.typeName diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt index 79c0ca3e2..09f00d662 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/CokoBackend.kt @@ -64,6 +64,7 @@ interface CokoBackend : Backend { /** Ensures that there are no calls to the [ops] which have arguments that fit the parameters specified in [ops] */ fun never(vararg ops: Op): Evaluator + /** Verifies that the [assertionBlock] is ensured when [premise] is found */ fun whenever( premise: Condition.() -> ConditionComponent, assertionBlock: WheneverEvaluator.() -> Unit diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt index b12a181be..ec92a7301 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt @@ -21,19 +21,33 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.CallConditionComponent import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.ConditionComponent +/** + * An evaluator that describes what should happen around the code that the [premise] represents. + */ abstract class WheneverEvaluator(protected val premise: ConditionComponent) : Evaluator { protected val ensures: MutableList = mutableListOf() protected val callAssertions: MutableList = mutableListOf() + /** + * Specifies a condition that has to be ensured around the [premise]. + */ fun ensure(block: Condition.() -> ConditionComponent) { ensures.add(Condition().run(block)) } + /** + * Specifies that [op] has to be called in the given [location] in regard to the [premise]. + */ fun call(op: Op, location: CallLocationBuilder.() -> CallLocation? = { null }) { callAssertions.add(CallAssertion(op, CallLocationBuilder().run(location))) } } +class CallLocationBuilder { + infix fun Direction.within(scope: Scope) = CallLocation(this@Direction, scope) +} +data class CallLocation(val direction: Direction, val scope: Scope) + enum class Direction { afterwards, before, somewhere } @@ -42,16 +56,14 @@ enum class Scope { Function, Block } -class CallLocationBuilder { - infix fun Direction.within(scope: Scope) = CallLocation(this@Direction, scope) -} -data class CallLocation(val direction: Direction, val scope: Scope) - -class CallAssertion(op: Op, val location: CallLocation? = null): CallConditionComponent(op) { +class CallAssertion(op: Op, val location: CallLocation? = null) : CallConditionComponent(op) { override fun toString(): String = "Call $op ${ - if(location != null) "${location.direction} in ${location.scope}." - else "" + if (location != null) { + "${location.direction} in ${location.scope}." + } else { + "" + } }" } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Annotations.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Annotations.kt index 5ca84b526..6d1e6d1bf 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Annotations.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Annotations.kt @@ -42,8 +42,6 @@ annotation class Rule( val precision: Precision = Precision.UNKNOWN, ) -annotation class RuleSet() - enum class Severity { INFO, WARNING, diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt index ca428fdb2..7ea34193c 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt @@ -21,50 +21,123 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* fun condition(block: Condition.() -> ConditionComponent) = Condition().run(block) +/** This class exists to restrict where the functions can be called */ class Condition { + /** Can be used to build a list like `list[x,y,z]` */ val list by lazy { ListBuilder() } - infix fun Any.within(collection: Collection<*>) = - ContainsConditionComponent(toValue(this), collection) - + /** + * Builds a [ContainsConditionComponent] that specifies that the value of this [DataItem] should be + * contained in [collection]. + */ + infix fun DataItem<*>.within(collection: Collection<*>) = + ContainsConditionComponent(this, collection) + + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this [DataItem] should be + * greater than the value of [other]. + */ infix fun DataItem<*>.gt(other: DataItem<*>) = ComparisonConditionComponent(this, other, ComparisonOperatorName.GT) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this should be + * greater than the value of [other]. + */ + // This function exists so users don't have to wrap the objects as Value themselves infix fun Any.gt(other: Any) = toValue(this) gt toValue(other) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this [DataItem] should be + * greater or equal to the value of [other]. + */ infix fun DataItem<*>.geq(other: DataItem<*>) = ComparisonConditionComponent(this, other, ComparisonOperatorName.GEQ) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this should be + * greater or equal to the value of [other]. + */ + // This function exists so users don't have to wrap the objects as Value themselves infix fun Any.geq(other: Any) = toValue(this) geq toValue(other) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this [DataItem] should be + * less than the value of [other]. + */ infix fun DataItem<*>.lt(other: DataItem<*>) = ComparisonConditionComponent(this, other, ComparisonOperatorName.LT) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this should be + * less than the value of [other]. + */ + // This function exists so users don't have to wrap the objects as Value themselves infix fun Any.lt(other: Any) = toValue(this) lt toValue(other) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this [DataItem] should be + * less or equal to the value of [other]. + */ infix fun DataItem<*>.leq(other: DataItem<*>) = ComparisonConditionComponent(this, other, ComparisonOperatorName.LEQ) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this should be + * less or equal to the value of [other]. + */ + // This function exists so users don't have to wrap the objects as Value themselves infix fun Any.leq(other: Any) = toValue(this) leq toValue(other) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this [DataItem] should be + * equal to the value of [other]. + */ infix fun DataItem<*>.eq(other: DataItem<*>) = ComparisonConditionComponent(this, other, ComparisonOperatorName.EQ) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this should be + * equal to the value of [other]. + */ + // This function exists so users don't have to wrap the objects as Value themselves infix fun Any.eq(other: Any) = toValue(this) eq toValue(other) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this [DataItem] should not be + * equal to the value of [other]. + */ infix fun DataItem<*>.neq(other: DataItem<*>) = ComparisonConditionComponent(this, other, ComparisonOperatorName.NEQ) + /** + * Builds a [ComparisonConditionComponent] that specifies that the value of this should not be + * equal to the value of [other]. + */ + // This function exists so users don't have to wrap the objects as Value themselves infix fun Any.neq(other: Any) = toValue(this) neq toValue(other) + /** + * Builds a [CallConditionComponent] that specifies that [op] should be called. + */ + // TODO: Is this needed? fun call(op: Op) = CallConditionComponent(op) + /** + * Connects two [ConditionComponent]s with a logical and. + */ infix fun ConditionComponent.and(other: ConditionComponent) = BinaryLogicalConditionComponent(this, other, BinaryLogicalOperatorName.AND) + /** + * Connects two [ConditionComponent]s with a logical or. + */ infix fun ConditionComponent.or(other: ConditionComponent) = BinaryLogicalConditionComponent(this, other, BinaryLogicalOperatorName.OR) + /** + * Negates a [ConditionComponent] + */ fun not(conditionComponent: ConditionComponent) = UnaryConditionComponent(conditionComponent, UnaryOperatorName.NOT) private fun toValue(value: Any): DataItem<*> = diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt index cfeedfa24..0822fccf4 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt @@ -35,6 +35,9 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ordering.Term ) } +/** + * A helper class that makes it possible to construct an [ArgumentItem] with an indexed access (e.g. op.argument[1]) + */ class Arguments(val op: Op) { operator fun get(index: Int): ArgumentItem = ArgumentItem(op, index) } @@ -127,6 +130,7 @@ data class GroupingOp(val ops: Set, override val ownerClassFqn: String = "") /** * An [Op] that describes that the function calls found with [resultOp] depend on the function calls found with the [conditionOp] */ +// TODO: Clearly define the dependency we are describing here -> is it data flow, control flow, or something else? data class ConditionalOp(val resultOp: Op, val conditionOp: Op, override val ownerClassFqn: String = "") : Op { override fun toString(): String { return "$resultOp with $conditionOp" diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt index 4f2cc8503..189da0f1d 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt @@ -21,15 +21,21 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op sealed interface ConditionNode { override fun toString(): String } + +/** [ConditionNode] that can have children */ sealed interface ConditionComponent : ConditionNode + +/** [ConditionNode] that does not have children */ sealed interface ConditionLeaf : ConditionNode +/** A [ConditionComponent] that has two children and a [operator] */ sealed interface BinaryConditionComponent : ConditionComponent { val left: ConditionNode val right: ConditionNode val operator: BinaryOperatorName } +/** A [BinaryConditionComponent] that compares [left] and [right] based on [operator] (e.g. equality or less than) */ class ComparisonConditionComponent( override val left: DataItem<*>, override val right: DataItem<*>, @@ -38,6 +44,7 @@ class ComparisonConditionComponent( override fun toString(): String = "$left ${operator.name} $right" } +/** A [BinaryConditionComponent] that logically combines [left] and [right] based on [operator] (e.g. logical and) */ class BinaryLogicalConditionComponent( override val left: ConditionComponent, override val right: ConditionComponent, @@ -46,6 +53,7 @@ class BinaryLogicalConditionComponent( override fun toString(): String = "$left ${operator.name} $right" } +/** A [ConditionComponent] that has one child, [conditionNode], and a [operator] (e.g. negation) */ class UnaryConditionComponent( val conditionNode: ConditionNode, val operator: UnaryOperatorName @@ -53,10 +61,13 @@ class UnaryConditionComponent( override fun toString(): String = "${operator.name} $conditionNode" } +/** A [ConditionComponent] that represents a function call */ +// TODO: Is this necessary? And should it be a ConditionLeaf? open class CallConditionComponent(val op: Op) : ConditionComponent { override fun toString(): String = op.toString() } +/** A [ConditionComponent] that represents the condition that the value of [item] should be in [collection] */ class ContainsConditionComponent(val item: DataItem, val collection: Collection) : ConditionComponent { override fun toString(): String = "$item within $collection" } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt index 9f9ee8fdf..6e4182424 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt @@ -19,16 +19,30 @@ package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.TransformationResult +/** + * Represents a data item such as a variable or literal. + * + * [transformation] is a function that transforms data from the [CokoBackend] into an object with type [E] + */ sealed interface DataItem : ConditionLeaf { val transformation: (BackendDataItem) -> TransformationResult } +/** + * Adds an infix function [withTransformation] to a [DataItem]. + */ sealed interface CanChangeTransformation : DataItem { + /** + * Changes the [transformation] of this [DataItem] into the given [newTransformation]. + */ infix fun withTransformation( newTransformation: (BackendDataItem) -> TransformationResult ): DataItem } +/** + * Represents the data item that a function call represented by [op] returns. + */ data class ReturnValueItem( val op: Op, override val transformation: (BackendDataItem) -> TransformationResult = { @@ -43,6 +57,11 @@ data class ReturnValueItem( ReturnValueItem(op = op, transformation = newTransformation) } +/** + * Represents the argument at [index] given to the function call represented by [op]. + * + * The [index] starts counting from 0. + */ data class ArgumentItem( val op: Op, val index: Int, @@ -74,6 +93,9 @@ data class ArgumentItem( } } +/** + * A wrapper class for [value] to make it a [DataItem]. + */ data class Value internal constructor(val value: E) : DataItem { override val transformation: (BackendDataItem) -> TransformationResult get() = { TransformationResult.success(value) } From 06bb894cb499646d3d80a56619894d96962dcca4 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 8 Sep 2023 18:36:16 +0200 Subject: [PATCH 22/25] add very simple test for WheneverEvaluator --- .../coko/dsl/WheneverEvaluatorTest.kt | 87 +++++++++++++++++++ .../resources/concept/bsi-tr-rules.codyze.kts | 13 ++- 2 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/WheneverEvaluatorTest.kt diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/WheneverEvaluatorTest.kt b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/WheneverEvaluatorTest.kt new file mode 100644 index 000000000..9e2872290 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/WheneverEvaluatorTest.kt @@ -0,0 +1,87 @@ +/* + * 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.codyze.specificationLanguages.coko.dsl + +import de.fraunhofer.aisec.codyze.backends.cpg.CPGConfiguration +import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.host.CokoExecutor +import de.fraunhofer.aisec.cpg.passes.EdgeCachePass +import de.fraunhofer.aisec.cpg.passes.UnreachableEOGPass +import io.github.detekt.sarif4k.ResultKind +import org.junit.jupiter.api.Test +import kotlin.io.path.Path +import kotlin.test.assertEquals + +// TODO: should probably in codyze-backends or coko-core +class WheneverEvaluatorTest { + + private val sourceFiles = listOfNotNull( + CokoCpgIntegrationTest::class.java.classLoader + .getResource("concept/CipherTestFile.java"), + ).map { Path(it.path) }.also { assertEquals(1, it.size) } + + val cpgConfiguration = + CPGConfiguration( + source = sourceFiles, + useUnityBuild = false, + typeSystemActiveInFrontend = true, + debugParser = false, + disableCleanup = false, + codeInNodes = true, + matchCommentsToNodes = false, + processAnnotations = false, + failOnError = false, + useParallelFrontends = false, + defaultPasses = true, + additionalLanguages = setOf(), + symbols = mapOf(), + includeBlocklist = listOf(), + includePaths = listOf(), + includeAllowlist = listOf(), + loadIncludes = false, + passes = listOf(UnreachableEOGPass::class, EdgeCachePass::class), + ) + + /** + * Performs an end-to-end integration test of Codyze with the [CokoExecutor] and the [CokoCpgBackend] backend. + */ + @Test + fun `test coko with cpg backend and multiple spec files`() { + val specFiles = listOfNotNull( + CokoCpgIntegrationTest::class.java.classLoader + .getResource("concept/bsi-tr-rules.codyze.kts"), + CokoCpgIntegrationTest::class.java.classLoader + .getResource("concept/jca-cipher.codyze.kts") + ).map { Path(it.path) }.also { assertEquals(2, it.size) } + + val cokoConfiguration = + CokoConfiguration( + goodFindings = true, + pedantic = false, + spec = specFiles, + disabledSpecRules = emptyList(), + ) + + val backend = CokoCpgBackend(cpgConfiguration) + val executor = CokoExecutor(cokoConfiguration, backend) + + val run = executor.evaluate() + + // assertions for the order rule + assertEquals(1, run.results?.size, "Expected to find one result but was ${run.results?.size}") + assertEquals(ResultKind.Fail, run.results?.firstOrNull()?.kind, "Result was not fail") + } +} diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts index 60a2cb51d..6b66258e6 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/concept/bsi-tr-rules.codyze.kts @@ -1,7 +1,6 @@ //@file:Import("bsi-tr.concepts") import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.DataItem -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.value enum class Algorithm { AES, DES, TripleDES @@ -45,13 +44,13 @@ interface Encryption { whenever({ call(enc.encrypt(Wildcard)) }) { ensure { enc.cypher.algo eq Algorithm.AES } ensure { enc.cypher.mode within list[Mode.CCM, Mode.GCM, Mode.CTR] } - ensure { enc.cypher.keySize within list[128, 192, 256]} +// ensure { enc.cypher.keySize within list[128, 192, 256]} } - @Rule - fun `modes of operation`(enc: Encryption) = - whenever({ call(enc.encrypt(Wildcard)) and (enc.cypher.mode eq Mode.CCM) }) { - ensure { enc.cypher.tagSize geq 96 } - } +// @Rule +// fun `modes of operation`(enc: Encryption) = +// whenever({ call(enc.encrypt(Wildcard)) and (enc.cypher.mode eq Mode.CCM) }) { +// ensure { enc.cypher.tagSize geq 96 } +// } //} From f4484587d532550cb217e1d86f02870d7437d741 Mon Sep 17 00:00:00 2001 From: "Wendland, Florian" Date: Thu, 11 Apr 2024 10:05:47 +0200 Subject: [PATCH 23/25] Add fixes for CPG v8, Detekt and Spotless --- .../aisec/codyze/backends/cpg/coko/dsl/DataItemCpgDsl.kt | 5 ++--- .../backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt | 8 ++++---- .../backends/cpg/coko/modelling/CpgBackendDataItem.kt | 3 +-- .../specificationLanguages/coko/core/WheneverEvaluator.kt | 3 +-- .../specificationLanguages/coko/core/dsl/Condition.kt | 2 +- .../codyze/specificationLanguages/coko/core/dsl/Op.kt | 2 ++ .../coko/core/dsl/TransformationResult.kt | 1 - .../coko/core/modelling/BackendDataItem.kt | 1 - .../coko/core/modelling/ConditionComponents.kt | 1 - .../coko/core/modelling/DataItem.kt | 1 - .../codyze/specificationLanguages/coko/dsl/CokoScript.kt | 1 - .../specificationLanguages/coko/dsl/host/CokoExecutor.kt | 1 + .../coko/dsl/host/ConceptTranslator.kt | 1 - .../coko/dsl/ConceptTranslationTest.kt | 2 -- .../coko/dsl/WheneverEvaluatorTest.kt | 1 - 15 files changed, 12 insertions(+), 21 deletions(-) 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 dc8410962..287ca5986 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 @@ -13,7 +13,6 @@ * 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.Nodes @@ -24,7 +23,7 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Ret import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.Value import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Reference /** * Get all [Nodes] that are associated with this [DataItem]. @@ -74,5 +73,5 @@ private fun Node.getVariableInNextDFGOrThis(): Nodes = this.nextDFG .filter { next -> - next is DeclaredReferenceExpression || next is VariableDeclaration + next is Reference || next is VariableDeclaration }.ifEmpty { listOf(this) } 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 a8c3b6e08..667a55617 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 @@ -13,7 +13,6 @@ * 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.CpgFinding @@ -29,6 +28,7 @@ import de.fraunhofer.aisec.cpg.query.executionPath import kotlin.reflect.full.findAnnotation context(de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend) +@Suppress("complexity.TooManyFunctions") class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(premise) { private val defaultFailMessage: String = "" @@ -81,9 +81,9 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem when (location.scope) { Scope.Function -> { when (location.direction) { - Direction.afterwards -> TODO() - Direction.before -> TODO() - Direction.somewhere -> TODO() + Direction.Afterwards -> TODO() + Direction.Before -> TODO() + Direction.Somewhere -> TODO() } } Scope.Block -> TODO() diff --git a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt index c2b22240a..b72d0194e 100644 --- a/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt +++ b/codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/modelling/CpgBackendDataItem.kt @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package de.fraunhofer.aisec.codyze.backends.cpg.coko.modelling import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.BackendDataItem -import de.fraunhofer.aisec.cpg.graph.HasType import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.evaluate import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.types.HasType /** Implementation of the [BackendDataItem] interface for the CPG */ class CpgBackendDataItem( diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt index ec92a7301..86ced94a9 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/WheneverEvaluator.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Condition @@ -49,7 +48,7 @@ class CallLocationBuilder { data class CallLocation(val direction: Direction, val scope: Scope) enum class Direction { - afterwards, before, somewhere + Afterwards, Before, Somewhere } enum class Scope { diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt index 7ea34193c..79f345bb5 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Condition.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.ListBuilder @@ -22,6 +21,7 @@ import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling.* fun condition(block: Condition.() -> ConditionComponent) = Condition().run(block) /** This class exists to restrict where the functions can be called */ +@Suppress("complexity.TooManyFunctions") class Condition { /** Can be used to build a list like `list[x,y,z]` */ val list by lazy { ListBuilder() } diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt index 0822fccf4..e8b707603 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/Op.kt @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("TooManyFunctions") + package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoMarker diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt index 83b371580..552262745 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/dsl/TransformationResult.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl /** diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/BackendDataItem.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/BackendDataItem.kt index 56b0a78b2..07b269fae 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/BackendDataItem.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/BackendDataItem.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt index 189da0f1d..aea42f0c2 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/ConditionComponents.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op diff --git a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt index 6e4182424..f23e99ace 100644 --- a/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt +++ b/codyze-specification-languages/coko/coko-core/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/core/modelling/DataItem.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.modelling import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Op diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt index b5c60d8da..f3e268e05 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoScript.kt @@ -16,7 +16,6 @@ package de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend -import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Import import io.github.oshai.kotlinlogging.KotlinLogging import java.io.File import kotlin.script.experimental.annotations.KotlinScript diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt index 1b0a3c1cc..54313962b 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/CokoExecutor.kt @@ -135,6 +135,7 @@ class CokoExecutor(private val configuration: CokoConfiguration, private val bac * @return A [SpecEvaluator] object containing the extracted information from all * [specFiles] */ + @Suppress("complexity.CyclomaticComplexMethod") fun compileScriptsIntoSpecEvaluator( backend: CokoBackend, specFiles: List diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt index be726a3b0..c2a93f2f9 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl.host import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.GroupingOp diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt index f6e5e2db6..13cecd6cd 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/ConceptTranslationTest.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package de.fraunhofer.aisec.codyze.specificationLanguages.coko.dsl import de.fraunhofer.aisec.codyze.backends.cpg.CPGConfiguration @@ -41,7 +40,6 @@ class ConceptTranslationTest { CPGConfiguration( source = sourceFiles, useUnityBuild = false, - typeSystemActiveInFrontend = true, debugParser = false, disableCleanup = false, codeInNodes = true, diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/WheneverEvaluatorTest.kt b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/WheneverEvaluatorTest.kt index 9e2872290..5b5255a56 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/WheneverEvaluatorTest.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/WheneverEvaluatorTest.kt @@ -37,7 +37,6 @@ class WheneverEvaluatorTest { CPGConfiguration( source = sourceFiles, useUnityBuild = false, - typeSystemActiveInFrontend = true, debugParser = false, disableCleanup = false, codeInNodes = true, From 9ec8694f50071e3a930b5a2ef9f13832bf9a196d Mon Sep 17 00:00:00 2001 From: "Wendland, Florian" Date: Thu, 11 Apr 2024 10:47:38 +0200 Subject: [PATCH 24/25] Fix more detekt issues --- .../backends/cpg/coko/evaluators/CpgWheneverEvaluator.kt | 3 ++- .../specificationLanguages/coko/dsl/host/ConceptTranslator.kt | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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 667a55617..c6595faa0 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 @@ -90,7 +90,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem } } TODO() - EvaluationResult(nodes, emptyList(), Problems()) + // EvaluationResult(nodes, emptyList(), Problems()) } findings.addAll( generateFindings(premiseNode, ensuresToNodes + callAssertionsToNodes, passMessage, failMessage) @@ -187,6 +187,7 @@ class CpgWheneverEvaluator(premise: ConditionComponent) : WheneverEvaluator(prem /** * Finds the nodes that are represented by [value]. */ + @Suppress("style.UnusedParameter") private fun evaluateValueToCpgNodes(value: Value<*>, premiseNode: Node? = null): EvaluationResult { // We return a [DummyNode] here because Value objects don't use the information from the backend // for their transformation. diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt index c2a93f2f9..1c4ecca4c 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/host/ConceptTranslator.kt @@ -129,7 +129,7 @@ class ConceptTranslator { sb.append("(") // Find the definitions of the ops that are used for this opPointer - val opDefinitions = opNames.map { (Regex("\\s+op\\s+$it\\(.*\\)").find(conceptBody)?.value ?: "") } + val opDefinitions = opNames.map { (Regex("\\s+op\\s+$it\\(.*\\)").find(conceptBody)?.value.orEmpty()) } // Append parameters needed for the function which are built by combining // the parameters of the grouped ops sb.append(buildFunctionParameters(opDefinitions)) @@ -155,7 +155,7 @@ class ConceptTranslator { // Find out all needed parameters val functionParameters = opDefinitions.flatMap { opDefinition -> // find the parameters that are used for the op - val opParameters = Regex("\\(.*\\)").find(opDefinition)?.value ?: "" + val opParameters = Regex("\\(.*\\)").find(opDefinition)?.value.orEmpty() // remove the `(` and `)` and then split the parameters removeFirstAndLastChar(opParameters).split(Regex("\\s*,\\s*")) } From 3f38b66d2ec101af519280d34a8d956d2d8cb0cc Mon Sep 17 00:00:00 2001 From: Robert Haimerl Date: Mon, 15 Apr 2024 09:42:45 +0200 Subject: [PATCH 25/25] update user feedback on wrong spec input --- .../codyze/specificationLanguages/coko/dsl/cli/Validation.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/cli/Validation.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/cli/Validation.kt index f9be875cd..1654c725d 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/cli/Validation.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/cli/Validation.kt @@ -24,7 +24,7 @@ val Path.fileNameString: String fun validateSpec(spec: List): List { require(spec.all { it.isRegularFile() }) { "All given spec paths must be files." } require(spec.all { it.fileNameString.endsWith(".codyze.kts") || it.fileNameString.endsWith(".concepts") }) { - "All given specification files must be coko specification files (*.codyze.kts)." + "All given specification files must be coko specification files (*.codyze.kts) or concept files (*.concepts)." } return spec }