diff --git a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoSarifBuilder.kt b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoSarifBuilder.kt index d4f024a7a..a26cd24b4 100644 --- a/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoSarifBuilder.kt +++ b/codyze-specification-languages/coko/coko-dsl/src/main/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoSarifBuilder.kt @@ -27,21 +27,24 @@ import kotlin.reflect.full.findAnnotation private fun CokoRule.toReportingDescriptor() = ReportingDescriptor( id = toString(), name = name, - shortDescription = findAnnotation()?.shortDescription?.let { desc -> - MultiformatMessageString( - text = desc - ) + shortDescription = findAnnotation()?.let { + MultiformatMessageString(text = it.shortDescription) + }, + fullDescription = findAnnotation()?.let { + MultiformatMessageString(text = it.description) + }, + defaultConfiguration = findAnnotation()?.let { + ReportingConfiguration(level = it.severity.toResultLevel()) }, - fullDescription = findAnnotation()?.description?.let { desc -> - MultiformatMessageString( - text = desc + help = findAnnotation()?.let { + MultiformatMessageString(text = it.help) + }, + properties = findAnnotation()?.let { + PropertyBag( + tags = it.tags.toSet(), + map = emptyMap() ) }, - defaultConfiguration = ReportingConfiguration(level = findAnnotation()?.severity?.toResultLevel()), - help = findAnnotation()?.help?.let { desc -> MultiformatMessageString(text = desc) }, - properties = PropertyBag( - tags = findAnnotation()?.tags?.toList() - ) // TODO: add precision, severity ) diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoSarifBuilderTest.kt b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoSarifBuilderTest.kt new file mode 100644 index 000000000..c4851241d --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/kotlin/de/fraunhofer/aisec/codyze/specificationLanguages/coko/dsl/CokoSarifBuilderTest.kt @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.fraunhofer.aisec.codyze.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.core.CokoRule +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Evaluator +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Severity +import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.toResultLevel +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.* +import org.junit.jupiter.api.Test +import kotlin.io.path.toPath +import kotlin.reflect.KParameter +import kotlin.reflect.KType +import kotlin.reflect.KTypeParameter +import kotlin.reflect.KVisibility + +class CokoSarifBuilderTest { + + private val cpgConfiguration = + CPGConfiguration( + source = emptyList(), + useUnityBuild = false, + 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), + ) + + private val cokoRulewithoutRuleAnnotation = object : CokoRule { + override val annotations: List + get() = emptyList() + override val name: String + get() = "norule" + + // remaining methods are not required in this test + override val isAbstract: Boolean + get() = throw UnsupportedOperationException("Not required for this test") + override val isExternal: Boolean + get() = throw UnsupportedOperationException("Not required for this test") + override val isFinal: Boolean + get() = throw UnsupportedOperationException("Not required for this test") + override val isInfix: Boolean + get() = throw UnsupportedOperationException("Not required for this test") + override val isInline: Boolean + get() = throw UnsupportedOperationException("Not required for this test") + override val isOpen: Boolean + get() = throw UnsupportedOperationException("Not required for this test") + override val isOperator: Boolean + get() = throw UnsupportedOperationException("Not required for this test") + override val isSuspend: Boolean + get() = throw UnsupportedOperationException("Not required for this test") + override val parameters: List + get() = throw UnsupportedOperationException("Not required for this test") + override val returnType: KType + get() = throw UnsupportedOperationException("Not required for this test") + override val typeParameters: List + get() = throw UnsupportedOperationException("Not required for this test") + override val visibility: KVisibility? + get() = throw UnsupportedOperationException("Not required for this test") + + override fun call(vararg args: Any?): Evaluator = + throw UnsupportedOperationException("Not required for this test") + + override fun callBy(args: Map): Evaluator = + throw UnsupportedOperationException("Not required for this test") + } + + @Test + fun `test empty rules list causes empty reportingDescriptors list`() { + val backend = CokoCpgBackend(cpgConfiguration) + val csb = CokoSarifBuilder(rules = emptyList(), backend = backend) + + assertTrue(csb.reportingDescriptors.isEmpty()) + } + + @Test + fun `test spec without rule annotation`() { + val backend = CokoCpgBackend(cpgConfiguration) + val csb = CokoSarifBuilder(rules = listOf(cokoRulewithoutRuleAnnotation), backend = backend) + + val reportingDescriptor = csb.reportingDescriptors.first() + assertNotNull(reportingDescriptor) + assertNull(reportingDescriptor.shortDescription) + assertNull(reportingDescriptor.fullDescription) + assertNull(reportingDescriptor.defaultConfiguration) + assertNull(reportingDescriptor.help) + assertNull(reportingDescriptor.properties) + } + + @Test + fun `test rule with default shortDescription`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/ruledefaults.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val shortDescription = csb.reportingDescriptors.first().shortDescription + assertNotNull(shortDescription) + assertTrue(shortDescription?.text!!.isEmpty()) + } + + @Test + fun `test rule with some shortDescription`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/ruleshortdescription.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val shortDescription = csb.reportingDescriptors.first().shortDescription + assertNotNull(shortDescription) + assertEquals(shortDescription?.text, "test") + } + + @Test + fun `test rule with default description`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/ruledefaults.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val fullDescription = csb.reportingDescriptors.first().fullDescription + assertNotNull(fullDescription) + assertTrue(fullDescription?.text!!.isEmpty()) + } + + @Test + fun `test rule with some description`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/rulefulldescription.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val fullDescription = csb.reportingDescriptors.first().fullDescription + assertNotNull(fullDescription) + assertEquals(fullDescription?.text, "some description") + } + + @Test + fun `test rule with default severity`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/ruledefaults.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val defaultConfiguration = csb.reportingDescriptors.first().defaultConfiguration + assertNotNull(defaultConfiguration) + + val level = defaultConfiguration?.level + assertNotNull(level) + assertTrue(level == Severity.WARNING.toResultLevel()) + } + + @Test + fun `test rule with some severity`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/ruleseverity.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val defaultConfiguration = csb.reportingDescriptors.first().defaultConfiguration + assertNotNull(defaultConfiguration) + + val level = defaultConfiguration?.level + assertNotNull(level) + assertTrue(level != Severity.WARNING.toResultLevel()) + } + + @Test + fun `test rule with default help`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/ruledefaults.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val help = csb.reportingDescriptors.first().help + assertNotNull(help) + assertTrue(help?.text!!.isEmpty()) + } + + @Test + fun `test rule with some help`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/rulehelp.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val help = csb.reportingDescriptors.first().help + assertNotNull(help) + assertEquals(help?.text, "some help") + } + + @Test + fun `test rule with default empty tags`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/ruledefaults.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val alternative = csb.reportingDescriptors.first().properties?.tags?.let { assertTrue(it.isEmpty()) } + assertNotNull(alternative) + } + + @Test + fun `test rules with some tags`() { + val specFiles = listOfNotNull( + CokoSarifBuilderTest::class.java.classLoader + .getResource("sarif/ruletags.codyze.kts") + ).map { it.toURI().toPath() } + + val backend = CokoCpgBackend(cpgConfiguration) + val specEvaluator = CokoExecutor.compileScriptsIntoSpecEvaluator(backend = backend, specFiles = specFiles) + val csb = CokoSarifBuilder(rules = specEvaluator.rules, backend = backend) + + val alternative = csb.reportingDescriptors.first().properties?.tags?.let { assertTrue(it.isNotEmpty()) } + assertNotNull(alternative) + } +} diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruledefaults.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruledefaults.codyze.kts new file mode 100644 index 000000000..11a51a51f --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruledefaults.codyze.kts @@ -0,0 +1,3 @@ +@Rule() +fun rule() = + Unit diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/rulefulldescription.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/rulefulldescription.codyze.kts new file mode 100644 index 000000000..6d768ee98 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/rulefulldescription.codyze.kts @@ -0,0 +1,3 @@ +@Rule(description = "some description") +fun rule() = + Unit diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/rulehelp.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/rulehelp.codyze.kts new file mode 100644 index 000000000..453db8131 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/rulehelp.codyze.kts @@ -0,0 +1,3 @@ +@Rule(help = "some help") +fun rule() = + Unit diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruleseverity.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruleseverity.codyze.kts new file mode 100644 index 000000000..47986abc1 --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruleseverity.codyze.kts @@ -0,0 +1,3 @@ +@Rule(severity = Severity.INFO) +fun rule() = + Unit diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruleshortdescription.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruleshortdescription.codyze.kts new file mode 100644 index 000000000..2ed066a0c --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruleshortdescription.codyze.kts @@ -0,0 +1,3 @@ +@Rule(shortDescription = "test") +fun rule() = + Unit diff --git a/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruletags.codyze.kts b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruletags.codyze.kts new file mode 100644 index 000000000..e63928dbf --- /dev/null +++ b/codyze-specification-languages/coko/coko-dsl/src/test/resources/sarif/ruletags.codyze.kts @@ -0,0 +1,3 @@ +@Rule(tags = ["one", "two"]) +fun rule() = + Unit diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dfde6d9db..4a2ce1731 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,15 @@ [versions] kotlin = "1.9.23" cpg = "8.1.2" -koin = "3.5.3" -koin-test = "3.5.3" +koin = "3.5.4" +koin-test = "3.5.4" detekt = "1.23.6" spotless = "6.25.0" dokka = "1.9.20" [libraries] -sarif4k = { module = "io.github.detekt.sarif4k:sarif4k", version = "0.5.0"} # The code can be found here: https://github.com/detekt/sarif4k. It was generated using https://app.quicktype.io/ +sarif4k = { module = "io.github.detekt.sarif4k:sarif4k", version = "0.6.0"} # The code can be found here: https://github.com/detekt/sarif4k. It was generated using https://app.quicktype.io/ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3"} kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"} @@ -28,7 +28,7 @@ cpg-language-java = { module = "de.fraunhofer.aisec:cpg-language-java", version. #cpg-analysis = { module = "com.github.Fraunhofer-AISEC.cpg:cpg-analysis", version.ref = "cpg"} #cpg-language-go = { module = "com.github.Fraunhofer-AISEC.cpg:cpg-language-go", version.ref = "cpg"} -kotlin-logging = { module = "io.github.oshai:kotlin-logging-jvm", version = "6.0.3" } +kotlin-logging = { module = "io.github.oshai:kotlin-logging-jvm", version = "6.0.4" } log4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version = "2.23.1"} clikt = { module = "com.github.ajalt.clikt:clikt", version = "4.3.0"} koin = { module = "io.insert-koin:koin-core", version.ref = "koin"}