diff --git a/diktat-analysis.yml b/diktat-analysis.yml index f6250c87..d4b067ea 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -177,3 +177,9 @@ - name: DEBUG_PRINT enabled: true ignoreAnnotated: [ LoggerImpl ] +- name: WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR + enabled: false +- name: EMPTY_BLOCK_STRUCTURE_ERROR + enabled: false +- name: KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER + enabled: false diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt deleted file mode 100644 index 40234d6d..00000000 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt +++ /dev/null @@ -1,268 +0,0 @@ -package kotlinx.fuzz - -import java.nio.file.Path -import kotlin.io.path.Path -import kotlin.io.path.absolute -import kotlin.io.path.absolutePathString -import kotlin.math.max -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty -import kotlin.reflect.KProperty1 -import kotlin.reflect.full.memberProperties -import kotlin.reflect.jvm.isAccessible -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds - -/** - * Class that stores generals fuzzing configuration - * - * @param fuzzEngine - name of engine to be used. Default: "jazzer" - * @param hooks - apply fuzzing instrumentation. Default: true - * @param keepGoing - Maximum number of new and unique bugs to be discovered before finishing fuzzing. Duplicates of both old and new will not be counted. - * Value of 0 will mean that there are no limitations. Default: 1 - * @param instrument - glob patterns matching names of classes that should be instrumented for fuzzing - * @param customHookExcludes - Glob patterns matching names of classes that should not be instrumented with hooks - * @param workDir - Directory where the all fuzzing results will be stored. Default: `build/fuzz` - * @param dumpCoverage - Whether fuzzer will generate jacoco .exec files. - * @param logLevel - Logging level enabled for kotlinx.fuzz - * Default: true - * (custom and built-in). - * Default: empty list - * @param maxSingleTargetFuzzTime - max time to fuzz a single target. Default: 1 minute - * @param reproducerPath - Path to store reproducers. Default: `$workDir/reproducers` - * @param threads - Number of cpu threads to use for executing targets in parallel. Default `threads available for jvm / 2`, usually half of logical threads - */ -interface KFuzzConfig { - val fuzzEngine: String - val hooks: Boolean - val keepGoing: Long - val instrument: List - val customHookExcludes: List - val maxSingleTargetFuzzTime: Duration - val workDir: Path - val dumpCoverage: Boolean - val reproducerPath: Path - val logLevel: String - val threads: Int - - fun toPropertiesMap(): Map - - companion object { - fun fromSystemProperties(): KFuzzConfig = KFuzzConfigImpl.fromSystemProperties() - - fun fromPropertiesMap(properties: Map): KFuzzConfig = - KFuzzConfigImpl.fromPropertiesMap(properties) - } -} - -class KFuzzConfigImpl private constructor() : KFuzzConfig { - override var fuzzEngine: String by KFuzzConfigProperty( - SystemProperty.ENGINE, - defaultValue = "jazzer", - fromString = { it }, - toString = { it }, - ) - override var hooks: Boolean by KFuzzConfigProperty( - SystemProperty.HOOKS, - defaultValue = Defaults.HOOKS, - toString = { it.toString() }, - fromString = { it.toBooleanStrict() }, - ) - override var keepGoing: Long by KFuzzConfigProperty( - SystemProperty.KEEP_GOING, - defaultValue = Defaults.KEEP_GOING, - validate = { require(it >= 0) { "'keepGoing' must be positive" } }, - toString = { it.toString() }, - fromString = { it.toLong() }, - ) - override var instrument: List by KFuzzConfigProperty( - SystemProperty.INSTRUMENT, - toString = { it.joinToString(",") }, - fromString = { it.split(",") }, - ) - override var customHookExcludes: List by KFuzzConfigProperty( - SystemProperty.CUSTOM_HOOK_EXCLUDES, - defaultValue = emptyList(), - toString = { it.joinToString(",") }, - fromString = { it.split(",") }, - ) - override var maxSingleTargetFuzzTime: Duration by KFuzzConfigProperty( - SystemProperty.MAX_SINGLE_TARGET_FUZZ_TIME, - defaultValue = Duration.parse(Defaults.MAX_SINGLE_TARGET_FUZZ_TIME_STRING), - validate = { require(it.inWholeSeconds > 0) { "'maxSingleTargetFuzzTime' must be at least 1 second" } }, - toString = { it.inWholeSeconds.toString() }, - fromString = { it.toInt().seconds }, - ) - override var workDir: Path by KFuzzConfigProperty( - SystemProperty.WORK_DIR, - toString = { it.toString() }, - fromString = { Path(it).absolute() }, - ) - override var dumpCoverage: Boolean by KFuzzConfigProperty( - SystemProperty.DUMP_COVERAGE, - defaultValue = Defaults.DUMP_COVERAGE, - toString = { it.toString() }, - fromString = { it.toBooleanStrict() }, - ) - override var reproducerPath: Path by KFuzzConfigProperty( - SystemProperty.REPRODUCER_PATH, - toString = { it.absolutePathString() }, - fromString = { Path(it).absolute() }, - ) - override var logLevel: String by KFuzzConfigProperty( - SystemProperty.LOG_LEVEL, - defaultValue = "WARN", - validate = { require(it.uppercase() in listOf("TRACE", "INFO", "DEBUG", "WARN", "ERROR")) }, - toString = { it }, - fromString = { it }, - ) - override var threads: Int by KFuzzConfigProperty( - SystemProperty.THREADS, - defaultValue = max(1, Runtime.getRuntime().availableProcessors() / 2), - validate = { require(it > 0) { "'threads' must be positive" } }, - toString = { it.toString() }, - fromString = { it.toInt() }, - ) - - override fun toPropertiesMap(): Map = configProperties() - .associate { it.systemProperty.name to it.stringValue } - - private fun assertAllSet() { - configProperties().forEach { it.assertIsSet() } - } - - private fun validate() { - configProperties().forEach { it.validate() } - } - - companion object { - internal object Defaults { - const val KEEP_GOING = 0L - const val HOOKS = true - const val DUMP_COVERAGE = true - - // string for compatibility with annotations - const val MAX_SINGLE_TARGET_FUZZ_TIME_STRING = "1m" - } - - fun build(block: KFuzzConfigImpl.() -> Unit): KFuzzConfig = wrapConfigErrors { - KFuzzConfigImpl().apply { - block() - assertAllSet() - validate() - } - } - - internal fun fromSystemProperties(): KFuzzConfig = wrapConfigErrors { - KFuzzConfigImpl().apply { - configProperties().forEach { it.setFromSystemProperty() } - assertAllSet() - validate() - } - } - - internal fun fromPropertiesMap(properties: Map): KFuzzConfigImpl = - wrapConfigErrors { - KFuzzConfigImpl().apply { - configProperties().forEach { - val propertyKey = it.systemProperty.name - it.setFromString( - properties[propertyKey] ?: error("map missing property $propertyKey"), - ) - } - assertAllSet() - validate() - } - } - - internal fun fromAnotherConfig( - config: KFuzzConfig, - edit: KFuzzConfigImpl.() -> Unit, - ): KFuzzConfig = wrapConfigErrors { - fromPropertiesMap(config.toPropertiesMap()).apply { edit() } - } - } -} - -class ConfigurationException( - override val message: String?, - override val cause: Throwable? = null, -) : IllegalArgumentException() - -/** - * A delegate property class that manages a configuration option for KFuzz. - * - * Can be set only once per instance. - * `KFuzzConfigImpl.build` set it from `systemProperty` - * - * @param systemProperty the system property key associated with this configuration option - * @param defaultValue the default value for this configuration, if none is provided - * @param validate a function which asserts if the value is correct - * @param toString a function that converts the property value to its string representation - * @param fromString a function that converts a string value to the property value type - */ -internal class KFuzzConfigProperty internal constructor( - val systemProperty: SystemProperty, - val defaultValue: T? = null, - private val validate: (T) -> Unit = {}, - private val toString: (T) -> String, - private val fromString: (String) -> T, -) : ReadWriteProperty { - private val name: String = systemProperty.name.substringAfterLast('.') - private var cachedValue: T? = null - val stringValue: String get() = toString(get()) - - override fun getValue(thisRef: Any, property: KProperty<*>): T = get() - - override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { - cachedValue = value - } - - internal fun get(): T { - cachedValue ?: defaultValue?.let { cachedValue = it } ?: error("Option '$name' is not set") - return cachedValue!! - } - - internal fun validate(): Unit = validate(get()) - - internal fun assertIsSet() { - get() - } - - internal fun setFromSystemProperty() { - assertCanSet() - systemProperty.get()?.let { - cachedValue = fromString(it) - } ?: error("System property '$systemProperty' is not set") - } - - internal fun setFromString(stringValue: String) { - assertCanSet() - cachedValue = fromString(stringValue) - } - - private fun assertCanSet() { - cachedValue?.let { - error("Property '$name' is already set") - } - } -} - -private fun KProperty1.asKFuzzConfigProperty(delegate: KFuzzConfigImpl): KFuzzConfigProperty<*> { - this.isAccessible = true - return this.getDelegate(delegate)!! as KFuzzConfigProperty<*> -} - -@Suppress("TYPE_ALIAS") -private fun KFuzzConfigImpl.configProperties(): List> = - KFuzzConfigImpl::class.memberProperties - .map { it.asKFuzzConfigProperty(this) } - -private inline fun wrapConfigErrors(buildConfig: () -> T): T = try { - buildConfig() -} catch (e: Throwable) { - throw when (e) { - is ConfigurationException -> e - else -> ConfigurationException("cannot create config", e) - } -} diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzTest.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzTest.kt index 716607fc..2f507b44 100644 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzTest.kt +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzTest.kt @@ -1,6 +1,9 @@ package kotlinx.fuzz import kotlin.time.Duration +import kotlinx.fuzz.config.KFuzzConfig +import kotlinx.fuzz.config.KFuzzConfigBuilder +import kotlinx.fuzz.config.TargetConfig import org.junit.platform.commons.annotation.Testable /** @@ -20,31 +23,32 @@ import org.junit.platform.commons.annotation.Testable @Target(AnnotationTarget.FUNCTION, AnnotationTarget.ANNOTATION_CLASS) @Testable annotation class KFuzzTest( - val keepGoing: Long = KFuzzConfigImpl.Companion.Defaults.KEEP_GOING, - val maxFuzzTime: String = KFuzzConfigImpl.Companion.Defaults.MAX_SINGLE_TARGET_FUZZ_TIME_STRING, + val keepGoing: Long = TargetConfig.Defaults.KEEP_GOING, + val maxFuzzTime: String = TargetConfig.Defaults.MAX_FUZZ_TIME_STRING, val instrument: Array = [], val customHookExcludes: Array = [], - val dumpCoverage: Boolean = KFuzzConfigImpl.Companion.Defaults.DUMP_COVERAGE, + val dumpCoverage: Boolean = TargetConfig.Defaults.DUMP_COVERAGE, ) -fun KFuzzConfig.addAnnotationParams(annotation: KFuzzTest): KFuzzConfig = KFuzzConfigImpl.fromAnotherConfig(this) { - keepGoing = newUnlessDefault( - old = this@addAnnotationParams.keepGoing, - new = annotation.keepGoing, - default = KFuzzConfigImpl.Companion.Defaults.KEEP_GOING, - ) - maxSingleTargetFuzzTime = newUnlessDefault( - old = this@addAnnotationParams.maxSingleTargetFuzzTime, - new = Duration.parse(annotation.maxFuzzTime), - default = Duration.parse(KFuzzConfigImpl.Companion.Defaults.MAX_SINGLE_TARGET_FUZZ_TIME_STRING), - ) - instrument = this@addAnnotationParams.instrument + annotation.instrument - customHookExcludes = this@addAnnotationParams.customHookExcludes + annotation.customHookExcludes - dumpCoverage = newUnlessDefault( - old = this@addAnnotationParams.dumpCoverage, - new = annotation.dumpCoverage, - default = KFuzzConfigImpl.Companion.Defaults.DUMP_COVERAGE, - ) -} +fun KFuzzConfig.addAnnotationParams(annotation: KFuzzTest): KFuzzConfig = KFuzzConfigBuilder.fromAnotherConfig(this) + .editOverride { + target.keepGoing = newUnlessDefault( + old = this@addAnnotationParams.target.keepGoing, + new = annotation.keepGoing, + default = TargetConfig.Defaults.KEEP_GOING, + ) + target.maxFuzzTime = newUnlessDefault( + old = this@addAnnotationParams.target.maxFuzzTime, + new = Duration.parse(annotation.maxFuzzTime), + default = Duration.parse(TargetConfig.Defaults.MAX_FUZZ_TIME_STRING), + ) + global.instrument = this@addAnnotationParams.global.instrument + annotation.instrument + global.customHookExcludes = this@addAnnotationParams.global.customHookExcludes + annotation.customHookExcludes + target.dumpCoverage = newUnlessDefault( + old = this@addAnnotationParams.target.dumpCoverage, + new = annotation.dumpCoverage, + default = TargetConfig.Defaults.DUMP_COVERAGE, + ) + }.build() private fun newUnlessDefault(old: T, new: T, default: T): T = if (new == default) old else new diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/SystemProperty.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/SystemProperty.kt deleted file mode 100644 index e70c44c0..00000000 --- a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/SystemProperty.kt +++ /dev/null @@ -1,24 +0,0 @@ -package kotlinx.fuzz - -enum class SystemProperty(name: String) { - CUSTOM_HOOK_EXCLUDES("kotlinx.fuzz.customHookExcludes"), - DUMP_COVERAGE("kotlinx.fuzz.dumpCoverage"), - ENGINE("kotlinx.fuzz.engine"), - HOOKS("kotlinx.fuzz.hooks"), - INSTRUMENT("kotlinx.fuzz.instrument"), - INTELLIJ_DEBUGGER_DISPATCH_PORT("idea.debugger.dispatch.port"), - JAZZER_ENABLE_LOGGING("kotlinx.fuzz.jazzer.enableLogging"), - JAZZER_LIBFUZZERARGS_RSS_LIMIT_MB("kotlinx.fuzz.jazzer.libFuzzerArgs.rssLimitMb"), - KEEP_GOING("kotlinx.fuzz.keepGoing"), - LOG_LEVEL("kotlinx.fuzz.log.level"), - MAX_SINGLE_TARGET_FUZZ_TIME("kotlinx.fuzz.maxSingleTargetFuzzTime"), - REGRESSION("kotlinx.fuzz.regression"), - REPRODUCER_PATH("kotlinx.fuzz.reproducerPath"), - THREADS("kotlinx.fuzz.threads"), - WORK_DIR("kotlinx.fuzz.workDir"), - ; - - fun get(): String? = System.getProperty(name) - - fun get(default: String): String = System.getProperty(name, default) -} diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/CoverageConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/CoverageConfig.kt new file mode 100644 index 00000000..601c2c49 --- /dev/null +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/CoverageConfig.kt @@ -0,0 +1,27 @@ +package kotlinx.fuzz.config + +private const val NAME_PREFIX = "coverage" + +interface CoverageConfig { + val reportTypes: Set + val includeDependencies: Set +} + +class CoverageConfigImpl internal constructor(builder: KFuzzConfigBuilder) : CoverageConfig { + override var reportTypes: Set by builder.KFuzzPropProvider( + nameSuffix = "$NAME_PREFIX.reportTypes", + intoString = { it.joinToString(",") }, + fromString = { it.split(",").map { CoverageReportType.valueOf(it) }.toSet() }, + default = setOf(CoverageReportType.HTML), + ) + override var includeDependencies: Set by builder.KFuzzPropProvider( + nameSuffix = "$NAME_PREFIX.includeDependencies", + intoString = { it.joinToString(",") }, + fromString = { it.split(",").toSet() }, + default = emptySet(), + ) +} + +enum class CoverageReportType { + CSV, HTML, XML; +} diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/EngineConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/EngineConfig.kt new file mode 100644 index 00000000..8e3b140d --- /dev/null +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/EngineConfig.kt @@ -0,0 +1,19 @@ +package kotlinx.fuzz.config + +private const val NAME_PREFIX = "jazzer" + +sealed interface EngineConfig + +interface JazzerConfig : EngineConfig { + val libFuzzerRssLimitMb: Int +} + +class JazzerConfigImpl internal constructor(builder: KFuzzConfigBuilder) : JazzerConfig { + override var libFuzzerRssLimitMb: Int by builder.KFuzzPropProvider( + "$NAME_PREFIX.libFuzzerArgs.rssLimitMb", + intoString = { it.toString() }, + fromString = { it.toInt() }, + validate = { require(it >= 0) { "rssLimit must be positive!" } }, + default = 0, + ) +} diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/GlobalConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/GlobalConfig.kt new file mode 100644 index 00000000..6ca0b0b2 --- /dev/null +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/GlobalConfig.kt @@ -0,0 +1,78 @@ +package kotlinx.fuzz.config + +import java.nio.file.Path +import kotlin.io.path.Path +import kotlin.io.path.absolute +import kotlin.io.path.absolutePathString +import kotlin.math.max + +interface GlobalConfig { + val workDir: Path + val reproducerDir: Path + val instrument: List + val customHookExcludes: List + val hooks: Boolean + val logLevel: LogLevel + val regressionEnabled: Boolean + val detailedLogging: Boolean + val threads: Int +} + +class GlobalConfigImpl internal constructor(builder: KFuzzConfigBuilder) : GlobalConfig { + override var workDir: Path by builder.KFuzzPropProvider( + nameSuffix = "workDir", + intoString = { it.absolutePathString() }, + fromString = { Path(it).absolute() }, + ) + override var logLevel: LogLevel by builder.KFuzzPropProvider( + nameSuffix = "logLevel", + intoString = { it.toString() }, + fromString = { LogLevel.valueOf(it) }, + default = LogLevel.WARN, + ) + override var reproducerDir: Path by builder.KFuzzPropProvider( + nameSuffix = "reproducerDir", + intoString = { it.absolutePathString() }, + fromString = { Path(it).absolute() }, + ) + override var instrument: List by builder.KFuzzPropProvider( + nameSuffix = "instrument", + intoString = { it.joinToString(",") }, + fromString = { it.split(",") }, + ) + override var customHookExcludes: List by builder.KFuzzPropProvider( + nameSuffix = "customHookExcludes", + intoString = { it.joinToString(",") }, + fromString = { it.split(",") }, + default = emptyList(), + ) + override var hooks: Boolean by builder.KFuzzPropProvider( + nameSuffix = "enableHooks", + intoString = { it.toString() }, + fromString = { it.toBooleanStrict() }, + default = true, + ) + override var regressionEnabled: Boolean by builder.KFuzzPropProvider( + nameSuffix = "regression", + intoString = { it.toString() }, + fromString = { it.toBooleanStrict() }, + default = false, + ) + override var detailedLogging: Boolean by builder.KFuzzPropProvider( + nameSuffix = "detailedLogging", + intoString = { it.toString() }, + fromString = { it.toBooleanStrict() }, + default = false, + ) + override var threads: Int by builder.KFuzzPropProvider( + nameSuffix = "threads", + intoString = { it.toString() }, + fromString = { it.toInt() }, + validate = { require(it > 0) { "'threads' must be positive" } }, + default = max(1, Runtime.getRuntime().availableProcessors() / 2), + ) +} + +enum class LogLevel { + DEBUG, ERROR, INFO, TRACE, WARN; +} diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/KFuzzConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/KFuzzConfig.kt new file mode 100644 index 00000000..c169874b --- /dev/null +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/KFuzzConfig.kt @@ -0,0 +1,18 @@ +package kotlinx.fuzz.config + +/** + * Configuration for fuzzing. Can be obtained via static methods or [KFuzzConfigBuilder] directly. + */ +interface KFuzzConfig { + val global: GlobalConfig + val target: TargetConfig + val engine: EngineConfig + val coverage: CoverageConfig + + fun toPropertiesMap(): Map + + companion object { + const val PROPERTY_NAME_PREFIX = "kotlinx.fuzz." + fun fromSystemProperties(): KFuzzConfig = KFuzzConfigBuilder(getSystemPropertiesMap()).build() + } +} diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/KFuzzConfigBuilder.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/KFuzzConfigBuilder.kt new file mode 100644 index 00000000..5a0fdf66 --- /dev/null +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/KFuzzConfigBuilder.kt @@ -0,0 +1,174 @@ +package kotlinx.fuzz.config + +import kotlin.reflect.KProperty + +/* +How to add a new property: + - If you want to add it to existing config set (e.g. global / target / ...): + - Add it to the interface and to interface Impl similary to existing ones + - Add it in plugin DSL if you want to let the user configure it + - If you want to add a new config set: + - Make a new interface and its impl, make sure impl takes [KFuzzConfigBuilder] as ctor argument + - Add it to [KFuzzConfig] interface + - Add its impl to [KFuzzConfigBuilder.KFuzzConfigImpl] + - If you want, add it to DSL + - If you add it to DSL, then also add it to [FuzzConfigDSLTest] + */ + +class KFuzzConfigBuilder( + private val propertiesMap: Map, +) { + private var isBuilt = false + + // FQN --> KFProp + private val delegatesMap = mutableMapOf>() + private val overrideSteps = mutableListOf Unit>() + private val fallbackSteps = mutableListOf Unit>() + private val configImpl = KFuzzConfigImpl() + + fun editOverride(editor: KFuzzConfigImpl.() -> Unit): KFuzzConfigBuilder = this.also { + overrideSteps.add(editor) + } + + fun editFallback(editor: KFuzzConfigImpl.() -> Unit): KFuzzConfigBuilder = this.also { + fallbackSteps.add(editor) + } + + fun build(): KFuzzConfig { + check(!isBuilt) { "config is already built!" } + try { + /* + To guarantee the priority in the following order... + 1) editOverride + 2) property map + 3) editFallback + 4) default + ...we can set values in backwards order. + */ + val delegates = delegatesMap.values + delegates.forEach { it.setFromDefault() } + fallbackSteps.forEach { it.invoke(configImpl) } + delegates.forEach { it.setFromPropertiesMap() } + overrideSteps.forEach { it.invoke(configImpl) } + + delegates.forEach { it.validate() } + isBuilt = true + return configImpl + } catch (e: Throwable) { + throw ConfigurationException(e) + } + } + + @Suppress("UNCHECKED_CAST") + fun getPropertyDelegate(propertySelector: KFuzzConfigImpl.() -> KProperty): KFuzzProperty { + val property = propertySelector(configImpl) + + val kfuzzProperty = delegatesMap[property.toString()] ?: error("no KFProp found for property '${property.name}'") + return kfuzzProperty as KFuzzProperty + } + + /** + * A fuzzing config property. The value is looked up with the following priority: + * + * 1) editOverride + * 2) property map + * 3) editFallback + * 4) default + * + * @param default throws + */ + inner class KFuzzProperty( + val name: String, + private val fromString: (String) -> T, + private val intoString: (T) -> String, + private val validate: (T) -> Unit, + private val default: T?, + ) { + private var value: T? = null + private val isBuilt get() = this@KFuzzConfigBuilder.isBuilt + + operator fun getValue(thisRef: Any?, property: KProperty<*>): T { + check(value != null) { "cannot get value, config is not built yet!" } + return value!! + } + + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + check(!isBuilt) { "cannot set value, config is already built!" } + this.value = value + } + + fun setFromPropertiesMap() { + propertiesMap[name]?.let { + value = fromString(it) + } + } + + fun setFromDefault() { + default?.let { + value = it + } + } + + fun getStringValue(): String { + check(isBuilt) { "cannot get string value, config is not built yet!" } + return intoString(value!!) + } + + fun validate() { + check(value != null) { "property '$name' was not set!" } + validate(value!!) + } + } + + inner class KFuzzConfigImpl internal constructor() : KFuzzConfig { + override val global = GlobalConfigImpl(this@KFuzzConfigBuilder) + override val target = TargetConfigImpl(this@KFuzzConfigBuilder) + override val coverage = CoverageConfigImpl(this@KFuzzConfigBuilder) + + // TODO: we have to know which engine to use before building the config... + // Viable solution for future: build a stub config, check the engine, build a new config. + // Will need to be careful with instantiating delegates, as they will get validated. + override val engine = JazzerConfigImpl(this@KFuzzConfigBuilder) + + override fun toPropertiesMap(): Map { + check(isBuilt) { "cannot get properties map, config is not built yet!" } + return delegatesMap.values.associate { it.name to it.getStringValue() } + } + } + + companion object { + fun fromAnotherConfig(config: KFuzzConfig): KFuzzConfigBuilder = KFuzzConfigBuilder(config.toPropertiesMap()) + } + + inner class KFuzzPropProvider( + private val nameSuffix: String, + private val intoString: (T) -> String, + private val fromString: (String) -> T, + private val validate: (T) -> Unit = {}, + private val default: T? = null, + ) { + operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): KFuzzProperty { + val kfuzzProperty = KFuzzProperty( + name = KFuzzConfig.PROPERTY_NAME_PREFIX + nameSuffix, + fromString = fromString, + intoString = intoString, + validate = validate, + default = default, + ) + delegatesMap[property.toString()] = kfuzzProperty + return kfuzzProperty + } + } +} + +class ConfigurationException(cause: Throwable?) : IllegalArgumentException("cannot create config: ${cause?.message}", cause) + +fun getSystemPropertiesMap(): Map = buildMap { + val properties = System.getProperties() + val propNames = properties.propertyNames() + for (name in propNames) { + val key = name.toString() + val value = properties.getProperty(key).toString() + put(key, value) + } +} diff --git a/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/TargetConfig.kt b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/TargetConfig.kt new file mode 100644 index 00000000..a4cba25b --- /dev/null +++ b/kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/config/TargetConfig.kt @@ -0,0 +1,38 @@ +package kotlinx.fuzz.config + +import kotlin.time.Duration + +interface TargetConfig { + val maxFuzzTime: Duration + val keepGoing: Long + val dumpCoverage: Boolean + + object Defaults { + const val MAX_FUZZ_TIME_STRING = "1m" + const val KEEP_GOING = 0L + const val DUMP_COVERAGE = true + } +} + +class TargetConfigImpl internal constructor(builder: KFuzzConfigBuilder) : TargetConfig { + override var maxFuzzTime: Duration by builder.KFuzzPropProvider( + nameSuffix = "maxFuzzTimePerTarget", + intoString = { it.toString() }, + fromString = { Duration.parse(it) }, + validate = { require(it.isPositive()) { "maxFuzzTimePerTarget must be positive" } }, + default = Duration.parse(TargetConfig.Defaults.MAX_FUZZ_TIME_STRING), + ) + override var keepGoing: Long by builder.KFuzzPropProvider( + nameSuffix = "keepGoing", + intoString = { it.toString() }, + fromString = { it.toLong() }, + validate = { require(it >= 0) { "keepGoing must be non-negative" } }, + default = TargetConfig.Defaults.KEEP_GOING, + ) + override var dumpCoverage: Boolean by builder.KFuzzPropProvider( + nameSuffix = "dumpCoverage", + intoString = { it.toString() }, + fromString = { it.toBooleanStrict() }, + default = TargetConfig.Defaults.DUMP_COVERAGE, + ) +} diff --git a/kotlinx.fuzz.api/src/test/kotlin/kotlinx/fuzz/KFuzzConfigTest.kt b/kotlinx.fuzz.api/src/test/kotlin/kotlinx/fuzz/KFuzzConfigTest.kt new file mode 100644 index 00000000..51980f34 --- /dev/null +++ b/kotlinx.fuzz.api/src/test/kotlin/kotlinx/fuzz/KFuzzConfigTest.kt @@ -0,0 +1,46 @@ +package kotlinx.fuzz + +import kotlin.io.path.Path +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.time.Duration +import kotlinx.fuzz.config.KFuzzConfigBuilder + +class KFuzzConfigTest { + @Test + fun testBasic() { + val props = mapOf( + "kotlinx.fuzz.workDir" to "tmp", + "kotlinx.fuzz.reproducerDir" to "tmp2", + "kotlinx.fuzz.instrument" to "", + ) + val config = KFuzzConfigBuilder(props) + .editFallback { + target.maxFuzzTime = Duration.parse("10s") + } + .build() + val actualWorkDirString = config.global.workDir.fileName.toString() + assertEquals("tmp", actualWorkDirString) + assertEquals(Duration.parse("10s"), config.target.maxFuzzTime) + } + + @Test + fun testEditAndCopy() { + val props = mapOf( + "kotlinx.fuzz.workDir" to "tmp", + "kotlinx.fuzz.reproducerDir" to "tmp2", + "kotlinx.fuzz.instrument" to "", + "kotlinx.fuzz.maxFuzzTimePerTarget" to "10s", + ) + val config = KFuzzConfigBuilder(props) + .editFallback { global.workDir = Path("bad") } + .build() + val configClone = KFuzzConfigBuilder.fromAnotherConfig(config) + .editOverride { target.maxFuzzTime = Duration.parse("5s") } + .build() + val actualWorkDirString = configClone.global.workDir.fileName.toString() + assertEquals("tmp", actualWorkDirString) + assertEquals(Duration.parse("10s"), config.target.maxFuzzTime) + assertEquals(Duration.parse("5s"), configClone.target.maxFuzzTime) + } +} diff --git a/kotlinx.fuzz.engine/src/main/kotlin/kotlinx/fuzz/log/LoggerFacade.kt b/kotlinx.fuzz.engine/src/main/kotlin/kotlinx/fuzz/log/LoggerFacade.kt index 18897959..775f6e39 100644 --- a/kotlinx.fuzz.engine/src/main/kotlin/kotlinx/fuzz/log/LoggerFacade.kt +++ b/kotlinx.fuzz.engine/src/main/kotlin/kotlinx/fuzz/log/LoggerFacade.kt @@ -1,6 +1,7 @@ package kotlinx.fuzz.log -import kotlinx.fuzz.SystemProperty +import kotlinx.fuzz.config.KFuzzConfig +import kotlinx.fuzz.config.LogLevel import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level @@ -9,9 +10,12 @@ import org.slf4j.event.Level * Custom logger facade that uses slf4j service provider if available and falls back to StdoutLogger if not */ object LoggerFacade { - val LOG_LEVEL = SystemProperty.LOG_LEVEL.get(Level.WARN.toString()) - .uppercase() - .let { levelName -> Level.entries.first { it.toString() == levelName } } + val LOG_LEVEL by lazy { + KFuzzConfig.fromSystemProperties() + .global + .logLevel + .toSLF4JLevel() + } private val isSlf4jAvailable: Boolean by lazy { val slf4jProviders = this::class.java.classLoader.getResource("org.slf4j.spi.SLF4JServiceProvider") ?.readText() @@ -30,3 +34,11 @@ object LoggerFacade { inline fun getLogger(): Logger = getLogger(T::class.java.name) } + +private fun LogLevel.toSLF4JLevel(): Level = when (this) { + LogLevel.TRACE -> Level.TRACE + LogLevel.DEBUG -> Level.DEBUG + LogLevel.INFO -> Level.INFO + LogLevel.WARN -> Level.WARN + LogLevel.ERROR -> Level.ERROR +} diff --git a/kotlinx.fuzz.engine/src/main/kotlin/kotlinx/fuzz/util.kt b/kotlinx.fuzz.engine/src/main/kotlin/kotlinx/fuzz/util.kt index 2d6dc797..60624d98 100644 --- a/kotlinx.fuzz.engine/src/main/kotlin/kotlinx/fuzz/util.kt +++ b/kotlinx.fuzz.engine/src/main/kotlin/kotlinx/fuzz/util.kt @@ -10,12 +10,13 @@ import java.lang.reflect.Method import java.nio.file.Path import kotlin.io.path.* import kotlinx.fuzz.KFuzzer.RegexConfiguration +import kotlinx.fuzz.config.KFuzzConfig fun String?.toBooleanOrTrue(): Boolean = this?.toBoolean() != false fun String?.toBooleanOrFalse(): Boolean = this?.toBoolean() == true fun KFuzzConfig.reproducerPathOf(method: Method): Path = - Path(reproducerPath.absolutePathString(), method.declaringClass.simpleName, method.name).absolute() + Path(global.reproducerDir.absolutePathString(), method.declaringClass.simpleName, method.name).absolute() fun Path.listCrashes(): List = if (this.exists()) listDirectoryEntries("{crash-*,timeout-*,slow-unit-*}") else emptyList() diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/FuzzConfigDSL.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/FuzzConfigDSL.kt new file mode 100644 index 00000000..e929860e --- /dev/null +++ b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/FuzzConfigDSL.kt @@ -0,0 +1,115 @@ +@file:Suppress("WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES", "KDOC_EXTRA_PROPERTY") + +package kotlinx.fuzz.gradle + +import kotlin.reflect.KProperty +import kotlinx.fuzz.config.KFuzzConfig +import kotlinx.fuzz.config.KFuzzConfigBuilder + +/** + * DSL for specifying fuzzing config. Sample usage: + * + * ```kotlin + * fuzzConfig { + * workDir = Path("fuzz-workdir") + * maxFuzzTimePerTarget = 1.hour + * coverage { + * reportTypes = setOf(CoverageReportType.HTML) + * } + * } + * ``` + * + * @property workDir Working directory for internal fuzzing files. Default: {buildDir}/fuzz + * @property reproducerDir Directory for crash reproducers. Default: {workDir}/reproducers + * @property hooks Whether to apply custom hooks (currently unsupported). Default: true + * @property logLevel Sets the logging level for kotlinx.fuzz library. Default: WARN + * @property detailedLogging Forwards logs from fuzzing engine. Default: false + * @property threads How many threads to use for parallel fuzzing. Default: #cpu_cores / 2 + * @property maxFuzzTimePerTarget Max time to fuzz each @KFuzzTest. Default: 1 minute + * @property keepGoing How many crashes to find before stopping fuzzing, or 0 for unlimited. Default: 0 + * @property instrument Which packages to instrument with coverage tracking. Should include your files. + * @property customHookExcludes In which packages NOT to apply custom hooks. Default: none + * @property dumpCoverage Whether to dump coverage data. Default: true + * @property coverage Section for specifying coverage params. See [CoverageConfigDSL] + * @property engine Section for specifying engine params (currently only Jazzer). See [JazzerConfigDSL] + * + * @see KFuzzConfig + */ +open class FuzzConfigDSL( + projectProperties: Map, +) { + private val builder = KFuzzConfigBuilder(projectProperties) + + // ========== global ========== + var workDir by KFConfigDelegate { global::workDir } + var reproducerDir by KFConfigDelegate { global::reproducerDir } + var hooks by KFConfigDelegate { global::hooks } + var logLevel by KFConfigDelegate { global::logLevel } + var detailedLogging by KFConfigDelegate { global::detailedLogging } + var threads by KFConfigDelegate { global::threads } + + // ========== target ========== + var maxFuzzTimePerTarget by KFConfigDelegate { target::maxFuzzTime } + var keepGoing by KFConfigDelegate { target::keepGoing } + + // TODO: default to project packages? + var instrument by KFConfigDelegate { global::instrument } + var customHookExcludes by KFConfigDelegate { global::customHookExcludes } + var dumpCoverage by KFConfigDelegate { target::dumpCoverage } + private val builtConfig: KFuzzConfig by lazy { builder.build() } + + fun build(): KFuzzConfig = builtConfig + + // ========== engine ========== + + sealed interface EngineConfigDSL + + /** + * @property libFuzzerRssLimit LibFuzzer rss limit parameter. Default: 0 + */ + inner class JazzerConfigDSL : EngineConfigDSL { + var libFuzzerRssLimit by KFConfigDelegate { engine::libFuzzerRssLimitMb } + } + + /** + * TODO: no support for different engines yet. See [KFuzzConfigBuilder.KFuzzConfigImpl] + */ + private val engineDSL = JazzerConfigDSL() + fun engine(block: JazzerConfigDSL.() -> Unit) { + engineDSL.block() + } + + // ========== coverage ========== + + /** + * @property reportTypes Which reports to generate. Default: HTML + * @property includeDependencies Which dependencies to calculate coverage for. Default: none + */ + @Suppress("USE_DATA_CLASS") + inner class CoverageConfigDSL { + var reportTypes by KFConfigDelegate { coverage::reportTypes } + var includeDependencies by KFConfigDelegate { coverage::includeDependencies } + } + + private val coverageDSL = CoverageConfigDSL() + + fun coverage(block: CoverageConfigDSL.() -> Unit) { + coverageDSL.block() + } + + // ========== internals ========== + + private inner class KFConfigDelegate( + propertySelector: KFuzzConfigBuilder.KFuzzConfigImpl.() -> KProperty, + ) { + private val kfProp = builder.getPropertyDelegate(propertySelector) + + operator fun getValue(thisRef: Any?, property: KProperty<*>): T = + kfProp.getValue(thisRef, property) + + // because editFallback is necessary, getValue() is not possible before building + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + builder.editFallback { kfProp.setValue(thisRef, property, value) } + } + } +} diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/JacocoConfig.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/JacocoConfig.kt deleted file mode 100644 index 60c22deb..00000000 --- a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/JacocoConfig.kt +++ /dev/null @@ -1,26 +0,0 @@ -package kotlinx.fuzz.gradle - -/** - * @param html - Enable HTML report (default: true) - * @param xml - Enable XML report (default: false) - * @param csv - Enable CSV report (default: false) - * @param includeDependencies - Dependencies to include in the report (default: empty set) - */ -open class JacocoConfig( - var html: Boolean = true, - var xml: Boolean = false, - var csv: Boolean = false, - var includeDependencies: Set = emptySet(), -) { - internal fun reportTypes(): Set = buildSet { - if (html) { - add(JacocoReport.HTML) - } - if (xml) { - add(JacocoReport.XML) - } - if (csv) { - add(JacocoReport.CSV) - } - } -} diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/JacocoReport.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/JacocoReport.kt deleted file mode 100644 index 06316273..00000000 --- a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/JacocoReport.kt +++ /dev/null @@ -1,5 +0,0 @@ -package kotlinx.fuzz.gradle - -internal enum class JacocoReport { - CSV, HTML, XML -} diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzConfigBuilder.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzConfigBuilder.kt deleted file mode 100644 index 52c75f03..00000000 --- a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzConfigBuilder.kt +++ /dev/null @@ -1,6 +0,0 @@ -package kotlinx.fuzz.gradle - -import kotlinx.fuzz.KFuzzConfigImpl - -@Suppress("TYPEALIAS_NAME_INCORRECT_CASE") -typealias KFuzzConfigBuilder = KFuzzConfigImpl diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzPlugin.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzPlugin.kt index eed0b8e1..4b28dfa9 100644 --- a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzPlugin.kt +++ b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/KFuzzPlugin.kt @@ -3,8 +3,8 @@ package kotlinx.fuzz.gradle import java.io.File import java.nio.file.Path import kotlin.io.path.createDirectories -import kotlinx.fuzz.KFuzzConfig -import kotlinx.fuzz.SystemProperty +import kotlinx.fuzz.config.KFuzzConfig +import kotlinx.fuzz.config.KFuzzConfigBuilder import kotlinx.fuzz.log.LoggerFacade import kotlinx.fuzz.log.warn import org.gradle.api.Plugin @@ -12,13 +12,18 @@ import org.gradle.api.Project import org.gradle.api.file.FileCollection import org.gradle.api.logging.Logging import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.options.Option import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.* +private val Project.fuzzConfig: KFuzzConfig + get() { + val dsl = this.extensions.getByType() + return dsl.build() + } + @Suppress("unused") abstract class KFuzzPlugin : Plugin { val log = Logging.getLogger(KFuzzPlugin::class.java)!! @@ -30,6 +35,10 @@ abstract class KFuzzPlugin : Plugin { add("testRuntimeOnly", "org.jetbrains:kotlinx.fuzz.gradle:$pluginVersion") } + val projectPropertiesMap: Map = project.properties.mapValues { (_, v) -> v.toString() } + val fuzzConfigDSL = project.extensions.create("fuzzConfig", projectPropertiesMap) + project.preconfigureFuzzConfigDSL(fuzzConfigDSL) + project.tasks.withType().configureEach { configureLogging() @@ -47,21 +56,21 @@ abstract class KFuzzPlugin : Plugin { project.registerRegressionTask(defaultCP, defaultTCD) } + private fun Project.preconfigureFuzzConfigDSL(dsl: FuzzConfigDSL) { + val buildDir = layout.buildDirectory.get() + val defaultWorkDir = buildDir.dir("fuzz").asFile.toPath() + dsl.workDir = defaultWorkDir + dsl.reproducerDir = defaultWorkDir.resolve("reproducers") + } + private fun Project.registerFuzzTask(defaultCP: FileCollection, defaultTCD: FileCollection) { - val jacocoConfigExtension = project.extensions.create("jacocoReport") project.tasks.register("fuzz") { classpath = defaultCP testClassesDirs = defaultTCD outputs.upToDateWhen { false } // so the task will run on every invocation - jacocoConfig = jacocoConfigExtension doFirst { systemProperties(fuzzConfig.toPropertiesMap()) - for (property in SystemProperty.values()) { - property.get()?.let { - systemProperties[property.name] = property.get() - } - } } useJUnitPlatform { includeEngines("kotlinx.fuzz") @@ -75,12 +84,10 @@ abstract class KFuzzPlugin : Plugin { testClassesDirs = defaultTCD outputs.upToDateWhen { false } doFirst { - systemProperties(fuzzConfig.toPropertiesMap() + (SystemProperty.REGRESSION.name to "true")) - for (property in SystemProperty.values()) { - property.get()?.let { - systemProperties[property.name] = property.get() - } - } + val regressionConfig = KFuzzConfigBuilder.fromAnotherConfig(fuzzConfig).editOverride { + global.regressionEnabled = true + }.build() + systemProperties(regressionConfig.toPropertiesMap()) } useJUnitPlatform { includeEngines("kotlinx.fuzz") @@ -116,17 +123,21 @@ abstract class KFuzzPlugin : Plugin { ?: run { log.warn("'fuzz' and 'regression' task was not able to inherit the 'classpath' and 'testClassesDirs' properties, as it found conflicting configurations") log.warn("Please, specify them manually in your gradle config using the following syntax:") - log.warn(""" + log.warn( + """ tasks.withType().configureEach { classpath = TODO() testClassesDirs = TODO() - }""".trimIndent(), + } + """.trimIndent(), ) - log.warn(""" + log.warn( + """ tasks.withType().configureEach { classpath = TODO() testClassesDirs = TODO() - }""".trimIndent(), + } + """.trimIndent(), ) project.files() to project.files() } @@ -142,12 +153,6 @@ abstract class FuzzTask : Test() { @get:Input var reportWithAllClasspath: Boolean = false - @get:Internal - internal lateinit var fuzzConfig: KFuzzConfig - - @get:Internal - lateinit var jacocoConfig: JacocoConfig - init { description = "Runs fuzzing" group = "verification" @@ -156,8 +161,8 @@ abstract class FuzzTask : Test() { @TaskAction fun action() { overallStats() - if (fuzzConfig.dumpCoverage) { - val workDir = fuzzConfig.workDir + if (project.fuzzConfig.target.dumpCoverage) { + val workDir = project.fuzzConfig.global.workDir val coverageMerged = workDir.resolve("merged-coverage.exec") jacocoMerge(workDir.resolve("coverage"), coverageMerged) @@ -167,12 +172,12 @@ abstract class FuzzTask : Test() { } private fun overallStats() { - val workDir = fuzzConfig.workDir + val workDir = project.fuzzConfig.global.workDir overallStats(workDir.resolve("stats"), workDir.resolve("overall-stats.csv")) } private fun jacocoReport(execFile: Path, workDir: Path) { - val extraDeps = getDependencies(jacocoConfig.includeDependencies) + val extraDeps = getDependencies(project.fuzzConfig.coverage.includeDependencies) val mainSourceSet = project.extensions.getByType()["main"] val runtimeClasspath = project.configurations["runtimeClasspath"].files @@ -187,7 +192,7 @@ abstract class FuzzTask : Test() { classPath = jacocoClassPath, sourceDirectories = sourceDirectories, reportDir = workDir.resolve("jacoco-report").createDirectories(), - reports = jacocoConfig.reportTypes(), + reports = project.fuzzConfig.coverage.reportTypes, ) } @@ -210,29 +215,8 @@ abstract class FuzzTask : Test() { } abstract class RegressionTask : Test() { - @get:Internal - internal lateinit var fuzzConfig: KFuzzConfig - init { description = "Runs regression tests" group = "verification" } } - -@Suppress("unused") -fun Project.fuzzConfig(block: KFuzzConfigBuilder.() -> Unit) { - val buildDir = layout.buildDirectory.get() - val defaultWorkDir = buildDir.dir("fuzz").asFile.toPath() - val config = KFuzzConfigBuilder.build { - workDir = defaultWorkDir - reproducerPath = defaultWorkDir.resolve("reproducers") - block() - } - - tasks.withType().forEach { task -> - task.fuzzConfig = config - } - tasks.withType().forEach { task -> - task.fuzzConfig = config - } -} diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/jacoco.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/jacoco.kt index e99e2f7c..cff6c2fb 100644 --- a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/jacoco.kt +++ b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/jacoco.kt @@ -5,6 +5,7 @@ import java.nio.file.Path import kotlin.io.path.inputStream import kotlin.io.path.listDirectoryEntries import kotlin.io.path.outputStream +import kotlinx.fuzz.config.CoverageReportType import org.jacoco.core.analysis.Analyzer import org.jacoco.core.analysis.CoverageBuilder import org.jacoco.core.tools.ExecFileLoader @@ -16,12 +17,12 @@ import org.jacoco.report.csv.CSVFormatter import org.jacoco.report.html.HTMLFormatter import org.jacoco.report.xml.XMLFormatter -private fun JacocoReport.toVisitor(reportDir: Path): IReportVisitor = when (this) { - JacocoReport.HTML -> HTMLFormatter().createVisitor(FileMultiReportOutput(reportDir.toFile())) - JacocoReport.XML -> +private fun CoverageReportType.toVisitor(reportDir: Path): IReportVisitor = when (this) { + CoverageReportType.HTML -> HTMLFormatter().createVisitor(FileMultiReportOutput(reportDir.toFile())) + CoverageReportType.XML -> XMLFormatter().createVisitor(reportDir.resolve("jacoco.xml").outputStream().buffered()) - JacocoReport.CSV -> + CoverageReportType.CSV -> CSVFormatter().createVisitor(reportDir.resolve("jacoco.csv").outputStream().buffered()) } @@ -56,7 +57,7 @@ internal fun jacocoReport( classPath: Set, sourceDirectories: Set, reportDir: Path, - reports: Set, + reports: Set, ) { val execLoader = ExecFileLoader() execFile.inputStream().buffered().use { execLoader.load(it) } diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/ClassTestDescriptor.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/ClassTestDescriptor.kt index 0162fd9b..d41e205e 100644 --- a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/ClassTestDescriptor.kt +++ b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/ClassTestDescriptor.kt @@ -1,7 +1,7 @@ package kotlinx.fuzz.gradle.junit -import kotlinx.fuzz.KFuzzConfig import kotlinx.fuzz.KFuzzTest +import kotlinx.fuzz.config.KFuzzConfig import org.junit.platform.commons.util.AnnotationUtils import org.junit.platform.commons.util.ReflectionUtils import org.junit.platform.engine.TestDescriptor diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/KotlinxFuzzJunitEngine.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/KotlinxFuzzJunitEngine.kt index 3bc64b70..3f7c5cbf 100644 --- a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/KotlinxFuzzJunitEngine.kt +++ b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/KotlinxFuzzJunitEngine.kt @@ -5,6 +5,7 @@ import java.net.URI import kotlin.reflect.KClass import kotlinx.coroutines.* import kotlinx.fuzz.* +import kotlinx.fuzz.config.* import kotlinx.fuzz.log.LoggerFacade import kotlinx.fuzz.log.debug import kotlinx.fuzz.log.info @@ -28,16 +29,12 @@ internal class KotlinxFuzzJunitEngine : TestEngine { KFuzzConfig.fromSystemProperties() } private val fuzzEngine: KFuzzEngine by lazy { - when (config.fuzzEngine) { - "jazzer" -> Class.forName("kotlinx.fuzz.jazzer.JazzerEngine") + when (config.engine) { + is JazzerConfig -> Class.forName("kotlinx.fuzz.jazzer.JazzerEngine") .getConstructor(KFuzzConfig::class.java).newInstance(config) as KFuzzEngine - - else -> throw AssertionError("Unsupported fuzzer engine!") } } - private val isRegression: Boolean by lazy { - SystemProperty.REGRESSION.get().toBooleanOrFalse() - } + private val isRegression: Boolean by lazy { config.global.regressionEnabled } override fun getId(): String = "kotlinx.fuzz" @@ -69,7 +66,7 @@ internal class KotlinxFuzzJunitEngine : TestEngine { val root = request.rootTestDescriptor fuzzEngine.initialise() - val dispatcher = Dispatchers.Default.limitedParallelism(config.threads, "kotlinx.fuzz") + val dispatcher = Dispatchers.Default.limitedParallelism(config.global.threads, "kotlinx.fuzz") runBlocking(dispatcher) { root.children.map { child -> async { executeImpl(request, child) } }.awaitAll() } diff --git a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/MethodRegressionTestDescriptor.kt b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/MethodRegressionTestDescriptor.kt index a8e8e53a..dac5cdbe 100644 --- a/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/MethodRegressionTestDescriptor.kt +++ b/kotlinx.fuzz.gradle/src/main/kotlin/kotlinx/fuzz/gradle/junit/MethodRegressionTestDescriptor.kt @@ -1,7 +1,7 @@ package kotlinx.fuzz.gradle.junit import java.lang.reflect.Method -import kotlinx.fuzz.KFuzzConfig +import kotlinx.fuzz.config.KFuzzConfig import kotlinx.fuzz.listCrashes import kotlinx.fuzz.reproducerPathOf import org.junit.platform.engine.TestDescriptor diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/AnnotationsTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/AnnotationsTest.kt index 7b8c6b30..6f2274ea 100644 --- a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/AnnotationsTest.kt +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/AnnotationsTest.kt @@ -4,23 +4,24 @@ import kotlin.reflect.KFunction import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.time.Duration.Companion.seconds -import kotlinx.fuzz.ConfigurationException import kotlinx.fuzz.KFuzzTest import kotlinx.fuzz.KFuzzer +import kotlinx.fuzz.config.ConfigurationException +import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.platform.engine.TestExecutionResult import org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod import org.junit.platform.testkit.engine.EngineTestKit -class AnnotationsTest { +object AnnotationsTest { @BeforeEach fun setup() { writeToSystemProperties { - maxSingleTargetFuzzTime = 10.seconds - instrument = listOf("kotlinx.fuzz.test.**") - workDir = kotlin.io.path.createTempDirectory("fuzz-test") - reproducerPath = workDir.resolve("reproducers") + target.maxFuzzTime = 10.seconds + global.instrument = listOf("kotlinx.fuzz.test.**") + global.workDir = kotlin.io.path.createTempDirectory("fuzz-test") + global.reproducerDir = global.workDir.resolve("reproducers") } } @@ -64,6 +65,10 @@ class AnnotationsTest { ) } + @AfterAll + @JvmStatic + fun cleanup() = cleanupSystemProperties() + private fun runMethodFuzz(method: KFunction<*>): TestExecutionResult { val methodFQN = "${AnnotationsTest::class.qualifiedName!!}#${method.name}(kotlinx.fuzz.KFuzzer)" return EngineTestKit diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt index 4eb12d23..a159bc94 100644 --- a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx.fuzz.gradle.junit.test/EngineTest.kt @@ -5,8 +5,9 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.fuzz.IgnoreFailures import kotlinx.fuzz.KFuzzTest import kotlinx.fuzz.KFuzzer -import kotlinx.fuzz.gradle.KFuzzConfigBuilder +import kotlinx.fuzz.config.KFuzzConfigBuilder import kotlinx.fuzz.gradle.junit.KotlinxFuzzJunitEngine +import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.platform.engine.discovery.DiscoverySelectors.selectClass @@ -79,11 +80,12 @@ object EngineTest { @BeforeEach fun setup() { writeToSystemProperties { - maxSingleTargetFuzzTime = 5.seconds - instrument = listOf("kotlinx.fuzz.test.**") - workDir = kotlin.io.path.createTempDirectory("fuzz-test") - reproducerPath = workDir.resolve("reproducers") - keepGoing = 2 + // subtle check that getValue() works before build() + global.workDir = kotlin.io.path.createTempDirectory("fuzz-test") + global.reproducerDir = global.workDir.resolve("reproducers") + target.maxFuzzTime = 5.seconds + global.instrument = listOf("kotlinx.fuzz.test.**") + target.keepGoing = 2 } } @@ -102,9 +104,26 @@ object EngineTest { it.started(startedTests).succeeded(successTests).failed(failedTests) } } + + // without cleanup, KFuzzConfigTest fails hehe + @AfterAll + @JvmStatic + fun cleanup() = cleanupSystemProperties() } -fun writeToSystemProperties(block: KFuzzConfigBuilder.() -> Unit) { - KFuzzConfigBuilder.build(block).toPropertiesMap() +fun writeToSystemProperties(config: KFuzzConfigBuilder.KFuzzConfigImpl.() -> Unit) { + KFuzzConfigBuilder(emptyMap()) + .editOverride(config) + .build() + .toPropertiesMap() .forEach { (key, value) -> System.setProperty(key, value) } } + +fun cleanupSystemProperties() { + val needsCleanup = listOf( + "kotlinx.fuzz.workDir", + "kotlinx.fuzz.reproducerDir", + "kotlinx.fuzz.instrument", + ) + needsCleanup.forEach { prop -> System.clearProperty(prop) } +} diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigBuilderTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigBuilderTest.kt index d49cb7ba..abf50198 100644 --- a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigBuilderTest.kt +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigBuilderTest.kt @@ -2,7 +2,7 @@ package kotlinx.fuzz.gradle.test import kotlin.io.path.Path import kotlin.time.Duration.Companion.seconds -import kotlinx.fuzz.gradle.KFuzzConfigBuilder +import kotlinx.fuzz.config.KFuzzConfigBuilder import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows @@ -11,46 +11,49 @@ object FuzzConfigBuilderTest { @Test fun `not initialize something`() { @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") - assertThrows { KFuzzConfigBuilder.build {} } + assertThrows { KFuzzConfigBuilder(emptyMap()).build() } } @Test fun `0 maxSingleTargetFuzzTime fails`() { assertThrows { - KFuzzConfigBuilder.build { - maxSingleTargetFuzzTime = 0.seconds - instrument = emptyList() - workDir = Path("test") - reproducerPath = Path("test") - } + KFuzzConfigBuilder(emptyMap()).editOverride { + target.maxFuzzTime = 0.seconds + global.instrument = emptyList() + global.workDir = Path("test") + global.reproducerDir = Path("test") + }.build() } } @Test fun `enough set`() { assertDoesNotThrow { - KFuzzConfigBuilder.build { - instrument = listOf("1", "2") - maxSingleTargetFuzzTime = 30.seconds - workDir = Path("test") - reproducerPath = Path("test") - } + KFuzzConfigBuilder(emptyMap()).editOverride { + global.instrument = listOf("1", "2") + target.maxFuzzTime = 30.seconds + global.workDir = Path("test") + global.reproducerDir = Path("test") + }.build() } } @Test fun `all set`() { assertDoesNotThrow { - KFuzzConfigBuilder.build { - fuzzEngine = "engine" - hooks = true - keepGoing = 339 - instrument = listOf() - customHookExcludes = listOf("exclude") - maxSingleTargetFuzzTime = 1000.seconds - workDir = Path("test") - reproducerPath = Path("test") - } + KFuzzConfigBuilder(emptyMap()).editOverride { + engine.apply { + libFuzzerRssLimitMb = 5 + } + global.detailedLogging = false + global.hooks = true + target.keepGoing = 339 + global.instrument = listOf() + global.customHookExcludes = listOf("exclude") + target.maxFuzzTime = 1000.seconds + global.workDir = Path("test") + global.reproducerDir = Path("test") + }.build() } } } diff --git a/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigDSLTest.kt b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigDSLTest.kt new file mode 100644 index 00000000..f5124419 --- /dev/null +++ b/kotlinx.fuzz.gradle/src/test/kotlin/kotlinx/fuzz/gradle/test/FuzzConfigDSLTest.kt @@ -0,0 +1,62 @@ +@file:Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") + +package kotlinx.fuzz.gradle.test + +import kotlin.io.path.Path +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.seconds +import kotlinx.fuzz.config.KFuzzConfigBuilder +import kotlinx.fuzz.config.LogLevel +import kotlinx.fuzz.gradle.FuzzConfigDSL +import org.junit.jupiter.api.assertDoesNotThrow + +object FuzzConfigDsltest { + @Test + fun basicTest() { + val dsl = object : FuzzConfigDSL(emptyMap()) {} + dsl.apply { + workDir = Path(".") + reproducerDir = Path(".") + instrument = emptyList() + } + val actualConfig = dsl.build() + val expectedConfig = KFuzzConfigBuilder(emptyMap()).editOverride { + global.workDir = Path(".") + global.reproducerDir = Path(".") + global.instrument = emptyList() + }.build() + assertEquals(expectedConfig.global.workDir, actualConfig.global.workDir) + } + + @Test + fun allDSLParameters() { + assertDoesNotThrow { + val dsl = object : FuzzConfigDSL(emptyMap()) {} + dsl.apply { + workDir = Path(".") + reproducerDir = Path(".") + hooks = true + logLevel = LogLevel.DEBUG + detailedLogging = true + threads = 5 + + maxFuzzTimePerTarget = 1.seconds + keepGoing = 5 + instrument = emptyList() + customHookExcludes = emptyList() + dumpCoverage = false + + engine { + libFuzzerRssLimit = 5 + } + + coverage { + reportTypes = emptySet() + includeDependencies = emptySet() + } + } + dsl.build() + } + } +} diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerConfig.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerConfig.kt deleted file mode 100644 index 3f762bd5..00000000 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerConfig.kt +++ /dev/null @@ -1,21 +0,0 @@ -package kotlinx.fuzz.jazzer - -import kotlinx.fuzz.SystemProperty - -/** - * Jazzer specific configuration properties - * - * @param libFuzzerRssLimit rss limit in MB, 0 by default - * @param enableLogging flag to enable jazzer logs, false by default - */ -data class JazzerConfig( - val libFuzzerRssLimit: Int, - val enableLogging: Boolean, -) { - companion object { - fun fromSystemProperties(): JazzerConfig = JazzerConfig( - libFuzzerRssLimit = SystemProperty.JAZZER_LIBFUZZERARGS_RSS_LIMIT_MB.get("0").toInt(), - enableLogging = SystemProperty.JAZZER_ENABLE_LOGGING.get("false").toBooleanStrict(), - ) - } -} diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt index b3cd9168..efa98195 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerEngine.kt @@ -12,30 +12,32 @@ import java.nio.file.Files import java.nio.file.Path import kotlin.concurrent.thread import kotlin.io.path.* -import kotlinx.fuzz.KFuzzConfig import kotlinx.fuzz.KFuzzEngine import kotlinx.fuzz.KFuzzTest -import kotlinx.fuzz.SystemProperty import kotlinx.fuzz.addAnnotationParams +import kotlinx.fuzz.config.JazzerConfig +import kotlinx.fuzz.config.KFuzzConfig import kotlinx.fuzz.log.LoggerFacade import kotlinx.fuzz.log.error +private const val INTELLIJ_DEBUGGER_DISPATCH_PORT_VAR_NAME = "idea.debugger.dispatch.port" + internal val Method.fullName: String get() = "${this.declaringClass.name}.${this.name}" internal val KFuzzConfig.corpusDir: Path - get() = workDir.resolve("corpus") + get() = global.workDir.resolve("corpus") internal val KFuzzConfig.logsDir: Path - get() = workDir.resolve("logs") + get() = global.workDir.resolve("logs") internal val KFuzzConfig.exceptionsDir: Path - get() = workDir.resolve("exceptions") + get() = global.workDir.resolve("exceptions") @Suppress("unused") class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { private val log = LoggerFacade.getLogger() - private val jazzerConfig = JazzerConfig.fromSystemProperties() + private val jazzerConfig = config.engine as JazzerConfig override fun initialise() { config.corpusDir.createDirectories() @@ -72,7 +74,8 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { val propertiesList = methodConfig.toPropertiesMap().map { (property, value) -> "-D$property=$value" } val debugOptions = if (isDebugMode()) { - getDebugSetup(SystemProperty.INTELLIJ_DEBUGGER_DISPATCH_PORT.get()!!.toInt(), method) + val intellijDebuggerDispatchPort = System.getProperty(INTELLIJ_DEBUGGER_DISPATCH_PORT_VAR_NAME)!!.toInt() + getDebugSetup(intellijDebuggerDispatchPort, method) } else { emptyList() } @@ -108,7 +111,7 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { private fun clusterCrashes() { val crashesForDeletion = mutableListOf() - Files.walk(config.reproducerPath) + Files.walk(config.global.reproducerDir) .filter { it.isDirectory() && it.name.startsWith("cluster-") } .map { it to it.listStacktraces() } .flatMap { (dir, files) -> files.stream().map { dir to it } } @@ -130,9 +133,10 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { } private fun collectStatistics() { - val statsDir = config.workDir.resolve("stats").createDirectories() + val statsDir = config.global.workDir.resolve("stats") + .createDirectories() config.logsDir.listDirectoryEntries("*.err").forEach { file -> - val csvText = jazzerLogToCsv(file, config.maxSingleTargetFuzzTime) + val csvText = jazzerLogToCsv(file, config.target.maxFuzzTime) statsDir.resolve("${file.nameWithoutExtension}.csv").writeText(csvText) } } @@ -142,12 +146,12 @@ class JazzerEngine(private val config: KFuzzConfig) : KFuzzEngine { val stdoutStream = config.logsDir.resolve(stdout).outputStream() val stderrStream = config.logsDir.resolve(stderr).outputStream() val stdoutThread = logProcessStream(process.inputStream, stdoutStream) { - if (jazzerConfig.enableLogging) { + if (config.global.detailedLogging) { log.info(it) } } val stderrThread = logProcessStream(process.errorStream, stderrStream) { - if (jazzerConfig.enableLogging) { + if (config.global.detailedLogging) { log.info(it) } } diff --git a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt index 1567a052..4223731d 100644 --- a/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt +++ b/kotlinx.fuzz.jazzer/src/main/kotlin/kotlinx/fuzz/jazzer/JazzerLauncher.kt @@ -17,7 +17,8 @@ import kotlin.reflect.full.memberFunctions import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.javaMethod import kotlin.system.exitProcess -import kotlinx.fuzz.KFuzzConfig +import kotlinx.fuzz.config.JazzerConfig +import kotlinx.fuzz.config.KFuzzConfig import kotlinx.fuzz.log.LoggerFacade import kotlinx.fuzz.log.debug import kotlinx.fuzz.log.error @@ -27,7 +28,7 @@ import org.jetbrains.casr.adapter.CasrAdapter object JazzerLauncher { private val log = LoggerFacade.getLogger() private val config = KFuzzConfig.fromSystemProperties() - private val jazzerConfig = JazzerConfig.fromSystemProperties() + private val jazzerConfig = config.engine as JazzerConfig private var oldRepresentatives: Int? = null // Number of clusters initially, so that keepGoing will depend inly on new findings set(value) { require(field == null && value != null) { "Number of old representatives should be set only once to a non-null value" } @@ -73,8 +74,8 @@ object JazzerLauncher { val currentCorpus = config.corpusDir.resolve(method.fullName) currentCorpus.createDirectories() - if (config.dumpCoverage) { - val coverageFile = config.workDir + if (config.target.dumpCoverage) { + val coverageFile = config.global.workDir .resolve("coverage") .createDirectories() .resolve("${method.fullName}.exec") @@ -84,9 +85,9 @@ object JazzerLauncher { libFuzzerArgs += currentCorpus.absolutePathString() libFuzzerArgs += reproducerPath.absolutePathString() - libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}" + libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimitMb}" libFuzzerArgs += "-artifact_prefix=${reproducerPath.absolute()}/" - libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}" + libFuzzerArgs += "-max_total_time=${config.target.maxFuzzTime.inWholeSeconds}" return libFuzzerArgs } @@ -128,18 +129,18 @@ object JazzerLauncher { } val currentRepresentatives = clusterCrashes(reproducerPath) - return config.keepGoing != 0L && currentRepresentatives - oldRepresentatives!! >= config.keepGoing + return config.target.keepGoing != 0L && currentRepresentatives - oldRepresentatives!! >= config.target.keepGoing } private fun initJazzer() { Log.fixOutErr(System.out, System.err) - Opt.hooks.setIfDefault(config.hooks) - Opt.instrumentationIncludes.setIfDefault(config.instrument) - Opt.customHookIncludes.setIfDefault(config.instrument) - Opt.customHookExcludes.setIfDefault(config.customHookExcludes) - Opt.keepGoing.setIfDefault(config.keepGoing) - Opt.reproducerPath.setIfDefault(config.reproducerPath.absolutePathString()) + Opt.hooks.setIfDefault(config.global.hooks) + Opt.instrumentationIncludes.setIfDefault(config.global.instrument) + Opt.customHookIncludes.setIfDefault(config.global.instrument) + Opt.customHookExcludes.setIfDefault(config.global.customHookExcludes) + Opt.keepGoing.setIfDefault(config.target.keepGoing) + Opt.reproducerPath.setIfDefault(config.global.reproducerDir.absolutePathString()) AgentInstaller.install(Opt.hooks.get()) diff --git a/kotlinx.fuzz.test/build.gradle.kts b/kotlinx.fuzz.test/build.gradle.kts index 708ded22..582fc129 100644 --- a/kotlinx.fuzz.test/build.gradle.kts +++ b/kotlinx.fuzz.test/build.gradle.kts @@ -1,4 +1,4 @@ -import kotlinx.fuzz.gradle.fuzzConfig +import kotlinx.fuzz.config.CoverageReportType import kotlin.time.Duration.Companion.seconds plugins { @@ -26,17 +26,14 @@ fuzzConfig { "kotlinx.collections.immutable.**", "kotlinx.serialization.**", ) - maxSingleTargetFuzzTime = 10.seconds -} - -jacocoReport { - csv = true - html = true - xml = true - includeDependencies = setOf( - "org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm", - "org.jetbrains.kotlinx:kotlinx-serialization-protobuf-jvm", - ) + maxFuzzTimePerTarget = 10.seconds + coverage { + reportTypes = setOf(CoverageReportType.HTML, CoverageReportType.CSV) + includeDependencies = setOf( + "org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm", + "org.jetbrains.kotlinx:kotlinx-serialization-protobuf-jvm", + ) + } } kotlin {