Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression test mode #34

Merged
merged 24 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ version = VERSION

repositories {
mavenCentral()
maven(url = "https://plan-maven.apal-research.com")
FerrumBrain marked this conversation as resolved.
Show resolved Hide resolved
AbdullinAM marked this conversation as resolved.
Show resolved Hide resolved
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ version = VERSION

repositories {
mavenCentral()
maven(url = "https://plan-maven.apal-research.com")
FerrumBrain marked this conversation as resolved.
Show resolved Hide resolved
}

dependencies {
Expand Down
8 changes: 4 additions & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
kotlin = "2.0.21"
diktat = "2.0.0"
jazzer = "0.22.1"
jazzer = "0.0.2"

junit-platform = "1.11.4"
junit-jupiter = "5.11.4"
Expand All @@ -24,9 +24,9 @@ gradle-publish = "1.2.1"
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
diktat-gradle-plugin = { module = "com.saveourtool.diktat:diktat-gradle-plugin", version.ref = "diktat" }

jazzer = { module = "com.code-intelligence:jazzer", version.ref = "jazzer" }
jazzer-junit = { module = "com.code-intelligence:jazzer-junit", version.ref = "jazzer" }
jazzer-api = { module = "com.code-intelligence:jazzer-api", version.ref = "jazzer" }
jazzer = { module = "org.jetbrains:jazzer", version.ref = "jazzer" }
jazzer-junit = { module = "org.jetbrains:jazzer-junit", version.ref = "jazzer" }
jazzer-api = { module = "org.jetbrains:jazzer-api", version.ref = "jazzer" }

reflections = { module = "org.reflections:reflections", version.ref = "reflections" }

Expand Down
30 changes: 25 additions & 5 deletions kotlinx.fuzz.api/src/main/kotlin/kotlinx/fuzz/KFuzzConfig.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package kotlinx.fuzz

import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
Expand All @@ -25,16 +24,20 @@ import kotlin.time.Duration.Companion.seconds
* (custom and built-in).
* Default: empty list
* @param maxSingleTargetFuzzTime - max time to fuzz a single target in seconds
* @param runModes - Set of modes to be run: each element can be regression or fuzzing. Default: regression, fuzzing
* @param reproducerPath - Path to store reproducers. Default: `$workDir/reproducers`
*/
interface KFuzzConfig {
val fuzzEngine: String
val hooks: Boolean
val keepGoing: Int
val keepGoing: Long
val instrument: List<String>
FerrumBrain marked this conversation as resolved.
Show resolved Hide resolved
val customHookExcludes: List<String>
val maxSingleTargetFuzzTime: Duration
val workDir: Path
val dumpCoverage: Boolean
val runModes: Set<RunMode>
val reproducerPath: Path

fun toPropertiesMap(): Map<String, String>

Expand All @@ -56,12 +59,12 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig {
toString = { it.toString() },
fromString = { it.toBooleanStrict() },
)
override var keepGoing: Int by KFuzzConfigProperty(
override var keepGoing: Long by KFuzzConfigProperty(
"kotlinx.fuzz.keepGoing",
defaultValue = 1,
validate = { require(it > 0) { "'keepGoing' must be positive" } },
toString = { it.toString() },
fromString = { it.toInt() },
fromString = { it.toLong() },
)
override var instrument: List<String> by KFuzzConfigProperty(
"kotlinx.fuzz.instrument",
Expand Down Expand Up @@ -91,6 +94,18 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig {
toString = { it.toString() },
fromString = { it.toBooleanStrict() },
)
override var runModes: Set<RunMode> by KFuzzConfigProperty(
"kotlinx.fuzz.runModes",
defaultValue = setOf(RunMode.REGRESSION, RunMode.FUZZING),
validate = { require(it.isNotEmpty()) { "runModes should not be empty" } },
toString = { it.joinToString(", ") },
fromString = { it.split(",").map { RunMode.valueOf(it.trim().uppercase()) }.toSet() },
)
override var reproducerPath: Path by KFuzzConfigProperty(
"kotlinx.fuzz.reproducerPath",
toString = { it.absolutePathString() },
fromString = { Path(it).absolute() },
)

override fun toPropertiesMap(): Map<String, String> = configProperties()
.associate { it.systemProperty to it.stringValue }
Expand Down Expand Up @@ -118,6 +133,10 @@ class KFuzzConfigImpl private constructor() : KFuzzConfig {
}
}

enum class RunMode {
FUZZING, REGRESSION
}

/**
* A delegate property class that manages a configuration option for KFuzz.
*
Expand All @@ -144,6 +163,7 @@ internal class KFuzzConfigProperty<T : Any> internal constructor(
override fun getValue(thisRef: Any, property: KProperty<*>): T = get()

override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
assertCanSet()
cachedValue = value
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.plan.research

import com.code_intelligence.jazzer.api.FuzzedDataProvider
import com.code_intelligence.jazzer.third_party.kotlin.reflect.jvm.internal.impl.resolve.constants.ArrayValue
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ fun Project.fuzzConfig(block: KFuzzConfigBuilder.() -> Unit) {
val defaultWorkDir = buildDir.dir("fuzz").asFile.toPath()
val config = KFuzzConfigBuilder.build {
workDir = defaultWorkDir
reproducerPath = defaultWorkDir.resolve("reproducers")
block()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import kotlin.time.Duration.Companion.seconds
import kotlinx.fuzz.IgnoreFailures
import kotlinx.fuzz.KFuzzTest
import kotlinx.fuzz.KFuzzer
import kotlinx.fuzz.RunMode
import kotlinx.fuzz.gradle.KFuzzConfigBuilder
import kotlinx.fuzz.gradle.junit.KotlinxFuzzJunitEngine
import org.junit.jupiter.api.BeforeEach
Expand Down Expand Up @@ -36,9 +37,11 @@ object EngineTest {
@BeforeEach
fun setup() {
writeToSystemProperties {
runModes = setOf(RunMode.FUZZING)
maxSingleTargetFuzzTime = 5.seconds
instrument = listOf("kotlinx.fuzz.test.**")
workDir = kotlin.io.path.createTempDirectory("fuzz-test")
reproducerPath = workDir.resolve("reproducers")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ object FuzzConfigBuilderTest {
maxSingleTargetFuzzTime = 0.seconds
instrument = emptyList()
workDir = Path("test")
reproducerPath = Path("test")
}
}
}
Expand All @@ -32,6 +33,7 @@ object FuzzConfigBuilderTest {
instrument = listOf("1", "2")
maxSingleTargetFuzzTime = 30.seconds
workDir = Path("test")
reproducerPath = Path("test")
}
}
}
Expand All @@ -47,6 +49,7 @@ object FuzzConfigBuilderTest {
customHookExcludes = listOf("exclude")
maxSingleTargetFuzzTime = 1000.seconds
workDir = Path("test")
reproducerPath = Path("test")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@
import java.lang.reflect.Method
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicReference
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.absolute
import kotlin.io.path.createDirectories
import kotlin.io.path.outputStream
import kotlin.io.path.*
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.KLoggerFactory
import kotlinx.fuzz.RunMode

object JazzerLauncher {
private val log = KLoggerFactory.getLogger(JazzerLauncher::class.java)
Expand Down Expand Up @@ -53,8 +51,10 @@
exitProcess(0)
}

@OptIn(ExperimentalPathApi::class)
fun runTarget(instance: Any, method: Method): Throwable? {
private fun countCrashes(reproducerPath: Path) =
reproducerPath.listDirectoryEntries("{crash-*,timeout-*,slow-imput-*}").size.toLong()
FerrumBrain marked this conversation as resolved.
Show resolved Hide resolved

private fun configure(reproducerPath: Path, method: Method): List<String> {
val libFuzzerArgs = mutableListOf("fake_argv0")
val currentCorpus = config.corpusDir.resolve(method.fullName)
currentCorpus.createDirectories()
Expand All @@ -70,16 +70,49 @@
}

libFuzzerArgs += currentCorpus.toString()
libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}"
libFuzzerArgs += "-rss_limit_mb=${jazzerConfig.libFuzzerRssLimit}"
libFuzzerArgs += "-artifact_prefix=${reproducerPath.toAbsolutePath()}/"
FerrumBrain marked this conversation as resolved.
Show resolved Hide resolved

var keepGoing = when(RunMode.REGRESSION) {
Fixed Show fixed Hide fixed
in config.runModes -> countCrashes(reproducerPath)
else -> 0
}
if (config.runModes.contains(RunMode.FUZZING)) {
libFuzzerArgs += "-max_total_time=${config.maxSingleTargetFuzzTime.inWholeSeconds}"
keepGoing += config.keepGoing
}

Opt.keepGoing.setIfDefault(keepGoing)

return libFuzzerArgs
}

@OptIn(ExperimentalPathApi::class)
fun runTarget(instance: Any, method: Method): Throwable? {
val reproducerPath =
Path(Opt.reproducerPath.get(), method.declaringClass.simpleName, method.name).absolute()
if (!reproducerPath.exists()) {
reproducerPath.createDirectories()
}

val libFuzzerArgs = configure(reproducerPath, method)

val atomicFinding = AtomicReference<Throwable>()
FuzzTargetRunner.registerFatalFindingHandlerForJUnit { finding ->
FuzzTargetRunner.registerFatalFindingHandlerForJUnit { _, finding ->
atomicFinding.set(finding)
}

JazzerTarget.reset(MethodHandles.lookup().unreflect(method), instance)
FuzzTargetRunner.startLibFuzzer(libFuzzerArgs)

if (config.runModes.contains(RunMode.REGRESSION)) {
reproducerPath.listDirectoryEntries("crash-*").forEach {
FerrumBrain marked this conversation as resolved.
Show resolved Hide resolved
FuzzTargetRunner.runOne(it.readBytes())
}
}

if (config.runModes.contains(RunMode.FUZZING)) {
FuzzTargetRunner.startLibFuzzer(libFuzzerArgs)
}

return atomicFinding.get()
}
Expand All @@ -91,6 +124,7 @@
Opt.instrumentationIncludes.setIfDefault(config.instrument)
Opt.customHookIncludes.setIfDefault(config.instrument)
Opt.customHookExcludes.setIfDefault(config.customHookExcludes)
Opt.reproducerPath.setIfDefault(config.reproducerPath.absolutePathString())

AgentInstaller.install(Opt.hooks.get())

Expand Down
6 changes: 6 additions & 0 deletions kotlinx.fuzz.test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import kotlinx.fuzz.RunMode
import kotlinx.fuzz.gradle.fuzzConfig
import kotlin.time.Duration.Companion.seconds

plugins {
kotlin("jvm") version "2.0.21"
id("kotlinx.fuzz.gradle")
kotlin("plugin.serialization") version "2.0.20"
}

repositories {
mavenCentral()
maven(url = "https://plan-maven.apal-research.com")
}

dependencies {
testImplementation(kotlin("test")) // adds green arrow in IDEA (no idea why)
testRuntimeOnly("org.jetbrains:kotlinx.fuzz.jazzer")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
}

fuzzConfig {
keepGoing = 10
runModes = setOf(RunMode.REGRESSION)
FerrumBrain marked this conversation as resolved.
Show resolved Hide resolved
instrument = listOf("kotlinx.fuzz.test.**")
maxSingleTargetFuzzTime = 10.seconds
}
Expand Down
Loading
Loading