Skip to content

Commit

Permalink
try aggregating the results
Browse files Browse the repository at this point in the history
  • Loading branch information
CodingDepot committed Jan 15, 2024
1 parent db140bb commit 4e51ce5
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package de.fraunhofer.aisec.codyze.cli
import com.github.ajalt.clikt.core.subcommands
import de.fraunhofer.aisec.codyze.core.backend.BackendCommand
import de.fraunhofer.aisec.codyze.core.executor.ExecutorCommand
import de.fraunhofer.aisec.codyze.plugin.aggregator.Aggregate
import de.fraunhofer.aisec.codyze.plugin.plugins.Plugin
import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.context.startKoin
Expand Down Expand Up @@ -49,6 +50,9 @@ fun main(args: Array<String>) {
codyzeCli.main(args)
}

// TODO: following code still expects first argument to be the executor...
// is this preprocessing and the distinction between Executor and ExecutorCommand really necessary?

// get the used subcommands
val executorCommand = codyzeCli.currentContext.invokedSubcommand as? ExecutorCommand<*>

Expand All @@ -63,11 +67,12 @@ fun main(args: Array<String>) {
val backend = backendCommand?.getBackend() // [null] if the chosen executor does not support modular backends
val executor = executorCommand.getExecutor(codyzeConfiguration.goodFindings, codyzeConfiguration.pedantic, backend)

// TODO: how do we get a correct order of operation?
// executor -> plugins -> output
// ideally independent of cli order
val run = executor.evaluate()
Aggregate.addRun(run)

// use the chosen [OutputBuilder] to convert the SARIF format (a SARIF RUN) from the executor to the chosen format
codyzeConfiguration.outputBuilder.toFile(run, codyzeConfiguration.output)

// aggregate into one SARIF
TODO("take the separate sarif files and aggregate them")
codyzeConfiguration.outputBuilder.toFile(Aggregate.createRun() ?: run, codyzeConfiguration.output)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,138 +21,124 @@ import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger { }

/**
* A class containing information about the aggregated SARIF run.
* A static class containing information about the aggregated SARIF run.
* Each external Tool will be listed as an extension while Codyze functions as the driver.
*/
class Aggregate {
private lateinit var driver: ToolComponent
private var extensions: List<ToolComponent> = listOf()
private var results: List<Result> = listOf()

private var containedRuns: Set<Run> = setOf()

/**
* Creates a new run from the information stored within the aggregate.
*/
fun createRun(): Run? {
// Prevent Exception from uninitialized driver
if (containedRuns.isEmpty()) {
logger.error { "Failed to create run from aggregate: No runs added yet" }
return null
}

logger.info { "Creating single run from aggregate consisting of ${containedRuns.size} runs" }
return Run(
tool = Tool(driver, extensions),
results = results
)
}

/**
* Adds a new run to the aggregate.The driver of the first run will be locked in as the driver for the aggregate.
* @param run The new SARIF run to add
* @return The aggregate after adding the run
*/
fun addRun(run: Run): Aggregate {
var modifiedRun: Run = run

// TODO: for uniqueness, we should use the rule property in the result
// this is a reportingDescriptorReference which contains information about the toolComponent
// https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790895
// We remove the ruleIndex and only keep ruleID or rule.id (one of both SHALL be present)
// Rule IDs need not necessarily be unique, therefore a rare conflict occurring is not severe
val newResults = run.results?.map {
if (it.ruleID != null) {
it.copy(ruleIndex = null, rule = null)
} else {
val rule = it.rule?.copy(index = null)
it.copy(ruleIndex = null, rule = rule)
companion object {

private lateinit var driver: ToolComponent
private var extensions: List<ToolComponent> = listOf()
private var results: List<Result> = listOf()

private var containedRuns: Set<Run> = setOf()

/**
* Creates a new run from the information stored within the aggregate.
*/
fun createRun(): Run? {
// Prevent Exception from uninitialized driver
if (containedRuns.isEmpty()) {
logger.error { "Failed to create run from aggregate: No runs added yet" }
return null
}

logger.info { "Creating single run from aggregate consisting of ${containedRuns.size} runs" }
return Run(
tool = Tool(driver, extensions),
results = results
)
}

// TODO: we cannot directly use the "invocation" properties of the components as they cannot be assigned to toolComponents
// but we could build our own invocation from the invocations we got (e.g. successful only if all invocations were successful)
/**
* Adds a new run to the aggregate.The driver of the first run will be locked in as the driver for the aggregate.

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.
* @param run The new SARIF run to add
* @return The aggregate after adding the run
*/
fun addRun(run: Run) {
val modifiedRun: Run = modifyResults(run)

// TODO: search the SARIF specs for "result management system", e.g. fingerprints
// TODO: we cannot directly use the "invocation" properties of the components as they cannot be assigned to toolComponents

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.
// but we could build our own invocation from the invocations we got (e.g. successful only if all invocations were successful)

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.

// TODO: check each possible property in a run for whether/how they can be used in the aggregate
// TODO: when we modify information (e.g. invocation) we need to check for references (invocationIndex in resultProvenance)!
// TODO: search the SARIF specs for "result management system", e.g. fingerprints

modifiedRun = modifiedRun.copy(results = newResults)
// TODO: check each possible property in a run for whether/how they can be used in the aggregate
// TODO: when we modify information (e.g. invocation) we need to check for references (invocationIndex in resultProvenance)!

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.

if (containedRuns.isEmpty()) {
driver = modifiedRun.tool.driver
} else {
extensions += modifiedRun.tool.driver
}
extensions += modifiedRun.tool.extensions ?: listOf()
results += modifiedRun.results ?: listOf()
containedRuns += modifiedRun
if (containedRuns.isEmpty()) {
driver = modifiedRun.tool.driver
} else {
extensions += modifiedRun.tool.driver
}
extensions += modifiedRun.tool.extensions ?: listOf()

Check warning

Code scanning / detekt

Use `orEmpty()` call instead of `?:` with empty collection factory methods Warning

This '?: listOf()' can be replaced with 'orEmpty()' call
results += modifiedRun.results ?: listOf()

Check warning

Code scanning / detekt

Use `orEmpty()` call instead of `?:` with empty collection factory methods Warning

This '?: listOf()' can be replaced with 'orEmpty()' call
containedRuns += modifiedRun

logger.info { "Added run from ${driver.name} to the aggregate" }
return this
}
logger.info { "Added run from ${driver.name} to the aggregate" }
}

/**
* This modifies the results object in a way that allows unique reference to the corresponding rule object within the aggregate.
* For this we need to populate the rule property in each result with the correct toolComponentReference or create the property from scratch.
* The properties result.ruleID and result.ruleIndex will be moved into the rule object if they exist.
* @param run The run which should have its results modified
* @return The run after applying the modifications
*/
private fun modifyResults(run: Run): Run {
var newResults = run.results?.map {
result ->
var newResult = result.copy(ruleIndex = null, ruleID = null)
if (result.rule != null) {
// rule property exists: keep id and index unchanged. ToolComponent must be set, otherwise it defaults to driver
val component = result.rule!!.toolComponent
// here we move result.ruleID and result.ruleIndex into the rule object if necessary
var newRule = result.rule!!.copy(
id = result.rule!!.id ?: result.ruleID,
index = result.rule!!.index ?: result.ruleIndex
)
if (component != null) {
// reference to component exists: fix index if necessary
if (component.index != null) {
val newComponent = component.copy(index = component.index!! + 1 + extensions.size)
/**
* This modifies the results object in a way that allows unique reference to the corresponding rule object within the aggregate.

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.
* For this we need to populate the rule property in each result with the correct toolComponentReference or create the property from scratch.

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.
* The properties result.ruleID and result.ruleIndex will be moved into the rule object if they exist.
* @param run The run which should have its results modified
* @return The run after applying the modifications
*/
private fun modifyResults(run: Run): Run {
val newResults = run.results?.map {
result ->
var newResult = result.copy(ruleIndex = null, ruleID = null)
if (result.rule != null) {
// rule property exists: keep id and index unchanged. ToolComponent must be set, otherwise it defaults to driver

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.
val component = result.rule!!.toolComponent

Check warning

Code scanning / detekt

Unsafe calls on nullable types detected. These calls will throw a NullPointerException in case the nullable value is null. Warning

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided.
// here we move result.ruleID and result.ruleIndex into the rule object if necessary
var newRule = result.rule!!.copy(

Check warning

Code scanning / detekt

Unsafe calls on nullable types detected. These calls will throw a NullPointerException in case the nullable value is null. Warning

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided.
id = result.rule!!.id ?: result.ruleID,

Check warning

Code scanning / detekt

Unsafe calls on nullable types detected. These calls will throw a NullPointerException in case the nullable value is null. Warning

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided.
index = result.rule!!.index ?: result.ruleIndex

Check warning

Code scanning / detekt

Unsafe calls on nullable types detected. These calls will throw a NullPointerException in case the nullable value is null. Warning

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided.
)
if (component != null) {
// reference to component exists: fix index if necessary
if (component.index != null) {
val newComponent = component.copy(index = component.index!! + 1 + extensions.size)

Check warning

Code scanning / detekt

Unsafe calls on nullable types detected. These calls will throw a NullPointerException in case the nullable value is null. Warning

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided.
newRule = newRule.copy(toolComponent = newComponent)
}
newResult = newResult.copy(rule = newRule)
} else {
// no reference to component: create new reference to the old driver (may now be an extension)
val newComponent = ToolComponentReference(
guid = run.tool.driver.guid,
index = if (containedRuns.isEmpty()) null else 1 + extensions.size.toLong(),
name = run.tool.driver.name
)
newRule = newRule.copy(toolComponent = newComponent)
newResult = newResult.copy(rule = newRule)
}
newResult = newResult.copy(rule = newRule)
} else {
// no reference to component: create new reference to the old driver (may now be an extension)
val newComponent = ToolComponentReference(
guid = run.tool.driver.guid,
index = if (containedRuns.isEmpty()) null else 1 + extensions.size.toLong(),
name = run.tool.driver.name
)
newRule = newRule.copy(toolComponent = newComponent)
newResult = newResult.copy(rule = newRule)
}
} else {
// rule property does not exist: create property that references the driver (no toolComponent-index)
val driverRules = run.tool.driver.rules
val rule = if (result.ruleIndex != null) driverRules?.get(result.ruleIndex!!.toInt()) else driverRules?.firstOrNull { it.id == result.ruleID }

// if no rule information is available at all, we can keep the result object unchanged
if (rule != null) {
val componentReference = ToolComponentReference(
guid = run.tool.driver.guid,
index = null,
name = run.tool.driver.name,
)
val ruleReference = ReportingDescriptorReference(
guid = rule.guid,
id = result.ruleID,
index = result.ruleIndex,
toolComponent = componentReference
)
newResult = newResult.copy(rule = ruleReference)
// rule property does not exist: create property that references the driver (no toolComponent-index)
val driverRules = run.tool.driver.rules
val rule = if (result.ruleIndex != null) driverRules?.get(result.ruleIndex!!.toInt()) else driverRules?.firstOrNull { it.id == result.ruleID }

Check warning

Code scanning / detekt

Reports lines with exceeded length Warning

Exceeded max line length (120)

Check warning

Code scanning / detekt

Reports incorrect argument list wrapping Warning

Argument should be on a separate line (unless all arguments can fit a single line)

Check warning

Code scanning / detekt

Reports incorrect argument list wrapping Warning

Missing newline before ")"

Check warning

Code scanning / detekt

Reports missing newlines (e.g. between parentheses of a multi-line function call Warning

Missing newline after "{"

Check warning

Code scanning / detekt

Unsafe calls on nullable types detected. These calls will throw a NullPointerException in case the nullable value is null. Warning

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided.

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.

// if no rule information is available at all, we can keep the result object unchanged
if (rule != null) {
val componentReference = ToolComponentReference(
guid = run.tool.driver.guid,
index = null,
name = run.tool.driver.name,
)
val ruleReference = ReportingDescriptorReference(
guid = rule.guid,
id = result.ruleID,
index = result.ruleIndex,
toolComponent = componentReference
)
newResult = newResult.copy(rule = ruleReference)
}
}
newResult
}
newResult
}

return run.copy(results = newResults)
return run.copy(results = newResults)
}
}
}

Check warning

Code scanning / detekt

Checks whether files end with a line separator. Warning

The file /home/runner/work/codyze/codyze/codyze-plugins/src/main/kotlin/de/fraunhofer/aisec/codyze/plugin/aggregator/Aggregate.kt is not ending with a new line.

Check warning

Code scanning / detekt

The class declaration is unnecessary because it only contains utility functions. An object declaration should be used instead. Warning

The class Aggregate only contains utility functions. Consider defining it as an object.
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,37 @@ import io.github.detekt.sarif4k.Run
import io.github.detekt.sarif4k.SarifSchema210
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import java.io.File

private val logger = KotlinLogging.logger { }

class Parser {
/**
* Extracts the last run from a valid SARIF result file
* @param resultFile The file containing the SARIF report
* @return Its last run or null on error
*/
fun extractLastRun(resultFile: File): Run? {
if (!resultFile.exists()) {
logger.error { "The SARIF file at \"${resultFile.canonicalPath}\" does not exist" }
return null
}

val serializer = Json {
ignoreUnknownKeys = true
}
/**

Check warning

Code scanning / detekt

Reports consecutive blank lines Warning

Needless blank line(s)
* Extracts the last run from a valid SARIF result file
* @param resultFile The file containing the SARIF report
* @return Its last run or null on error
*/
fun extractLastRun(resultFile: File): Run? {
if (!resultFile.exists()) {
logger.error { "The SARIF file at \"${resultFile.canonicalPath}\" does not exist" }
return null
}

val serializer = Json {
ignoreUnknownKeys = true
}

return try {
// We do not use sarif4k.SarifSerializer as it does not allow us to ignore unknown fields such as arbitrary content of rule.properties
val sarif = serializer.decodeFromString<SarifSchema210>(resultFile.readText())
sarif.runs.last()
} catch (e: SerializationException) {
logger.error { "Failed to serialize SARIF file at \"${resultFile.canonicalPath}\": ${e.localizedMessage}" }
null
} catch (e: Exception) {
logger.error { "Unexpected error while trying to parse SARIF at \"${resultFile.canonicalPath}\": ${e.localizedMessage}" }
null
}
return try {
// We do not use sarif4k.SarifSerializer as it does not allow us to ignore unknown fields such as arbitrary content of rule.properties

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.
val sarif = serializer.decodeFromString<SarifSchema210>(resultFile.readText())
sarif.runs.last()
} catch (e: SerializationException) {
logger.error { "Failed to serialize SARIF file at \"${resultFile.canonicalPath}\": ${e.localizedMessage}" }
null
} catch (e: Exception) {

Check warning

Code scanning / detekt

The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled. Warning

The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
logger.error { "Unexpected error while trying to parse SARIF at \"${resultFile.canonicalPath}\": ${e.localizedMessage}" }

Check warning

Code scanning / detekt

Reports lines with exceeded length Warning

Exceeded max line length (120)

Check warning

Code scanning / detekt

Reports missing newlines (e.g. between parentheses of a multi-line function call Warning

Missing newline after "{"

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.
null
}
}

Check warning

Code scanning / detekt

Checks whether files end with a line separator. Warning

The file /home/runner/work/codyze/codyze/codyze-plugins/src/main/kotlin/de/fraunhofer/aisec/codyze/plugin/aggregator/Parser.kt is not ending with a new line.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ class PMDPlugin: Plugin("PMD") {
config.isIgnoreIncrementalAnalysis = true

// from https://github.com/pmd/pmd/tree/master/pmd-core/src/main/resources/
config.addRuleSet("src/main/resources/pmd-rulesets/all-java.xml")
val ruleset = PMDPlugin::class.java.classLoader.getResource("pmd-rulesets/all-java.xml")
if (ruleset != null)
config.addRuleSet(ruleset.path)

Check warning

Code scanning / detekt

Detects multiline if-else statements without braces Warning

Missing { ... }

val analysis = PmdAnalysis.create(config)
analysis.performAnalysis()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ package de.fraunhofer.aisec.codyze.plugin.plugins

import com.github.ajalt.clikt.core.NoOpCliktCommand
import com.github.ajalt.clikt.parameters.groups.provideDelegate
import de.fraunhofer.aisec.codyze.plugin.aggregator.Aggregate
import de.fraunhofer.aisec.codyze.plugin.aggregator.extractLastRun
import io.github.oshai.kotlinlogging.KotlinLogging

Check warning

Code scanning / detekt

Detects unused imports Warning

Unused import
import java.io.File
import java.nio.file.Path

Expand All @@ -26,7 +29,7 @@ import java.nio.file.Path
* When developing a new Plugin, do not forget to add it to the respective [KoinModules],
* otherwise it will not be selectable in the configuration.
*/
abstract class Plugin(private val cliName: String) : NoOpCliktCommand(hidden = true, name = cliName) {
abstract class Plugin(private val cliName: String) : NoOpCliktCommand(hidden = true, name = cliName.lowercase()) {
private val options by PluginOptionGroup(cliName)

/**
Expand Down Expand Up @@ -55,5 +58,10 @@ abstract class Plugin(private val cliName: String) : NoOpCliktCommand(hidden = t
options.target,
options.output
)
if (!options.separate) {
val run = extractLastRun(options.output)
if (run != null)
Aggregate.addRun(run)

Check warning

Code scanning / detekt

Detects multiline if-else statements without braces Warning

Missing { ... }
}
}
}

Check warning

Code scanning / detekt

Checks whether files end with a line separator. Warning

The file /home/runner/work/codyze/codyze/codyze-plugins/src/main/kotlin/de/fraunhofer/aisec/codyze/plugin/plugins/Plugin.kt is not ending with a new line.

0 comments on commit 4e51ce5

Please sign in to comment.