diff --git a/settings.gradle.kts b/settings.gradle.kts index 3480b63ceb..7a0ec3e3c4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,6 +23,7 @@ include("utbot-intellij") include("utbot-sample") include("utbot-fuzzers") include("utbot-fuzzing") +include("utbot-greyboxfuzzer") include("utbot-junit-contest") include("utbot-analytics") include("utbot-analytics-torch") diff --git a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt index 3b789d25a8..358da93170 100644 --- a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt +++ b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt @@ -144,8 +144,21 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) : protected fun getWorkingDirectory(classFqn: String): Path? { val classRelativePath = classFqnToPath(classFqn) + ".class" val classAbsoluteURL = classLoader.getResource(classRelativePath) ?: return null - val classAbsolutePath = replaceSeparator(classAbsoluteURL.toPath().toString()) - .removeSuffix(classRelativePath) + val classAbsolutePath = + if (UtSettings.useGreyBoxFuzzing) { + if (classAbsoluteURL.toURI().scheme == "jar") { + replaceSeparator(classAbsoluteURL.file.removePrefix("file:")) + .removeSuffix(classRelativePath) + .removeSuffix("/") + .removeSuffix("!") + } else { + replaceSeparator(classAbsoluteURL.toPath().toString()) + .removeSuffix(classRelativePath) + } + } else { + replaceSeparator(classAbsoluteURL.toPath().toString()) + .removeSuffix(classRelativePath) + } return Paths.get(classAbsolutePath) } diff --git a/utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt index 6cc649f37f..d0f6a3ab0a 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt @@ -90,7 +90,10 @@ object FileUtil { for (clazz in classes) { val path = clazz.toClassFilePath() - val resource = clazz.classLoader.getResource(path) ?: error("No such file: $path") + val resource = + clazz.classLoader.getResource(path) + ?: ClassLoader.getSystemClassLoader().getResource(path) + ?: error("No such file: $path") if (resource.toURI().scheme == "jar") { val jarLocation = resource.toURI().extractJarName() diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index 02bf1bc3f3..68267044d3 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -242,6 +242,21 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS */ var useFuzzing: Boolean by getBooleanProperty(true) + /** + * Set to true to use grey-box fuzzing + */ + var useGreyBoxFuzzing: Boolean by getBooleanProperty(false) + + /** + * Set to true to use UtCompositeModels in grey-box fuzzing process + */ + var useCompositeModelsInGreyBoxFuzzing: Boolean by getBooleanProperty(false) + + /** + * Set to true to use grey-box fuzzing in competition mode (without asserts generation) + */ + var greyBoxFuzzingCompetitionMode: Boolean by getBooleanProperty(false) + /** * Set the total attempts to improve coverage by fuzzer. */ diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt index 20748404fa..07b7487d3d 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt @@ -1,5 +1,6 @@ package org.utbot.framework.plugin.api +import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.visible.UtStreamConsumingException import java.io.File import java.util.LinkedList @@ -69,6 +70,9 @@ class ConcreteExecutionFailureException(cause: Throwable, errorFile: File, val p appendLine("Cause:\n${cause.message}") appendLine("Last 1000 lines of the error log ${errorFile.absolutePath}:") appendLine("----------------------------------------") + if (!errorFile.exists()) { + errorFile.createNewFile() + } errorFile.useLines { lines -> val lastLines = LinkedList() for (line in lines) { @@ -102,6 +106,11 @@ inline fun UtExecutionResult.onFailure(action: (exception: Throwable) -> Unit): return this } +fun UtExecutionResult.getOrThrow(): UtModel = when (this) { + is UtExecutionSuccess -> model + is UtExecutionFailure -> throw exception +} + fun UtExecutionResult.exceptionOrNull(): Throwable? = when (this) { is UtExecutionFailure -> rootCauseException is UtExecutionSuccess -> null diff --git a/utbot-framework/build.gradle b/utbot-framework/build.gradle index a46be75524..be62217c6d 100644 --- a/utbot-framework/build.gradle +++ b/utbot-framework/build.gradle @@ -15,6 +15,7 @@ dependencies { api project(':utbot-summary') api project(':utbot-framework-api') api project(':utbot-rd') + api project(':utbot-greyboxfuzzer') implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: rdVersion implementation group: 'com.jetbrains.rd', name: 'rd-core', version: rdVersion diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 10a9138a8c..16327b99f8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -32,6 +32,8 @@ import org.utbot.framework.UtSettings.pathSelectorStepsLimit import org.utbot.framework.UtSettings.pathSelectorType import org.utbot.framework.UtSettings.processUnknownStatesDuringConcreteExecution import org.utbot.framework.UtSettings.useDebugVisualization +import org.utbot.framework.concrete.FuzzerConcreteExecutor +import org.utbot.framework.concrete.UtFuzzingExecutionInstrumentation import org.utbot.framework.util.convertToAssemble import org.utbot.framework.plugin.api.* import org.utbot.framework.plugin.api.Step @@ -41,13 +43,18 @@ import org.utbot.framework.util.sootMethod import org.utbot.fuzzer.* import org.utbot.fuzzing.* import org.utbot.fuzzing.utils.Trie +import org.utbot.greyboxfuzzer.GreyBoxFuzzer +import org.utbot.greyboxfuzzer.util.FuzzerUtModelConstructor import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor +import org.utbot.instrumentation.instrumentation.execution.phases.ValueConstructionContext import soot.jimple.Stmt import soot.tagkit.ParamNamesTag import java.lang.reflect.Method +import java.util.* import kotlin.system.measureTimeMillis val logger = KotlinLogging.logger {} @@ -334,7 +341,7 @@ class UtBotSymbolicEngine( fun fuzzing(until: Long = Long.MAX_VALUE, transform: (JavaValueProvider) -> JavaValueProvider = { it }) = flow { val isFuzzable = methodUnderTest.parameters.all { classId -> classId != Method::class.java.id && // causes the instrumented process crash at invocation - classId != Class::class.java.id // causes java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(Native Method) + classId != Class::class.java.id // causes java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(Native Method) } val hasMethodUnderTestParametersToFuzz = methodUnderTest.parameters.isNotEmpty() if (!isFuzzable || !hasMethodUnderTestParametersToFuzz && methodUnderTest.isStatic) { @@ -416,6 +423,42 @@ class UtBotSymbolicEngine( } } + //Simple fuzzing + fun greyBoxFuzzing(timeBudget: Long = Long.MAX_VALUE) = + flow { + val isFuzzable = methodUnderTest.parameters.all { classId -> + classId != Method::class.java.id // causes the child process crash at invocation + } + if (!isFuzzable) { + return@flow + } + val utModelConstructor = UtModelConstructor(IdentityHashMap()) + val fuzzerUtModelConstructor = FuzzerUtModelConstructor( + utModelConstructor::construct, + utModelConstructor::computeUnusedIdAndUpdate + ) + + try { + emitAll( + GreyBoxFuzzer( + methodUnderTest, + collectConstantsForGreyBoxFuzzer(methodUnderTest.sootMethod, utModelConstructor), + fuzzerUtModelConstructor, + FuzzerConcreteExecutor( + concreteExecutor.pathsToUserClasses + )::execute, + ValueConstructionContext(UtFuzzingExecutionInstrumentation.instrumentationContext, true)::constructParameters, + timeBudget + ).fuzz() + ) + } catch (e: CancellationException) { + logger.debug { "Cancelled by timeout" } + } catch (e: Throwable) { + emit(UtError("Unexpected fuzzing crash\n${e.stackTraceToString()}", e)) + } + return@flow + } + private suspend fun FlowCollector.emitFailedConcreteExecutionResult( stateBefore: EnvironmentModels, e: ConcreteExecutionFailureException @@ -556,7 +599,7 @@ private fun ResolvedModels.constructStateForMethod(methodUnderTest: ExecutableId return EnvironmentModels(thisInstanceBefore, paramsBefore, statics) } -private suspend fun ConcreteExecutor.executeConcretely( +internal suspend fun ConcreteExecutor.executeConcretely( methodUnderTest: ExecutableId, stateBefore: EnvironmentModels, instrumentation: List diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt index d3bf6bfbe4..cc942b7a7d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt @@ -70,7 +70,7 @@ object UtBotJavaApi { val concreteExecutor = ConcreteExecutor( UtExecutionInstrumentation, - classpath, + classpath ) testSets.addAll(generateUnitTests(concreteExecutor, methodsForGeneration, classUnderTest)) @@ -145,6 +145,7 @@ object UtBotJavaApi { dependencyClassPath: String, mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES, generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis, + isGreyBoxFuzzing: Boolean = false, primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null } ): MutableList { fun createPrimitiveModels(supplier: CustomFuzzerValueSupplier, classId: ClassId): Sequence = @@ -182,8 +183,12 @@ object UtBotJavaApi { chosenClassesToMockAlways = emptySet(), generationTimeoutInMillis, generate = { symbolicEngine -> - symbolicEngine.fuzzing { defaultModelProvider -> - customModelProvider.withFallback(defaultModelProvider) + if (isGreyBoxFuzzing) { + symbolicEngine.greyBoxFuzzing(generationTimeoutInMillis) + } else { + symbolicEngine.fuzzing { defaultModelProvider -> + customModelProvider.withFallback(defaultModelProvider) + } } } ) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt index 602aedd152..ba98a9fd4c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt @@ -45,6 +45,7 @@ interface CgElement { is CgAuxiliaryClass -> visit(element) is CgUtilMethod -> visit(element) is CgTestMethod -> visit(element) + is CgMockMethod -> visit(element) is CgErrorTestMethod -> visit(element) is CgParameterizedTestDataProviderMethod -> visit(element) is CgCommentedAnnotation -> visit(element) @@ -282,6 +283,17 @@ class CgTestMethod( override val requiredFields: List = emptyList(), ) : CgMethod(false) +class CgMockMethod( + override val name: String, + override val returnType: ClassId, + override val parameters: List, + override val statements: List, + override val exceptions: Set, + override val annotations: List, + override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList()), + override val requiredFields: List = emptyList(), +) : CgMethod(false) + class CgErrorTestMethod( override val name: String, override val statements: List, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt index c60e759cc9..a2b7056f05 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt @@ -74,6 +74,7 @@ import org.utbot.framework.codegen.domain.models.CgStaticFieldAccess import org.utbot.framework.codegen.domain.models.CgStaticRunnable import org.utbot.framework.codegen.domain.models.CgStaticsRegion import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgMockMethod import org.utbot.framework.codegen.domain.models.CgTestMethodCluster import org.utbot.framework.codegen.domain.models.CgThisInstance import org.utbot.framework.codegen.domain.models.CgThrowStatement @@ -97,6 +98,7 @@ import org.utbot.framework.plugin.api.util.isArray import org.utbot.framework.plugin.api.util.isRefType import org.utbot.framework.plugin.api.util.longClassId import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.isPublic abstract class CgAbstractRenderer( val context: CgRendererContext, @@ -245,6 +247,15 @@ abstract class CgAbstractRenderer( visit(element as CgMethod) } + override fun visit(element: CgMockMethod) { + renderMethodDocumentation(element) + for (annotation in element.annotations) { + annotation.accept(this) + } + renderMethodSignature(element) + visit(element as CgMethod) + } + override fun visit(element: CgErrorTestMethod) { renderMethodDocumentation(element) renderMethodSignature(element) @@ -749,6 +760,7 @@ abstract class CgAbstractRenderer( protected val maxParametersAmountInOneLine = 3 protected abstract fun renderMethodSignature(element: CgTestMethod) + protected abstract fun renderMethodSignature(element: CgMockMethod) protected abstract fun renderMethodSignature(element: CgErrorTestMethod) protected abstract fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt index f6b4ccdd02..b6371cac60 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt @@ -35,16 +35,13 @@ import org.utbot.framework.codegen.domain.models.CgClassBody import org.utbot.framework.codegen.domain.models.CgFormattedString import org.utbot.framework.codegen.domain.models.CgLiteral import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgMockMethod import org.utbot.framework.codegen.domain.models.CgTypeCast import org.utbot.framework.codegen.domain.models.CgVariable import org.utbot.framework.codegen.util.nullLiteral import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.TypeParameters -import org.utbot.framework.plugin.api.util.isFinal -import org.utbot.framework.plugin.api.util.isPrivate -import org.utbot.framework.plugin.api.util.isProtected -import org.utbot.framework.plugin.api.util.isPublic -import org.utbot.framework.plugin.api.util.wrapperByPrimitive +import org.utbot.framework.plugin.api.util.* internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = CgPrinterImpl()) : CgAbstractRenderer(context, printer) { @@ -241,6 +238,20 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C renderExceptions(element) } + + override fun renderMethodSignature(element: CgMockMethod) { + val returnType = element.returnType.asString() + print("public $returnType ") + print(element.name) + + print("(") + val newLinesNeeded = element.parameters.size > maxParametersAmountInOneLine + element.parameters.renderSeparated(newLinesNeeded) + print(")") + + renderExceptions(element) + } + override fun renderMethodSignature(element: CgErrorTestMethod) { // error test methods always have void return type println("public void ${element.name}()") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt index 98a7f32ee0..5c1102ea7a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt @@ -41,6 +41,7 @@ import org.utbot.framework.codegen.domain.models.CgClassBody import org.utbot.framework.codegen.domain.models.CgFormattedString import org.utbot.framework.codegen.domain.models.CgLiteral import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgMockMethod import org.utbot.framework.codegen.domain.models.CgTypeCast import org.utbot.framework.codegen.domain.models.CgValue import org.utbot.framework.codegen.domain.models.CgVariable @@ -403,6 +404,16 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = renderMethodReturnType(element) } + override fun renderMethodSignature(element: CgMockMethod) { + print("fun ") + print(element.name) + print("(") + val newLines = element.parameters.size > maxParametersAmountInOneLine + element.parameters.renderSeparated(newLines) + print(")") + renderMethodReturnType(element) + } + override fun renderMethodSignature(element: CgErrorTestMethod) { // error test methods always have void return type print("fun ") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt index 40e80e565c..d8023734d9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt @@ -79,6 +79,7 @@ import org.utbot.framework.codegen.domain.models.CgDocRegularLineStmt import org.utbot.framework.codegen.domain.models.CgFormattedString import org.utbot.framework.codegen.domain.models.CgNestedClassesRegion import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgMockMethod import org.utbot.framework.codegen.domain.models.CgTestMethodCluster import org.utbot.framework.codegen.domain.models.CgThisInstance import org.utbot.framework.codegen.domain.models.CgThrowStatement @@ -111,6 +112,7 @@ interface CgVisitor { // Methods fun visit(element: CgMethod): R fun visit(element: CgTestMethod): R + fun visit(element: CgMockMethod): R fun visit(element: CgErrorTestMethod): R fun visit(element: CgParameterizedTestDataProviderMethod): R diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/MockFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/MockFrameworkManager.kt index 87254a9829..0e8c05a7ce 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/MockFrameworkManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/MockFrameworkManager.kt @@ -20,16 +20,23 @@ import org.utbot.framework.codegen.domain.builtin.thenReturnMethodId import org.utbot.framework.codegen.domain.builtin.whenMethodId import org.utbot.framework.codegen.domain.context.CgContext import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgAnnotation import org.utbot.framework.codegen.domain.models.CgAnonymousFunction import org.utbot.framework.codegen.domain.models.CgAssignment +import org.utbot.framework.codegen.domain.models.CgClass +import org.utbot.framework.codegen.domain.models.CgClassBody import org.utbot.framework.codegen.domain.models.CgConstructorCall import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgDocumentationComment import org.utbot.framework.codegen.domain.models.CgExecutableCall import org.utbot.framework.codegen.domain.models.CgExpression import org.utbot.framework.codegen.domain.models.CgLiteral import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgMethodsCluster +import org.utbot.framework.codegen.domain.models.CgMockMethod import org.utbot.framework.codegen.domain.models.CgParameterDeclaration import org.utbot.framework.codegen.domain.models.CgRunnable +import org.utbot.framework.codegen.domain.models.CgSimpleRegion import org.utbot.framework.codegen.domain.models.CgStatement import org.utbot.framework.codegen.domain.models.CgStatementExecutableCall import org.utbot.framework.codegen.domain.models.CgStaticRunnable @@ -39,12 +46,15 @@ import org.utbot.framework.codegen.domain.models.CgValue import org.utbot.framework.codegen.domain.models.CgVariable import org.utbot.framework.codegen.services.access.CgCallableAccessManager import org.utbot.framework.codegen.services.access.CgCallableAccessManagerImpl +import org.utbot.framework.codegen.tree.* +import org.utbot.framework.codegen.tree.buildClassBody import org.utbot.framework.codegen.tree.CgStatementConstructor +import org.utbot.framework.codegen.tree.CgVariableConstructor import org.utbot.framework.codegen.tree.CgStatementConstructorImpl import org.utbot.framework.codegen.tree.CgTestClassConstructor.CgComponents.getVariableConstructorBy -import org.utbot.framework.codegen.tree.CgVariableConstructor import org.utbot.framework.codegen.tree.hasAmbiguousOverloadsOf import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.ExecutableId @@ -62,10 +72,13 @@ import org.utbot.framework.plugin.api.util.byteClassId import org.utbot.framework.plugin.api.util.charClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.longClassId import org.utbot.framework.plugin.api.util.shortClassId import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.framework.plugin.api.util.isRefType +import org.utbot.framework.plugin.api.util.exceptions abstract class CgVariableConstructorComponent(val context: CgContext) : CgContextOwner by context, @@ -116,6 +129,118 @@ abstract class CgVariableConstructorComponent(val context: CgContext) : argumentMatchersClassId[anyOfClass](getClassOf(id)) } +/** + * Experimental in-place mocker + */ +private class InPlaceMocker(context: CgContext) : ObjectMocker(context) { + + private var mockID = 0 + override fun createMock(model: UtCompositeModel, baseName: String): CgVariable { + val className = "${model.classId.simpleName}Mock$mockID" + val mockClassId = + BuiltinClassId( + canonicalName = className, + simpleName = className, + isPublic = false + ) + val methods = + model.mocks.entries.mapIndexed { modelIndex, (methodId, models) -> + CgMockMethod( + name = methodId.name, + returnType = methodId.returnType, + parameters = methodId.parameters.mapIndexed { i, param -> + CgParameterDeclaration( + parameter = declareParameter( + type = param, + name = "x$i", + ), + isReferenceType = param.isRefType + ) + }, + statements = block { + withNameScope { + if (methodId.returnType != voidClassId) { + +tryBlock { + val cachedModels = variableConstructor.valueByModelId.toMutableMap() + try { + variableConstructor.valueByModelId = mutableMapOf() + val returnStatements = + variableConstructor.getOrCreateVariable( + models.first(), + "mock${mockID}var$modelIndex" + ) + returnStatement { returnStatements } + } finally { + variableConstructor.valueByModelId = cachedModels + } + }.catch(Throwable::class.id) { + throwStatement { + val constructor = + IllegalStateException::class.java.id.allConstructors.first { it.parameters.isEmpty() } + constructor() + } + } + } + } + }, + exceptions = methodId.exceptions.toSet(), + annotations = emptyList() + ) + } + val methodsCluster = CgSimpleRegion(null, methods) + val classDeclaration = buildClass { + id = mockClassId + interfaces.add(model.classId) + body = buildClassBody(mockClassId) { + methodRegions += CgMethodsCluster(header = null, content = listOf(methodsCluster)) + } + } + currentBlock += CgClassAsStatement(classDeclaration) + ++mockID + val constructorId = ConstructorId(mockClassId, emptyList()) + return newVar(model.classId, baseName) { + CgConstructorCall(constructorId, emptyList()) + } + } + + override fun mock(clazz: CgExpression): CgMethodCall { + TODO("Not yet implemented") + } + + override fun `when`(call: CgExecutableCall): CgMethodCall { + TODO("Not yet implemented") + } + + private class CgClassAsStatement( + val id: ClassId, + val documentation: CgDocumentationComment?, + val annotations: List, + val superclass: ClassId?, + val interfaces: List, + val body: CgClassBody, + val isStatic: Boolean, + val isNested: Boolean + ) : CgStatement { + constructor(cgClass: CgClass) : this( + cgClass.id, + cgClass.documentation, + cgClass.annotations, + cgClass.superclass, + cgClass.interfaces, + cgClass.body, + cgClass.isStatic, + cgClass.isNested + ) + + val packageName + get() = id.packageName + + val simpleName + get() = id.simpleName + } + +} + class MockFrameworkManager(context: CgContext) : CgVariableConstructorComponent(context) { private val objectMocker = MockitoMocker(context) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt index ebc78ea8e2..8d98e8bcd5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt @@ -5,6 +5,7 @@ import org.utbot.common.WorkaroundReason import org.utbot.common.isStatic import org.utbot.common.workaround import org.utbot.engine.ArtificialError +import org.utbot.greyboxfuzzer.util.UtGreyBoxFuzzedExecution import org.utbot.framework.assemble.assemble import org.utbot.framework.codegen.domain.ForceStaticMocking import org.utbot.framework.codegen.domain.ParametrizedTestSource @@ -317,7 +318,9 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte } else { this.resultModel = resultModel val expected = variableConstructor.getOrCreateVariable(resultModel, "expected") - assertEquality(expected, actual) + if (currentExecution !is UtGreyBoxFuzzedExecution) { + assertEquality(expected, actual) + } } } .onFailure { exception -> processExecutionFailure(exception, executionResult) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt index 21d02a838c..cf5d44ed9d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt @@ -1,6 +1,7 @@ package org.utbot.framework.codegen.tree import org.utbot.common.isStatic +import org.utbot.framework.UtSettings import org.utbot.framework.codegen.domain.builtin.forName import org.utbot.framework.codegen.domain.builtin.setArrayElement import org.utbot.framework.codegen.domain.context.CgContext @@ -24,15 +25,10 @@ import org.utbot.framework.codegen.tree.CgTestClassConstructor.CgComponents.getC import org.utbot.framework.codegen.tree.CgTestClassConstructor.CgComponents.getMockFrameworkManagerBy import org.utbot.framework.codegen.tree.CgTestClassConstructor.CgComponents.getNameGeneratorBy import org.utbot.framework.codegen.tree.CgTestClassConstructor.CgComponents.getStatementConstructorBy -import org.utbot.framework.codegen.util.at +import org.utbot.framework.codegen.util.* import org.utbot.framework.codegen.util.canBeSetFrom import org.utbot.framework.codegen.util.fieldThatIsGotWith import org.utbot.framework.codegen.util.fieldThatIsSetWith -import org.utbot.framework.codegen.util.inc -import org.utbot.framework.codegen.util.isAccessibleFrom -import org.utbot.framework.codegen.util.lessThan -import org.utbot.framework.codegen.util.nullLiteral -import org.utbot.framework.codegen.util.resolve import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage @@ -51,19 +47,7 @@ import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.UtReferenceModel import org.utbot.framework.plugin.api.UtVoidModel -import org.utbot.framework.plugin.api.util.classClassId -import org.utbot.framework.plugin.api.util.defaultValueModel -import org.utbot.framework.plugin.api.util.jField -import org.utbot.framework.plugin.api.util.findFieldByIdOrNull -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.isArray -import org.utbot.framework.plugin.api.util.isEnum -import org.utbot.framework.plugin.api.util.isPrimitiveWrapperOrString -import org.utbot.framework.plugin.api.util.isStatic -import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.framework.plugin.api.util.supertypeOfAnonymousClass -import org.utbot.framework.plugin.api.util.wrapperByPrimitive +import org.utbot.framework.plugin.api.util.* /** * Constructs CgValue or CgVariable given a UtModel @@ -213,7 +197,23 @@ open class CgVariableConstructor(val context: CgContext) : is UtDirectSetFieldModel -> { val instance = declareOrGet(statementModel.instance) // fields here are supposed to be accessible, so we assign them directly without any checks - instance[statementModel.fieldId] `=` declareOrGet(statementModel.fieldModel) + if (UtSettings.useGreyBoxFuzzing) { + val fieldValue = declareOrGet(statementModel.fieldModel) + if (statementModel.fieldId.canBeSetFrom(context) && fieldValue.type isSubtypeOf statementModel.fieldId.type) { + instance[statementModel.fieldId] `=` fieldValue + } else { + with(statementModel) { + +utilsClassId[setField]( + instance, + fieldId.declaringClass.name, + fieldId.name, + fieldValue + ) + } + } + } else { + instance[statementModel.fieldId] `=` declareOrGet(statementModel.fieldModel) + } } is UtExecutableCallModel -> { val call = createCgExecutableCallFromUtExecutableCall(statementModel) @@ -332,7 +332,7 @@ open class CgVariableConstructor(val context: CgContext) : // and the size of an array is not greater than the fixed maximum size val canInitWithValues = (allPrimitives || allNulls) && elementModels.size <= MAX_ARRAY_INITIALIZER_SIZE - val initializer = if (canInitWithValues) { + val initializer = if (canInitWithValues && !UtSettings.greyBoxFuzzingCompetitionMode) { val elements = elementModels.map { model -> when (model) { is UtPrimitiveModel -> model.value.resolve() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt index c86c96e274..b0832725c4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt @@ -35,6 +35,8 @@ import org.utbot.framework.util.ConflictTriggers import org.utbot.framework.util.SootUtils import org.utbot.framework.util.jimpleBody import org.utbot.framework.util.toModel +import org.utbot.greyboxfuzzer.util.CoverageCollector +import org.utbot.greyboxfuzzer.util.GreyBoxFuzzingStatisticPrinter import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation import org.utbot.instrumentation.warmup @@ -130,6 +132,7 @@ open class TestCaseGenerator( generate: (engine: UtBotSymbolicEngine) -> Flow = defaultTestFlow(methodsGenerationTimeout) ): List { if (isCanceled()) return methods.map { UtMethodTestSet(it) } + if (UtSettings.useGreyBoxFuzzing) CoverageCollector.clear() val executionStartInMillis = System.currentTimeMillis() val executionTimeEstimator = ExecutionTimeEstimator(methodsGenerationTimeout, methods.size) @@ -230,6 +233,9 @@ open class TestCaseGenerator( forceMockListener.detach(this, forceMockListener) forceStaticMockListener.detach(this, forceStaticMockListener) + if (UtSettings.useGreyBoxFuzzing) { + GreyBoxFuzzingStatisticPrinter.printFuzzingStats(method2executions) + } return methods.map { method -> UtMethodTestSet( method, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt index 2407407acd..f05c018a4a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt @@ -19,6 +19,7 @@ fun defaultTestFlow(timeout: Long) = testFlow { isSymbolicEngineEnabled = true generationTimeout = timeout isFuzzingEnabled = UtSettings.useFuzzing + isGreyBoxFuzzingEnabled = UtSettings.useGreyBoxFuzzing if (generationTimeout > 0) { fuzzingValue = UtSettings.fuzzingTimeoutInMillis.coerceIn(0, generationTimeout) / generationTimeout.toDouble() } @@ -41,6 +42,7 @@ class TestFlow internal constructor(block: TestFlow.() -> Unit) { } var isSymbolicEngineEnabled = true var isFuzzingEnabled = false + var isGreyBoxFuzzingEnabled = false var fuzzingValue: Double = 0.1 set(value) { field = value.coerceIn(0.0, 1.0) @@ -59,6 +61,7 @@ class TestFlow internal constructor(block: TestFlow.() -> Unit) { fun build(engine: UtBotSymbolicEngine): Flow { return when { generationTimeout == 0L -> emptyFlow() + isGreyBoxFuzzingEnabled -> engine.greyBoxFuzzing(generationTimeout) isFuzzingEnabled -> { when (val value = if (isSymbolicEngineEnabled) (fuzzingValue * generationTimeout).toLong() else generationTimeout) { 0L -> engine.traverse() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt index 01c880caea..40dc120927 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt @@ -111,6 +111,7 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch isSymbolicEngineEnabled = params.isSymbolicEngineEnabled isFuzzingEnabled = params.isFuzzingEnabled fuzzingValue = params.fuzzingValue + isGreyBoxFuzzingEnabled = params.isGreyBoxFuzzingEnabled }) .apply { logger.info("generation ended, starting summarization, result size: ${this.size}") } .map { it.summarize(Paths.get(params.searchDirectory), sourceFile = null) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt index dc49b4d12e..fe18265f32 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt @@ -15,7 +15,7 @@ import kotlin.jvm.JvmStatic /** - * #### Generated from [EngineProcessModel.kt:30] + * #### Generated from [EngineProcessModel.kt:31] */ class EngineProcessModel private constructor( private val _setupUtContext: RdCall, @@ -71,9 +71,9 @@ class EngineProcessModel private constructor( bind(lifetime, protocol, "EngineProcessModel") } } - - - const val serializationHash = -6219345436129699239L + + + const val serializationHash = -5710118090342998881L } override val serializersOwner: ISerializersOwner get() = EngineProcessModel @@ -180,7 +180,7 @@ val IProtocol.engineProcessModel get() = getOrCreateExtension(EngineProcessModel /** - * #### Generated from [EngineProcessModel.kt:100] + * #### Generated from [EngineProcessModel.kt:102] */ data class FindMethodParamNamesArguments ( val classId: ByteArray, @@ -243,7 +243,7 @@ data class FindMethodParamNamesArguments ( /** - * #### Generated from [EngineProcessModel.kt:104] + * #### Generated from [EngineProcessModel.kt:106] */ data class FindMethodParamNamesResult ( val paramNames: ByteArray @@ -300,7 +300,7 @@ data class FindMethodParamNamesResult ( /** - * #### Generated from [EngineProcessModel.kt:93] + * #### Generated from [EngineProcessModel.kt:95] */ data class FindMethodsInClassMatchingSelectedArguments ( val classId: ByteArray, @@ -312,7 +312,7 @@ data class FindMethodsInClassMatchingSelectedArguments ( override val _type: KClass = FindMethodsInClassMatchingSelectedArguments::class @Suppress("UNCHECKED_CAST") - override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): FindMethodsInClassMatchingSelectedArguments { + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): FindMethodsInClassMatchingSelectedArguments { val classId = buffer.readByteArray() val methodDescriptions = buffer.readList { MethodDescription.read(ctx, buffer) } return FindMethodsInClassMatchingSelectedArguments(classId, methodDescriptions) @@ -335,7 +335,7 @@ data class FindMethodsInClassMatchingSelectedArguments ( if (other == null || other::class != this::class) return false other as FindMethodsInClassMatchingSelectedArguments - + if (!(classId contentEquals other.classId)) return false if (methodDescriptions != other.methodDescriptions) return false @@ -344,8 +344,8 @@ data class FindMethodsInClassMatchingSelectedArguments ( //hash code trait override fun hashCode(): Int { var __r = 0 - __r = __r * 31 + classId.contentHashCode() - __r = __r * 31 + methodDescriptions.hashCode() + __r = __r*31 + classId.contentHashCode() + __r = __r*31 + methodDescriptions.hashCode() return __r } //pretty print @@ -363,7 +363,7 @@ data class FindMethodsInClassMatchingSelectedArguments ( /** - * #### Generated from [EngineProcessModel.kt:97] + * #### Generated from [EngineProcessModel.kt:99] */ data class FindMethodsInClassMatchingSelectedResult ( val executableIds: ByteArray @@ -420,7 +420,7 @@ data class FindMethodsInClassMatchingSelectedResult ( /** - * #### Generated from [EngineProcessModel.kt:42] + * #### Generated from [EngineProcessModel.kt:43] */ data class GenerateParams ( val mockInstalled: Boolean, @@ -434,6 +434,7 @@ data class GenerateParams ( val isSymbolicEngineEnabled: Boolean, val isFuzzingEnabled: Boolean, val fuzzingValue: Double, + val isGreyBoxFuzzingEnabled: Boolean, val searchDirectory: String ) : IPrintable { //companion @@ -454,8 +455,9 @@ data class GenerateParams ( val isSymbolicEngineEnabled = buffer.readBool() val isFuzzingEnabled = buffer.readBool() val fuzzingValue = buffer.readDouble() + val isGreyBoxFuzzingEnabled = buffer.readBool() val searchDirectory = buffer.readString() - return GenerateParams(mockInstalled, staticsMockingIsConfigureda, conflictTriggers, methods, mockStrategy, chosenClassesToMockAlways, timeout, generationTimeout, isSymbolicEngineEnabled, isFuzzingEnabled, fuzzingValue, searchDirectory) + return GenerateParams(mockInstalled, staticsMockingIsConfigureda, conflictTriggers, methods, mockStrategy, chosenClassesToMockAlways, timeout, generationTimeout, isSymbolicEngineEnabled, isFuzzingEnabled, fuzzingValue, isGreyBoxFuzzingEnabled, searchDirectory) } override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GenerateParams) { @@ -470,6 +472,7 @@ data class GenerateParams ( buffer.writeBool(value.isSymbolicEngineEnabled) buffer.writeBool(value.isFuzzingEnabled) buffer.writeDouble(value.fuzzingValue) + buffer.writeBool(value.isGreyBoxFuzzingEnabled) buffer.writeString(value.searchDirectory) } @@ -497,6 +500,7 @@ data class GenerateParams ( if (isSymbolicEngineEnabled != other.isSymbolicEngineEnabled) return false if (isFuzzingEnabled != other.isFuzzingEnabled) return false if (fuzzingValue != other.fuzzingValue) return false + if (isGreyBoxFuzzingEnabled != other.isGreyBoxFuzzingEnabled) return false if (searchDirectory != other.searchDirectory) return false return true @@ -515,6 +519,7 @@ data class GenerateParams ( __r = __r*31 + isSymbolicEngineEnabled.hashCode() __r = __r*31 + isFuzzingEnabled.hashCode() __r = __r*31 + fuzzingValue.hashCode() + __r = __r*31 + isGreyBoxFuzzingEnabled.hashCode() __r = __r*31 + searchDirectory.hashCode() return __r } @@ -533,6 +538,7 @@ data class GenerateParams ( print("isSymbolicEngineEnabled = "); isSymbolicEngineEnabled.print(printer); println() print("isFuzzingEnabled = "); isFuzzingEnabled.print(printer); println() print("fuzzingValue = "); fuzzingValue.print(printer); println() + print("isGreyBoxFuzzingEnabled = "); isGreyBoxFuzzingEnabled.print(printer); println() print("searchDirectory = "); searchDirectory.print(printer); println() } printer.print(")") @@ -543,7 +549,7 @@ data class GenerateParams ( /** - * #### Generated from [EngineProcessModel.kt:60] + * #### Generated from [EngineProcessModel.kt:62] */ data class GenerateResult ( val notEmptyCases: Int, @@ -606,7 +612,7 @@ data class GenerateResult ( /** - * #### Generated from [EngineProcessModel.kt:112] + * #### Generated from [EngineProcessModel.kt:114] */ data class GenerateTestReportArgs ( val eventLogMessage: String?, @@ -699,7 +705,7 @@ data class GenerateTestReportArgs ( /** - * #### Generated from [EngineProcessModel.kt:121] + * #### Generated from [EngineProcessModel.kt:123] */ data class GenerateTestReportResult ( val notifyMessage: String, @@ -768,7 +774,7 @@ data class GenerateTestReportResult ( /** - * #### Generated from [EngineProcessModel.kt:31] + * #### Generated from [EngineProcessModel.kt:32] */ data class JdkInfo ( val path: String, @@ -831,64 +837,61 @@ data class JdkInfo ( /** - * #### Generated from [EngineProcessModel.kt:88] + * #### Generated from [EngineProcessModel.kt:90] */ -data class MethodDescription( +data class MethodDescription ( val name: String, val containingClass: String?, val parametersTypes: List ) : IPrintable { //companion - + companion object : IMarshaller { override val _type: KClass = MethodDescription::class - + @Suppress("UNCHECKED_CAST") - override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): MethodDescription { + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): MethodDescription { val name = buffer.readString() val containingClass = buffer.readNullable { buffer.readString() } val parametersTypes = buffer.readList { buffer.readNullable { buffer.readString() } } return MethodDescription(name, containingClass, parametersTypes) } - - override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: MethodDescription) { + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: MethodDescription) { buffer.writeString(value.name) buffer.writeNullable(value.containingClass) { buffer.writeString(it) } buffer.writeList(value.parametersTypes) { v -> buffer.writeNullable(v) { buffer.writeString(it) } } } - - + + } - //fields //methods //initializer //secondary constructor //equals trait - override fun equals(other: Any?): Boolean { + override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || other::class != this::class) return false - + other as MethodDescription - + if (name != other.name) return false if (containingClass != other.containingClass) return false if (parametersTypes != other.parametersTypes) return false - + return true } - //hash code trait - override fun hashCode(): Int { + override fun hashCode(): Int { var __r = 0 - __r = __r * 31 + name.hashCode() - __r = __r * 31 + if (containingClass != null) containingClass.hashCode() else 0 - __r = __r * 31 + parametersTypes.hashCode() + __r = __r*31 + name.hashCode() + __r = __r*31 + if (containingClass != null) containingClass.hashCode() else 0 + __r = __r*31 + parametersTypes.hashCode() return __r } - //pretty print - override fun print(printer: PrettyPrinter) { + override fun print(printer: PrettyPrinter) { printer.println("MethodDescription (") printer.indent { print("name = "); name.print(printer); println() @@ -903,9 +906,9 @@ data class MethodDescription( /** - * #### Generated from [EngineProcessModel.kt:64] + * #### Generated from [EngineProcessModel.kt:66] */ -data class RenderParams( +data class RenderParams ( val testSetsId: Long, val classUnderTest: ByteArray, val paramNames: ByteArray, @@ -1044,7 +1047,7 @@ data class RenderParams( /** - * #### Generated from [EngineProcessModel.kt:81] + * #### Generated from [EngineProcessModel.kt:83] */ data class RenderResult ( val generatedCode: String, @@ -1107,7 +1110,7 @@ data class RenderResult ( /** - * #### Generated from [EngineProcessModel.kt:85] + * #### Generated from [EngineProcessModel.kt:87] */ data class SetupContextParams ( val classpathForUrlsClassloader: List @@ -1164,7 +1167,7 @@ data class SetupContextParams ( /** - * #### Generated from [EngineProcessModel.kt:36] + * #### Generated from [EngineProcessModel.kt:37] */ data class TestGeneratorParams ( val buildDir: Array, @@ -1239,7 +1242,7 @@ data class TestGeneratorParams ( /** - * #### Generated from [EngineProcessModel.kt:107] + * #### Generated from [EngineProcessModel.kt:109] */ data class WriteSarifReportArguments ( val testSetsId: Long, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessRoot.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessRoot.Generated.kt index 0b333c3a50..dd4a221774 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessRoot.Generated.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessRoot.Generated.kt @@ -15,7 +15,7 @@ import kotlin.jvm.JvmStatic /** - * #### Generated from [EngineProcessModel.kt:5] + * #### Generated from [EngineProcessModel.kt:6] */ class EngineProcessRoot private constructor( ) : RdExtBase() { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdInstrumenterAdapter.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdInstrumenterAdapter.Generated.kt index f4fbc6f17d..c9c56ee162 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdInstrumenterAdapter.Generated.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdInstrumenterAdapter.Generated.kt @@ -15,7 +15,7 @@ import kotlin.jvm.JvmStatic /** - * #### Generated from [EngineProcessModel.kt:7] + * #### Generated from [EngineProcessModel.kt:8] */ class RdInstrumenterAdapter private constructor( private val _computeSourceFileByClass: RdCall @@ -97,7 +97,7 @@ val IProtocol.rdInstrumenterAdapter get() = getOrCreateExtension(RdInstrumenterA /** - * #### Generated from [EngineProcessModel.kt:8] + * #### Generated from [EngineProcessModel.kt:9] */ data class ComputeSourceFileByClassArguments ( val canonicalClassName: String diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdSourceFindingStrategy.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdSourceFindingStrategy.Generated.kt index 598ae1e086..11324e77f2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdSourceFindingStrategy.Generated.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdSourceFindingStrategy.Generated.kt @@ -15,7 +15,7 @@ import kotlin.jvm.JvmStatic /** - * #### Generated from [EngineProcessModel.kt:16] + * #### Generated from [EngineProcessModel.kt:17] */ class RdSourceFindingStrategy private constructor( private val _testsRelativePath: RdCall, @@ -111,7 +111,7 @@ val IProtocol.rdSourceFindingStrategy get() = getOrCreateExtension(RdSourceFindi /** - * #### Generated from [EngineProcessModel.kt:17] + * #### Generated from [EngineProcessModel.kt:18] */ data class SourceStrategyMethodArgs ( val testSetId: Long, diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt index 96fbd4d0d7..562d3e8759 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt @@ -1,6 +1,8 @@ package org.utbot.fuzzer import mu.KotlinLogging +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.byteClassId @@ -11,7 +13,15 @@ import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.longClassId import org.utbot.framework.plugin.api.util.shortClassId import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.byteWrapperClassId +import org.utbot.framework.plugin.api.util.charWrapperClassId +import org.utbot.framework.plugin.api.util.doubleWrapperClassId +import org.utbot.framework.plugin.api.util.floatWrapperClassId +import org.utbot.framework.plugin.api.util.intWrapperClassId +import org.utbot.framework.plugin.api.util.longWrapperClassId +import org.utbot.framework.plugin.api.util.shortWrapperClassId import org.utbot.framework.util.executableId +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor import soot.BooleanType import soot.ByteType import soot.CharType @@ -21,6 +31,7 @@ import soot.IntType import soot.Local import soot.LongType import soot.ShortType +import soot.SootMethod import soot.Unit import soot.Value import soot.ValueBox @@ -88,13 +99,42 @@ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set> { + val sootGraph = ExceptionalUnitGraph(sootMethod.activeBody) + + fun generateConstantsForBothPrimitives(classId: ClassId, value: Any, utModelConstructor: UtModelConstructor) = + when (classId) { + intWrapperClassId -> listOf(intClassId to utModelConstructor.construct(value, intClassId)) + intClassId -> listOf(intWrapperClassId to utModelConstructor.construct(value, intWrapperClassId)) + byteWrapperClassId -> listOf(byteClassId to utModelConstructor.construct(value, byteClassId)) + byteClassId -> listOf(byteWrapperClassId to utModelConstructor.construct(value, byteWrapperClassId)) + charWrapperClassId -> listOf(charClassId to utModelConstructor.construct(value, charClassId)) + charClassId -> listOf(charWrapperClassId to utModelConstructor.construct(value, charWrapperClassId)) + doubleWrapperClassId -> listOf(doubleClassId to utModelConstructor.construct(value, doubleClassId)) + doubleClassId -> listOf(doubleWrapperClassId to utModelConstructor.construct(value, doubleWrapperClassId)) + longWrapperClassId -> listOf(longClassId to utModelConstructor.construct(value, longClassId)) + longClassId -> listOf(longWrapperClassId to utModelConstructor.construct(value, longWrapperClassId)) + floatWrapperClassId -> listOf(floatClassId to utModelConstructor.construct(value, floatClassId)) + floatClassId -> listOf(floatWrapperClassId to utModelConstructor.construct(value, floatWrapperClassId)) + shortWrapperClassId -> listOf(shortClassId to utModelConstructor.construct(value, shortClassId)) + shortClassId -> listOf(shortWrapperClassId to utModelConstructor.construct(value, shortWrapperClassId)) + stringClassId -> listOf() + else -> null + }?.let { it + listOf(classId to utModelConstructor.construct(value, classId)) } ?: listOf() + + return collectConstantsForFuzzer(sootGraph) + .distinctBy { it.value } + .flatMap { generateConstantsForBothPrimitives(it.classId, it.value, utModelConstructor) } + .groupBy({ it.first }, { it.second }) +} + private interface ConstantsFinder { fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List } private object ConstantsFromIfStatement: ConstantsFinder { override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { - if (value !is Constant || (unit !is JIfStmt && unit !is JAssignStmt)) return emptyList() + if (value !is Constant || value is NullConstant || (unit !is JIfStmt && unit !is JAssignStmt)) return emptyList() var useBoxes: List = emptyList() var ifStatement: JIfStmt? = null diff --git a/utbot-greyboxfuzzer/build.gradle b/utbot-greyboxfuzzer/build.gradle new file mode 100644 index 0000000000..4618ed3b3a --- /dev/null +++ b/utbot-greyboxfuzzer/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '7.1.2' +} + +dependencies { + + api project(':utbot-instrumentation') + api project(':utbot-framework-api') + api project(':utbot-fuzzers') + + + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude group:'com.google.guava', module:'guava' + } + + implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: log4j2Version + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + implementation "org.javaruntype:javaruntype:1.3" + implementation "ru.vyarus:generics-resolver:3.0.3" + implementation "ognl:ognl:3.3.2" + + // we need this for construction mocks from composite models + implementation group: 'org.mockito', name: 'mockito-core', version: '4.2.0' + + // To use JUnit4, comment out JUnit5 and uncomment JUnit4 dependencies here. Please also check "test" section + //implementation group: 'junit', name: 'junit', version: '4.13.1' + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.1' + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.1' +} + + +shadowJar { + archiveClassifier.set('') + minimize{ + exclude(dependency('org.scala-lang:.*:.*')) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/GreyBoxFuzzer.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/GreyBoxFuzzer.kt new file mode 100644 index 0000000000..1fcf72ccef --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/GreyBoxFuzzer.kt @@ -0,0 +1,308 @@ +package org.utbot.greyboxfuzzer + +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.flow +import mu.KotlinLogging +import org.objectweb.asm.Type +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.isConstructor +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.UtFuzzedExecution +import org.utbot.greyboxfuzzer.generator.DataGenerator +import org.utbot.greyboxfuzzer.generator.GreyBoxFuzzerGeneratorsAndSettings +import org.utbot.greyboxfuzzer.generator.StaticMethodThisInstance +import org.utbot.greyboxfuzzer.generator.ThisInstance +import org.utbot.greyboxfuzzer.mutator.Mutator +import org.utbot.greyboxfuzzer.mutator.Seed +import org.utbot.greyboxfuzzer.mutator.SeedCollector +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.* +import org.utbot.instrumentation.instrumentation.execution.UtFuzzingConcreteExecutionResult +import ru.vyarus.java.generics.resolver.context.GenericsInfoFactory +import java.lang.reflect.Executable +import java.lang.reflect.Field +import kotlin.random.Random + +class GreyBoxFuzzer( + private val methodUnderTest: ExecutableId, + private val constants: Map>, + private val fuzzerUtModelConstructor: FuzzerUtModelConstructor, + private val executor: suspend (ExecutableId, EnvironmentModels, List) -> UtFuzzingConcreteExecutionResult, + private val valueConstructor: (EnvironmentModels) -> List>, + private val timeBudgetInMillis: Long +) { + + private var methodInstructions: Set? = null + private var seeds: SeedCollector = SeedCollector() + private val timeRemain + get() = timeOfStart + timeBudgetInMillis - System.currentTimeMillis() + private val timeOfStart = System.currentTimeMillis() + private val percentageOfTimeBudgetToChangeMode = 25 + private val logger = KotlinLogging.logger {} + private val classMutator = Mutator() + + init { + GenericsInfoFactory.disableCache() + } + + suspend fun fuzz() = flow { + logger.debug { "Started to fuzz ${methodUnderTest.name}" } + val javaClazz = methodUnderTest.classId.jClass + val sootMethod = methodUnderTest.sootMethod + val javaMethod = sootMethod.toJavaMethod() ?: return@flow + val generatorContext = GeneratorContext(fuzzerUtModelConstructor, constants) + val classFieldsUsedByFunc = sootMethod.getClassFieldsUsedByFunc(javaClazz) + while (timeRemain > 0 || !isMethodCovered()) { + explorationStage( + javaMethod, + classFieldsUsedByFunc, + methodUnderTest, + generatorContext + ) + logger.debug { "SEEDS AFTER EXPLORATION STAGE = ${seeds.seedsSize()}" } + if (timeRemain < 0 || isMethodCovered()) break + exploitationStage() + } + } + + private suspend fun FlowCollector.explorationStage( + method: Executable, + classFieldsUsedByFunc: Set, + methodUnderTest: ExecutableId, + generatorContext: GeneratorContext + ) { + val parametersToGenericsReplacer = method.parameters.map { it to GenericsReplacer() } + var regenerateThis = false + val thisInstancesHistory = ArrayDeque() + val startTime = System.currentTimeMillis() + val endTime = startTime + timeBudgetInMillis / (100L / percentageOfTimeBudgetToChangeMode) + var iterationNumber = 0 + while (System.currentTimeMillis() < endTime) { + try { + if (timeRemain < 0 || isMethodCovered()) return + logger.debug { "Func: ${methodUnderTest.name} Iteration number $iterationNumber" } + iterationNumber++ + while (thisInstancesHistory.size > 1) { + thisInstancesHistory.removeLast() + } + if (thisInstancesHistory.isEmpty()) { + thisInstancesHistory += generateThisInstance(methodUnderTest.classId, generatorContext) + } + if (iterationNumber != 0) { + if (regenerateThis || Random.getTrue(30)) { + logger.debug { "Trying to regenerate this instance" } + thisInstancesHistory.clear() + thisInstancesHistory += generateThisInstance(methodUnderTest.classId, generatorContext) + regenerateThis = false + } else if (Random.getTrue(60)) { + logger.debug { "Trying to mutate this instance" } + thisInstancesHistory += classMutator.mutateThisInstance( + thisInstancesHistory.last(), + classFieldsUsedByFunc.toList(), + generatorContext + ) + } + } + /** + * Replacing unresolved generics to random compatible to bounds type + */ + when { + Random.getTrue(10) -> parametersToGenericsReplacer.map { it.second.revert() } + Random.getTrue(50) -> parametersToGenericsReplacer.map { + it.second.replaceUnresolvedGenericsToRandomTypes( + it.first + ) + } + } + val thisInstance = thisInstancesHistory.last() + val generatedParameters = + method.parameters.mapIndexed { index, parameter -> + DataGenerator.generate( + parameter, + index, + generatorContext, + GreyBoxFuzzerGeneratorsAndSettings.sourceOfRandomness, + GreyBoxFuzzerGeneratorsAndSettings.genStatus + ) + } + logger.debug { "Generated params = $generatedParameters" } + logger.debug { "This instance = $thisInstance" } + val stateBefore = + EnvironmentModels(thisInstance.utModelForExecution, generatedParameters.map { it.utModel }, mapOf()) + try { + logger.debug { "Execution of ${methodUnderTest.name} started" } + val executionResult = (executor::invoke)(methodUnderTest, stateBefore, listOf()) + if (methodInstructions == null && executionResult.methodInstructions != null) { + methodInstructions = executionResult.methodInstructions!!.toSet() + } + logger.debug { "Execution of ${methodUnderTest.name} result: $executionResult" } + val seedCoverage = getCoverage(executionResult.coverage) + logger.debug { "Calculating seed score" } + val seedScore = seeds.calcSeedScore(seedCoverage) + logger.debug { "Adding seed" } + val seed = Seed(thisInstance, generatedParameters, seedCoverage, seedScore) + if (seeds.isSeedOpensNewCoverage(seed)) { + emit( + run { + val parametersModels = + if (stateBefore.thisInstance == null) { + stateBefore.parameters + } else { + listOfNotNull(stateBefore.thisInstance) + stateBefore.parameters + } + val stateBeforeWithNullsAsUtModels = + valueConstructor.invoke(stateBefore).zip(parametersModels) + .map { (concreteValue, model) -> concreteValue.value?.let { model } ?: UtNullModel(model.classId) } + .let { if (stateBefore.thisInstance != null) it.drop(1) else it } + val newStateBefore = EnvironmentModels(thisInstance.utModelForExecution, stateBeforeWithNullsAsUtModels, mapOf()) + if (executionResult.stateAfter != null) { + UtFuzzedExecution( + stateBefore = newStateBefore, + stateAfter = executionResult.stateAfter!!, + result = executionResult.result, + coverage = executionResult.coverage, + fuzzingValues = generatedParameters.map { FuzzedValue(it.utModel) }, + fuzzedMethodDescription = FuzzedMethodDescription(methodUnderTest) + ) + } else { + UtGreyBoxFuzzedExecution( + newStateBefore, + executionResult, + coverage = executionResult.coverage + ) + } + } + + ) + } + seeds.addSeed(seed) + logger.debug { "Execution of ${methodUnderTest.name} concrete result: ${executionResult.result}" } + logger.debug { "Seed score = $seedScore" } + } catch (e: Throwable) { + logger.debug(e) { "Exception while execution in method ${methodUnderTest.name} of class ${methodUnderTest.classId.name}" } + thisInstancesHistory.clear() + regenerateThis = true + continue + } + } catch (e: FuzzerIllegalStateException) { + logger.error(e) { "Something wrong in the fuzzing process" } + } + } + } + + private suspend fun FlowCollector.exploitationStage() { + logger.debug { "Exploitation began" } + if (seeds.seedsSize() == 0) return + if (seeds.all { it.parameters.isEmpty() }) return + val startTime = System.currentTimeMillis() + val endTime = startTime + timeBudgetInMillis / (100L / percentageOfTimeBudgetToChangeMode) + var iterationNumber = 0 + while (System.currentTimeMillis() < endTime) { + if (timeRemain < 0 || isMethodCovered()) return + //Infinite cycle of cant mutate seed + if (iterationNumber > 30_000) return + logger.debug { "Func: ${methodUnderTest.name} Mutation iteration number $iterationNumber" } + iterationNumber++ + val randomSeed = seeds.getRandomWeightedSeed() + logger.debug { "Random seed params = ${randomSeed.parameters}" } + val mutatedSeed = + classMutator.mutateSeed( + randomSeed, + GreyBoxFuzzerGeneratorsAndSettings.sourceOfRandomness, + GreyBoxFuzzerGeneratorsAndSettings.genStatus + ) + if (mutatedSeed == randomSeed) { + logger.debug { "Cant mutate seed" } + continue + } + logger.debug { "Mutated params = ${mutatedSeed.parameters}" } + val stateBefore = mutatedSeed.createEnvironmentModels() + try { + val executionResult = (executor::invoke)(methodUnderTest, stateBefore, listOf()) + logger.debug { "Execution result: $executionResult" } + val seedScore = getCoverage(executionResult.coverage) + mutatedSeed.score = 0.0 + if (seeds.isSeedOpensNewCoverage(mutatedSeed)) { + emit( + run { + val parametersModels = + if (stateBefore.thisInstance == null) { + stateBefore.parameters + } else { + listOfNotNull(stateBefore.thisInstance) + stateBefore.parameters + } + val stateBeforeWithNullsAsUtModels = + valueConstructor.invoke(stateBefore).zip(parametersModels) + .map { (concreteValue, model) -> concreteValue.value?.let { model } ?: UtNullModel(model.classId) } + .let { if (stateBefore.thisInstance != null) it.drop(1) else it } + val newStateBefore = EnvironmentModels(stateBefore.thisInstance, stateBeforeWithNullsAsUtModels, mapOf()) + if (executionResult.stateAfter != null) { + UtFuzzedExecution( + stateBefore = newStateBefore, + stateAfter = executionResult.stateAfter!!, + result = executionResult.result, + coverage = executionResult.coverage, + fuzzingValues = mutatedSeed.parameters.map { FuzzedValue(it.utModel) }, + fuzzedMethodDescription = FuzzedMethodDescription(methodUnderTest) + ) + } else { + UtGreyBoxFuzzedExecution( + newStateBefore, + executionResult, + coverage = executionResult.coverage + ) + } + } + ) + } + seeds.addSeed(mutatedSeed) + logger.debug { "Execution result: ${executionResult.result}" } + logger.debug { "Seed score = $seedScore" } + } catch (e: Throwable) { + logger.debug(e) { "Exception while execution in method ${methodUnderTest.name} of class ${methodUnderTest.classId.name}" } + continue + } + } + } + + private fun getCoverage( + coverage: Coverage + ): Set { + val currentMethodCoverage = coverage.coveredInstructions + .asSequence() + .filter { it.className == Type.getInternalName(methodUnderTest.classId.jClass) } + .filter { it.methodSignature == methodUnderTest.signature } +// .map { it.id } + //.filter { it in methodInstructionsIds!! } + .toSet() + logger.debug { "Covered instructions ${currentMethodCoverage.count()} from ${methodInstructions?.size}" } + coverage.coveredInstructions.forEach { CoverageCollector.addCoverage(it) } + return currentMethodCoverage + } + + private fun isMethodCovered(): Boolean { + methodInstructions ?: return false + val coveredInstructions = + CoverageCollector.coverage + .filter { it.className == Type.getInternalName(methodUnderTest.classId.jClass) } + .filter { it.methodSignature == methodUnderTest.signature } + .toSet() + return coveredInstructions.containsAll(methodInstructions!!) + } + + private fun generateThisInstance(classId: ClassId, generatorContext: GeneratorContext): ThisInstance = + if (!methodUnderTest.isStatic && !methodUnderTest.isConstructor) { + DataGenerator.generateThis( + classId, + generatorContext, + GreyBoxFuzzerGeneratorsAndSettings.sourceOfRandomness, + GreyBoxFuzzerGeneratorsAndSettings.genStatus + ) + } else { + StaticMethodThisInstance + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/coverage/GlobalCoverage.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/coverage/GlobalCoverage.kt new file mode 100644 index 0000000000..6f42d490e5 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/coverage/GlobalCoverage.kt @@ -0,0 +1,11 @@ +package org.utbot.greyboxfuzzer.coverage + +import org.utbot.framework.plugin.api.Instruction + +data class GlobalCoverage(val coveredInstructions: MutableSet) { + + fun addInstructions(instructions: List) { + instructions.forEach { coveredInstructions.add(it) } + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/DataGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/DataGenerator.kt new file mode 100644 index 0000000000..199130bbbe --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/DataGenerator.kt @@ -0,0 +1,142 @@ +package org.utbot.greyboxfuzzer.generator + +import org.utbot.greyboxfuzzer.util.FuzzerIllegalStateException +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import org.utbot.greyboxfuzzer.util.logger +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationState +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import java.lang.reflect.Parameter + +object DataGenerator { + + private val generatorRepository = GreyBoxFuzzerGeneratorsAndSettings.generatorRepository + + fun generateUtModel( + parameterTypeContext: ParameterTypeContext, + depth: Int = 0, + generatorContext: GeneratorContext, + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val classId = parameterTypeContext.rawClass.id + logger.debug { "Trying to generate UtModel of type ${classId.name} 3 times" } + var generatedInstance: UtModel? + repeat(3) { + generatedInstance = + try { + val generator = + generatorRepository.getOrProduceGenerator(parameterTypeContext, generatorContext, depth) + ?: return@repeat + //generator.generatorContext.startCheckpoint() + generator.generateImpl(random, status) + } catch (_: Throwable) { + null + } + generatedInstance?.let { if (it !is UtNullModel) return it } + } + return UtNullModel(classId) + } + + fun generate( + parameter: Parameter, + parameterIndex: Int, + generatorContext: GeneratorContext, + random: SourceOfRandomness, + status: GenerationStatus + ): FParameter { + val generator = + generatorRepository.getOrProduceGenerator(parameter, parameterIndex, generatorContext) + return generate(generator, parameter, random, status) + } + + fun generateThis( + classId: ClassId, + generatorContext: GeneratorContext, + random: SourceOfRandomness, + status: GenerationStatus + ): NormalMethodThisInstance { + val generator = + generatorRepository.getOrProduceGenerator(classId.jClass, generatorContext) + return generateThis(generator, classId, generatorContext, random, status) + } + + private fun generateThis( + generator: Generator?, + classId: ClassId, + generatorContext: GeneratorContext, + random: SourceOfRandomness, + status: GenerationStatus, + numberOfTries: Int = 3 + ): NormalMethodThisInstance { + logger.debug { "Trying to generate this instance of type ${classId.name} $numberOfTries times" } + generatorRepository.removeGeneratorForObjectClass() + if (generator == null) { + throw FuzzerIllegalStateException("Can't find generator for ${classId.name}") + } + var generatedValue: UtModel + repeat(numberOfTries) { iteration -> + logger.debug { "Try $iteration" } + try { + generator.generationState = GenerationState.REGENERATE + generator.generatorContext.startCheckpoint() + generatedValue = generator.generateImpl(random, status) + if (generatedValue is UtNullModel && iteration != numberOfTries - 1) return@repeat + return NormalMethodThisInstance( + generatedValue, + generator, + classId + ) + } catch (e: Throwable) { + logger.error(e) { "Exception while generation :(" } + return@repeat + } + } + throw FuzzerIllegalStateException("Can't generate for ${classId.name}") + } + + fun generate( + generator: Generator?, + parameter: Parameter, + random: SourceOfRandomness, + status: GenerationStatus, + numberOfTries: Int = 3 + ): FParameter { + logger.debug { "Trying to generate value for parameter ${parameter.name} of type ${parameter.type} $numberOfTries times" } + generatorRepository.removeGeneratorForObjectClass() + val classId = classIdForType(parameter.type) + if (generator == null) { + return FParameter(parameter, null, UtNullModel(classId), null, classId, listOf()) + } + var generatedValue: UtModel? + repeat(numberOfTries) { + logger.debug { "Try $it" } + try { + generator.generationState = GenerationState.REGENERATE + generator.generatorContext.startCheckpoint() + generatedValue = generator.generateImpl(random, status) + if (generatedValue is UtNullModel) return@repeat + return FParameter( + parameter, + null, + generatedValue!!, + generator, + emptyList() + ) + } catch (e: Throwable) { + logger.error(e) { "Exception while generation :(" } + return@repeat + } + } + return FParameter(parameter, null, UtNullModel(classId), generator, classId, listOf()) + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/FField.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/FField.kt new file mode 100644 index 0000000000..80ce70ef52 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/FField.kt @@ -0,0 +1,74 @@ +package org.utbot.greyboxfuzzer.generator + +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.util.toClass +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.ClassId +import java.lang.reflect.Field +import java.lang.reflect.Type + +data class FField( + val field: Field?, + val value: Any?, + val resolvedType: Type, + val generator: Generator?, + val classId: ClassId, + val subFields: List, + var isBlocked: Boolean, +) { + + constructor( + field: Field?, + value: Any?, + resolvedType: Type, + generator: Generator?, + subFields: List, + isBlocked: Boolean + ) : this( + field, + value, + resolvedType, + generator, + classIdForType(field?.type ?: resolvedType.toClass()!!), + subFields, + isBlocked + ) + + constructor( + field: Field?, + value: Any?, + resolvedType: Type, + generator: Generator?, + subFields: List, + ) : this( + field, + value, + resolvedType, + generator, + classIdForType(field?.type ?: resolvedType.toClass()!!), + subFields, + false + ) + + constructor( + field: Field?, + value: Any?, + resolvedType: Type, + generator: Generator?, + ) : this( + field, + value, + resolvedType, + generator, + classIdForType(field?.type ?: resolvedType.toClass()!!), + listOf(), + false + ) + + constructor( + field: Field?, + value: Any?, + resolvedType: Type + ) : this(field, value, resolvedType, null, classIdForType(field?.type ?: resolvedType.toClass()!!), listOf(), false) + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/FParameter.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/FParameter.kt new file mode 100644 index 0000000000..1fb1a8217e --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/FParameter.kt @@ -0,0 +1,71 @@ +package org.utbot.greyboxfuzzer.generator + +import org.utbot.greyboxfuzzer.mutator.Mutator +import org.utbot.greyboxfuzzer.util.copy +import org.utbot.greyboxfuzzer.util.getAllDeclaredFields +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.jClass +import java.lang.reflect.Field +import java.lang.reflect.Parameter + +data class FParameter( + val parameter: Parameter, + val value: Any?, + //TODO make it val + var utModel: UtModel, + val generator: Generator?, + val classId: ClassId, + val fields: List +) { + + constructor( + parameter: Parameter, + value: Any?, + utModel: UtModel, + generator: Generator? + ) : this(parameter, value, utModel, generator, classIdForType(parameter.type), emptyList()) + + constructor( + parameter: Parameter, + value: Any?, + utModel: UtModel, + generator: Generator?, + fields: List + ) : this(parameter, value, utModel, generator, classIdForType(parameter.type), fields) + + fun getAllSubFields(): List { + val res = mutableListOf() + val queue = ArrayDeque() + queue.addAll(fields) + while (queue.isNotEmpty()) { + val element = queue.removeFirst() + queue.addAll(element.subFields) + res.add(element) + } + return res + } + + fun copy(): FParameter = FParameter( + parameter, + value, + utModel.copy(), + generator?.copy(), + classId, + fields + ) + + fun replaceUtModel(newUtModel: UtModel): FParameter = FParameter( + parameter, + value, + newUtModel, + generator?.copy(), + classId, + fields + ) + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/FThisInstance.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/FThisInstance.kt new file mode 100644 index 0000000000..2b6658e508 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/FThisInstance.kt @@ -0,0 +1,33 @@ +package org.utbot.greyboxfuzzer.generator + +import org.utbot.greyboxfuzzer.util.copy +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator + +sealed interface ThisInstance { + val utModelForExecution: UtModel? + fun copy(): ThisInstance +} + +data class NormalMethodThisInstance( + val utModel: UtModel, + val generator: Generator, + val classId: ClassId +): ThisInstance { + override val utModelForExecution = utModel + override fun copy(): ThisInstance { + return NormalMethodThisInstance( + utModel.copy(), + generator.copy(), + classId + ) + } +} + +object StaticMethodThisInstance: ThisInstance { + override val utModelForExecution = null + override fun copy(): ThisInstance { + return StaticMethodThisInstance + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/GeneratorConfigurator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/GeneratorConfigurator.kt new file mode 100644 index 0000000000..855167dbdd --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/GeneratorConfigurator.kt @@ -0,0 +1,107 @@ +package org.utbot.greyboxfuzzer.generator + +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.Size +import org.utbot.greyboxfuzzer.quickcheck.generator.java.util.CollectionGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.java.util.MapGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.generator.ArrayGenerator +import org.utbot.greyboxfuzzer.util.getTrue +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.* +import kotlin.random.Random + +object GeneratorConfigurator { + private const val minByte: Byte = -100 + private const val maxByte: Byte = 100 + private val minShort: Short = -100 + private val maxShort: Short = 100 + private val minChar: Char = Character.MIN_VALUE + private val maxChar: Char = Character.MAX_VALUE + private val minInt: Int = -100 + private val maxInt: Int = 100 + private val minLong: Long = -100 + private val maxLong: Long = 100 + private val minFloat: Float = -100.0f + private val maxFloat: Float = 100.0f + private val minDouble: Double = -100.0 + private val maxDouble: Double = 100.0 + private val minStringLength: Int = 0 + private val maxStringLength: Int = 10 + val minCollectionSize: Int = 0 + val maxCollectionSize: Int = 5 + + val sizeAnnotationInstance: Size + val inRangeAnnotationInstance: InRange + + init { + val sizeAnnotationParams = + Size::class.constructors.first().parameters.associateWith { + if (it.name == "max") maxCollectionSize else minCollectionSize + } + sizeAnnotationInstance = Size::class.constructors.first().callBy(sizeAnnotationParams) + val inRangeAnnotationParams = + InRange::class.constructors.first().parameters.associateWith { + when (it.name) { + "minByte" -> minByte + "maxByte" -> maxByte + "minShort" -> minShort + "maxShort" -> maxShort + "minChar" -> minChar + "maxChar" -> maxChar + "minInt" -> minInt + "maxInt" -> maxInt + "minLong" -> minLong + "maxLong" -> maxLong + "minFloat" -> minFloat + "maxFloat" -> maxFloat + "minDouble" -> minDouble + "maxDouble" -> maxDouble + "max" -> "" + "min" -> "" + "format" -> "" + else -> "" + } + } + inRangeAnnotationInstance = InRange::class.constructors.first().callBy(inRangeAnnotationParams) + } + + fun configureGenerator(generator: Generator, prob: Int) { + (listOf(generator) + generator.getAllComponents()).forEach { + if (Random.getTrue(prob)) handleGenerator(it) + } + } + + private fun handleGenerator(generator: Generator) = + when (generator) { + is IntegerGenerator -> generator.configure(inRangeAnnotationInstance) + is ByteGenerator -> generator.configure(inRangeAnnotationInstance) + is ShortGenerator -> generator.configure(inRangeAnnotationInstance) + is CharacterGenerator -> generator.configure(inRangeAnnotationInstance) + is FloatGenerator -> generator.configure(inRangeAnnotationInstance) + is DoubleGenerator -> generator.configure(inRangeAnnotationInstance) + is LongGenerator -> generator.configure(inRangeAnnotationInstance) + is PrimitiveIntGenerator -> generator.configure(inRangeAnnotationInstance) + is PrimitiveByteGenerator -> generator.configure(inRangeAnnotationInstance) + is PrimitiveShortGenerator -> generator.configure(inRangeAnnotationInstance) + is PrimitiveCharGenerator -> generator.configure(inRangeAnnotationInstance) + is PrimitiveFloatGenerator -> generator.configure(inRangeAnnotationInstance) + is PrimitiveDoubleGenerator -> generator.configure(inRangeAnnotationInstance) + is PrimitiveLongGenerator -> generator.configure(inRangeAnnotationInstance) + is CollectionGenerator -> generator.configure(sizeAnnotationInstance) + is ArrayGenerator -> generator.configure(sizeAnnotationInstance) + is MapGenerator -> generator.configure(sizeAnnotationInstance) + is StringGenerator -> generator.configure( + if (Random.getTrue(50)) { + if (Random.getTrue(50)) { + setOf('a'.code..'z'.code) + } else { + setOf('A'.code..'Z'.code) + } + } else { + setOf(' '.code..'~'.code) + }, minStringLength..maxStringLength + ) + else -> Unit + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/GreyBoxFuzzerGeneratorsAndSettings.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/GreyBoxFuzzerGeneratorsAndSettings.kt new file mode 100644 index 0000000000..ca4216e60a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/GreyBoxFuzzerGeneratorsAndSettings.kt @@ -0,0 +1,82 @@ +package org.utbot.greyboxfuzzer.generator + + +import org.utbot.greyboxfuzzer.quickcheck.NonTrackingGenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.* +import org.utbot.greyboxfuzzer.quickcheck.generator.java.time.* +import org.utbot.greyboxfuzzer.quickcheck.generator.java.util.* +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.* +import org.utbot.greyboxfuzzer.quickcheck.generator.java.math.* +import org.utbot.greyboxfuzzer.quickcheck.generator.java.nio.charset.CharsetGenerator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.* +import java.util.concurrent.atomic.AtomicInteger + +object GreyBoxFuzzerGeneratorsAndSettings { + + const val seed = 42L + val maxDepthOfGeneration = AtomicInteger(5) + val sourceOfRandomness = SourceOfRandomness(Random(seed)) + val genStatus = NonTrackingGenerationStatus(sourceOfRandomness) + + val generatorRepository = + UTGeneratorRepository(sourceOfRandomness).also { + it.register(DurationGenerator()) +// it.register(MonthDayGenerator()) +// it.register(LocalDateTimeGenerator()) +// it.register(YearMonthGenerator()) +// it.register(ClockGenerator()) + //it.register(ZonedDateTimeGenerator()) + //it.register(LocalDateGenerator()) +// it.register(ZoneIdGenerator()) +// it.register(YearGenerator()) +// it.register(OffsetTimeGenerator()) +// it.register(InstantGenerator()) +// it.register(ZoneOffsetGenerator()) + //it.register(LocalTimeGenerator()) +// it.register(OffsetDateTimeGenerator()) +// it.register(PeriodGenerator()) + it.register(BigDecimalGenerator()) + it.register(BigIntegerGenerator()) + it.register(CharsetGenerator()) + it.register(ShortGenerator()) + it.register(BooleanGenerator()) + it.register(IntegerGenerator()) + it.register(ByteGenerator()) + it.register(StringGenerator()) + it.register(LongGenerator()) + it.register(DoubleGenerator()) + it.register(CharacterGenerator()) + it.register(FloatGenerator()) + it.register(OptionalIntGenerator()) + it.register(OptionalDoubleGenerator()) + it.register(LinkedListGenerator()) + it.register(LinkedHashSetGenerator()) + it.register(HashMapGenerator()) + it.register(LocaleGenerator()) + it.register(BitSetGenerator()) + //it.register(TimeZoneGenerator()) + it.register(HashSetGenerator()) + it.register(ArrayListGenerator()) + it.register(VectorGenerator()) + it.register(LinkedHashMapGenerator()) + it.register(HashtableGenerator()) + it.register(OptionalLongGenerator()) +// it.register(PropertiesGenerator()) + it.register(OptionalGenerator()) +// it.register(DateGenerator()) + it.register(StackGenerator()) + it.register(VoidGenerator()) + it.register(PrimitiveCharGenerator()) + it.register(PrimitiveBooleanGenerator()) + it.register(PrimitiveByteGenerator()) + it.register(PrimitiveDoubleGenerator()) + it.register(PrimitiveFloatGenerator()) + it.register(PrimitiveIntGenerator()) + it.register(PrimitiveLongGenerator()) + it.register(PrimitiveShortGenerator()) +// it.register(RFC4122.Version3()) +// it.register(RFC4122.Version4()) +// it.register(RFC4122.Version5()) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/QuickCheckExtensions.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/QuickCheckExtensions.kt new file mode 100644 index 0000000000..b7c9ce2edd --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/QuickCheckExtensions.kt @@ -0,0 +1,331 @@ +@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") + +package org.utbot.greyboxfuzzer.generator + +import org.javaruntype.exceptions.TypeValidationException +import org.javaruntype.type.StandardTypeParameter +import org.javaruntype.type.Types +import org.utbot.common.withAccessibility +import org.utbot.greyboxfuzzer.generator.userclasses.UserClassGenerator +import org.utbot.greyboxfuzzer.util.* +import org.utbot.greyboxfuzzer.util.logger +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.internal.FakeAnnotatedTypeFactory +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.internal.generator.ArrayGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.generator.CompositeGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.generator.GeneratorRepository +import ru.vyarus.java.generics.resolver.GenericsResolver +import ru.vyarus.java.generics.resolver.context.ConstructorGenericsContext +import ru.vyarus.java.generics.resolver.context.GenericsContext +import ru.vyarus.java.generics.resolver.context.GenericsInfo +import ru.vyarus.java.generics.resolver.context.MethodGenericsContext +import java.lang.reflect.* +import kotlin.random.Random + + +fun Generator.getAllComponents(): List { + val queue = ArrayDeque() + val res = mutableListOf() + this.getComponents().forEach { queue.add(it) } + while (queue.isNotEmpty()) { + val comp = queue.removeFirst() + res.add(comp) + comp.getComponents().forEach(queue::add) + } + return res +} + +fun Generator.getComponents(): List = + when (this) { + is org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator -> this.componentGenerators() + is ArrayGenerator -> listOf(this.component) + is CompositeGenerator -> this.composed.map { it.item } + else -> emptyList() + } + +fun GeneratorRepository.produceUserClassGenerator( + forClass: Class<*>, + parameterTypeContext: ParameterTypeContext, + depth: Int +): UserClassGenerator { + val userClassGenerator = UserClassGenerator().also { + it.clazz = forClass + it.parameterTypeContext = parameterTypeContext + it.depth = depth + } + addUserClassGenerator(forClass, userClassGenerator) + return userClassGenerator +} + +fun GeneratorRepository.getOrProduceGenerator( + field: Field, + generatorContext: GeneratorContext, + depth: Int = 0 +): Generator? = + getOrProduceGenerator(ParameterTypeContext.forField(field), generatorContext, depth) + +fun GeneratorRepository.getOrProduceGenerator( + param: Parameter, + parameterIndex: Int, + generatorContext: GeneratorContext, + depth: Int = 0 +): Generator? = + getOrProduceGenerator(param.createParameterTypeContext(parameterIndex), generatorContext, depth) + +fun GeneratorRepository.getOrProduceGenerator( + clazz: Class<*>, + generatorContext: GeneratorContext, + depth: Int = 0 +): Generator? = + getOrProduceGenerator(clazz.createParameterTypeContext(), generatorContext, depth) + +fun GeneratorRepository.getOrProduceGenerator( + parameterTypeContext: ParameterTypeContext, + generatorContext: GeneratorContext, + depth: Int +): Generator? { + val producedUserClassesGenerators = mutableListOf() + parameterTypeContext.getAllSubParameterTypeContexts(GreyBoxFuzzerGeneratorsAndSettings.sourceOfRandomness).reversed() + .forEach { typeContext -> + try { + this.produceGenerator(typeContext) + //TODO catch specific exception + } catch (e: Exception) { + producedUserClassesGenerators += produceUserClassGenerator( + typeContext.rawClass, + typeContext, + depth + 1 + ) + } + } + val generator = + try { + this.produceGenerator(parameterTypeContext) + } catch (e: Exception) { + logger.debug { "Can not get generator for ${parameterTypeContext.resolved}" } + return null + } finally { + producedUserClassesGenerators.forEach { removeGenerator(it.parameterTypeContext!!.resolved.rawClass) } + } + (listOf(generator) + generator.getAllComponents()).forEach { + it.generatorContext = generatorContext + } + return generator +} + +fun Parameter.createParameterTypeContext(parameterIndex: Int): ParameterTypeContext = + try { + //Classic scheme doesn't work for types with generics + if (this.type.typeParameters.isNotEmpty() && this.parameterizedType !is ParameterizedType) { + throw TypeValidationException("") + } + ParameterTypeContext.forParameter(this) + } catch (e: TypeValidationException) { + val clazz = this.type + val parametersBounds = + this.type.typeParameters.map { it.bounds.firstOrNull() ?: Any::class.java.rawType }.toTypedArray() + val p = ru.vyarus.java.generics.resolver.context.container.ParameterizedTypeImpl( + this.type, + *parametersBounds + ) + val genericContext = p.createGenericsContext(clazz) + createParameterContextForParameter(this, parameterIndex, genericContext, p) + } + +//fun ParameterTypeContext.getAllParameterTypeContexts(): List { +// fun ArrayDeque.addParameterContext(ctx: ParameterTypeContext) { +// if (ctx.resolved.name == Zilch::class.java.name) return +// add(ctx) +// } +// +// val res = mutableListOf(this) +// val queue = ArrayDeque() +// if (this.isArray) { +// this.arrayComponentContext().let { queue.addParameterContext(it) } +// } +// this.typeParameterContexts(GreyBoxFuzzerGenerators.sourceOfRandomness).forEach { queue.addParameterContext(it) } +// while (queue.isNotEmpty()) { +// val el = queue.removeFirst() +// if (el.isArray) { +// el.arrayComponentContext().let { queue.addParameterContext(it) } +// } +// el.typeParameterContexts(GreyBoxFuzzerGenerators.sourceOfRandomness).forEach { queue.addParameterContext(it) } +// res.add(el) +// } +// return res +//} + +fun Type.createGenericsContext(clazz: Class<*>): GenericsContext { + val actualTypeParams = this.getActualTypeArguments() + val klassTypeParams = this.toClass()?.typeParameters?.map { it.name } + val gm = LinkedHashMap() + klassTypeParams?.zip(actualTypeParams)?.forEach { gm[it.first] = it.second } + val m = mutableMapOf(clazz to gm) + val genericsInfo = GenericsInfo(clazz, m) + return GenericsContext(genericsInfo, clazz) +} + +fun Class<*>.createParameterTypeContext(): ParameterTypeContext { + val generics = GenericsResolver.resolve(this) + val resolvedGenerics = + generics.resolveTypeGenerics(this).map { createStandardTypeParameter(Types.forJavaLangReflectType(it)) } + val resolvedType = try { + Types.forClass(this, *resolvedGenerics.toTypedArray()) + } catch (e: Throwable) { + Types.forClass(this) + } + return ParameterTypeContext( + this.typeName, + FakeAnnotatedTypeFactory.makeFrom(this), + this.typeName, + resolvedType, + generics + ) +} + +fun createParameterContextForParameter( + parameter: Parameter, + parameterIndex: Int, + generics: GenericsContext +): ParameterTypeContext { + val exec = parameter.declaringExecutable + val clazz = exec.declaringClass + val declarerName = clazz.name + val resolvedType = + when (generics) { + is MethodGenericsContext -> generics.resolveParameterType(parameterIndex) + is ConstructorGenericsContext -> generics.resolveParameterType(parameterIndex) + else -> throw IllegalArgumentException("Unexpected type of GenericsContext") + } + return ParameterTypeContext( + parameter.name, + parameter.annotatedType, + declarerName, + Types.forJavaLangReflectType( + resolvedType + ), + generics, + parameterIndex + ) +} + +fun createParameterContextForParameter( + parameter: Parameter, + parameterIndex: Int, + generics: GenericsContext, + type: Type +): ParameterTypeContext { + val exec = parameter.declaringExecutable + val clazz = exec.declaringClass + val declarerName = clazz.name + '.' + exec.name + return ParameterTypeContext( + parameter.name, + parameter.annotatedType, + declarerName, + ReflectionUtils.forJavaReflectTypeSafe(type), + generics, + parameterIndex + ) +} + +private fun createStandardTypeParameter(type: org.javaruntype.type.Type<*>): StandardTypeParameter<*> { + val constructor = StandardTypeParameter::class.java.declaredConstructors.first() + constructor.isAccessible = true + return constructor.withAccessibility { + constructor.newInstance(type) as StandardTypeParameter<*> + } +} + +object QuickCheckExtensions { + + fun getRandomImplementerGenericContext(clazz: Class<*>, resolvedType: Type): GenericsContext? { + val sootClass = clazz.toSootClass() ?: return null + val implementers = sootClass.getImplementersOfWithChain() + val randomImplementersChain = + if (Random.getTrue(75)) { + if (Random.getTrue(50)) { + implementers + .minByOrNull { it.last().toJavaClass()?.constructors?.minByOrNull { it.parameterCount }?.parameterCount ?: Int.MAX_VALUE } + ?.drop(1) + } else { + implementers.shuffled().minByOrNull { it.size }?.drop(1) + } + } else { + implementers.randomOrNull()?.drop(1) + } ?: return null + //Deal with generics + val generics = mutableListOf>>() + var prevImplementer = clazz + resolvedType.getActualTypeArguments().forEachIndexed { index, typeVariable -> + if (prevImplementer.toClass() != null) { + generics.add(typeVariable to mutableListOf(prevImplementer.toClass()!!.typeParameters[index])) + } + } + for (implementer in randomImplementersChain) { + val javaImplementer = implementer.toJavaClass() ?: return null + val extendType = javaImplementer.let { it.genericInterfaces + it.genericSuperclass } + .find { it.toClass() == prevImplementer } + val tp = prevImplementer.typeParameters + prevImplementer = javaImplementer + if (tp.isEmpty()) continue + val newTp = extendType?.getActualTypeArguments()?.ifEmpty { return null } ?: return null + tp.mapIndexed { index, typeVariable -> typeVariable to newTp[index] } + .forEach { typeVar -> + val indexOfTypeParam = generics.indexOfFirst { it.second.last() == typeVar.first } + if (indexOfTypeParam != -1) { + generics[indexOfTypeParam].second.add(typeVar.second) + } + } + } + val g = + prevImplementer.typeParameters.associate { tp -> tp.name to generics.find { it.second.last() == tp }?.first } + val gm = LinkedHashMap() + g.forEach { + if (it.value != null) { + gm[it.key] = it.value!! + } + } + val m = mutableMapOf(prevImplementer to gm) + return GenericsContext(GenericsInfo(prevImplementer, m), prevImplementer) + } + + fun buildGenericsContextForInterfaceParent( + resolvedType: Type, + clazz: Class<*>, + parentChain: List> + ): GenericsContext? { + val generics = mutableListOf>>() + var curClass = clazz + resolvedType.getActualTypeArguments().forEachIndexed { index, typeVariable -> + if (curClass.toClass() != null) { + generics.add(typeVariable to mutableListOf(curClass.toClass()!!.typeParameters[index])) + } + } + for (parent in parentChain) { + val parentType = curClass.let { it.genericInterfaces.toList() + listOf(it.genericSuperclass) } + .find { it.toClass() == parent } + val tp = curClass.typeParameters + curClass = parent + if (tp.isEmpty()) continue + val newTp = parentType?.getActualTypeArguments()?.ifEmpty { return null } ?: return null + tp.mapIndexed { index, typeVariable -> typeVariable to newTp[index] } + .forEach { typeVar -> + val indexOfTypeParam = generics.indexOfFirst { it.second.last() == typeVar.first } + if (indexOfTypeParam != -1) { + generics[indexOfTypeParam].second.add(curClass.typeParameters[indexOfTypeParam]) + } + } + } + val g = curClass.typeParameters.associate { tp -> tp.name to generics.find { it.second.last() == tp }?.first } + val gm = LinkedHashMap() + g.forEach { + if (it.value != null) { + gm[it.key] = it.value!! + } + } + val m = mutableMapOf(curClass to gm) + return GenericsContext(GenericsInfo(curClass, m), curClass) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/UTGeneratorRepository.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/UTGeneratorRepository.kt new file mode 100644 index 0000000000..98ebf65b60 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/UTGeneratorRepository.kt @@ -0,0 +1,32 @@ +package org.utbot.greyboxfuzzer.generator + +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import org.utbot.greyboxfuzzer.util.logger +import org.utbot.greyboxfuzzer.quickcheck.internal.Zilch +import org.utbot.greyboxfuzzer.quickcheck.internal.generator.* + +class UTGeneratorRepository(random: SourceOfRandomness) : GeneratorRepository(random) { + + override fun generatorFor(parameter: ParameterTypeContext): Generator { + logger.debug { "TRYING TO GET GENERATOR FOR ${parameter.resolved}" } + if (parameter.resolved.name == Zilch::class.java.name) return ZilchGenerator() + val generator = super.generatorFor(parameter) + if (generator is MarkerInterfaceGenerator) { + throw IllegalArgumentException( + "Cannot find generator for " + parameter.name() + + " of type " + parameter.type().typeName + ) + } + return generator + } + +// override fun generatorsFor(parameter: ParameterTypeContext) = +// super.generatorsFor(parameter)//.onEach { GeneratorConfigurator.configureGenerator(it, 85) } +// +// override fun generatorForArrayType(parameter: ParameterTypeContext) = +// super.generatorForArrayType(parameter)//.also { GeneratorConfigurator.configureGenerator(it, 85) } + + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/UserClassGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/UserClassGenerator.kt new file mode 100644 index 0000000000..7d5c3987d3 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/UserClassGenerator.kt @@ -0,0 +1,205 @@ +@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") + +package org.utbot.greyboxfuzzer.generator.userclasses + +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import org.javaruntype.type.TypeParameter +import org.utbot.greyboxfuzzer.generator.* +import org.utbot.greyboxfuzzer.generator.userclasses.generator.* +import org.utbot.greyboxfuzzer.mutator.Mutator +import org.utbot.greyboxfuzzer.util.* +import org.utbot.greyboxfuzzer.util.logger +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.fieldClassId +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.* +import org.utbot.framework.UtSettings +import org.utbot.greyboxfuzzer.generator.userclasses.generator.ReflectionClassGenerator +import java.lang.reflect.* +import kotlin.random.Random + +class UserClassGenerator : ComponentizedGenerator(Any::class.java) { + + var clazz: Class<*>? = null + var parameterTypeContext: ParameterTypeContext? = null + var depth = 0 + var generationMethod = GenerationMethod.ANY + private val mutatedFields = mutableMapOf() + + override fun copy(): Generator { + return UserClassGenerator().also { + it.clazz = clazz + it.depth = depth + it.parameterTypeContext = parameterTypeContext + it.generationMethod = generationMethod + it.generatedUtModel = generatedUtModel + it.generationState = generationState + if (isGeneratorContextInitialized()) { + it.generatorContext = generatorContext + } + it.mutatedFields.putAll(mutatedFields.entries.map { it.key to it.value.copy() }) + } + } + + override fun canGenerateForParametersOfTypes(typeParameters: List>): Boolean { + return true + } + + override fun numberOfNeededComponents(): Int { + return parameterTypeContext?.resolved?.typeParameters?.size ?: 0 + } + + fun generate(random: SourceOfRandomness, status: GenerationStatus, generationMethod: GenerationMethod): UtModel { + this.generationMethod = generationMethod + return generateImpl(random, status) + } + + private fun regenerate(random: SourceOfRandomness, status: GenerationStatus): UtModel { + logger.debug { "Trying to generate ${parameterTypeContext!!.resolved}. Current depth: $depth" } + if (Random.getTrue(5)) { + return UtNullModel(clazz!!.id) + } + if (depth >= GreyBoxFuzzerGeneratorsAndSettings.maxDepthOfGeneration.toInt()) { + logger.debug { "Depth more than maxDepth ${GreyBoxFuzzerGeneratorsAndSettings.maxDepthOfGeneration.toInt()}. Return UtNullModel" } + return UtNullModel(clazz!!.id) + } + val immutableClazz = clazz!! + //TODO make this generators as usual generators + when (immutableClazz) { + Any::class.java -> return ObjectGenerator( + parameterTypeContext!!, + random, + status, + generatorContext + ).generate() + Class::class.java -> return ReflectionClassGenerator(parameterTypeContext!!, generatorContext).generate() + Type::class.java -> return ReflectionTypeGenerator(parameterTypeContext!!, generatorContext).generate() + //TODO implement field generator + Field::class.java -> return UtNullModel(fieldClassId) + } + //TODO! generate inner classes instances + if (immutableClazz.declaringClass != null && !immutableClazz.hasModifiers(Modifier.STATIC)) { + return UtNullModel(immutableClazz.id) + } + val resolvedJavaType = parameterTypeContext!!.generics.resolveType(parameterTypeContext!!.type()) + val gctx = + if (resolvedJavaType is Class<*> && parameterTypeContext!!.generics.genericsInfo.rootClass == immutableClazz) { + parameterTypeContext!!.generics + } else { + resolvedJavaType.createGenericsContext(immutableClazz) + } + if (!immutableClazz.canBeInstantiated()) { + return InterfaceImplementationsInstanceGenerator( + resolvedJavaType, + gctx, + GreyBoxFuzzerGeneratorsAndSettings.sourceOfRandomness, + GreyBoxFuzzerGeneratorsAndSettings.genStatus, + generatorContext, + depth + ).generate() + } + val unsafeGenerator = UnsafeBasedInstanceGenerator( + clazz!!, + gctx, + resolvedJavaType, + GreyBoxFuzzerGeneratorsAndSettings.sourceOfRandomness, + GreyBoxFuzzerGeneratorsAndSettings.genStatus, + generatorContext, + depth + ) + if (Random.getTrue(10) && UtSettings.useCompositeModelsInGreyBoxFuzzing) { + return unsafeGenerator.generate() + } + val generatedObject = ClassesInstanceGenerator( + clazz!!, + gctx, + parameterTypeContext!!.generics, + generationMethod, + GreyBoxFuzzerGeneratorsAndSettings.sourceOfRandomness, + GreyBoxFuzzerGeneratorsAndSettings.genStatus, + generatorContext, + depth + ).generate() + if (generatedObject is UtNullModel && Random.getTrue(75) && UtSettings.useCompositeModelsInGreyBoxFuzzing) { + return unsafeGenerator.generate() + } + return generatedObject + } + + override fun modify(random: SourceOfRandomness, status: GenerationStatus): UtModel { + if (generatedUtModel == null) { + return regenerate(random, status) + } + val cachedUtModel = generatedUtModel as? UtAssembleModel ?: return generatedUtModel!!.copy() + return if (Random.getTrue(80) && mutatedFields.isNotEmpty()) { + regenerateField(random, status, cachedUtModel) + } else { + setField(cachedUtModel) + } + } + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return modify(random, status) + } + + private fun regenerateField( + random: SourceOfRandomness, + status: GenerationStatus, + cachedUtModel: UtAssembleModel + ): UtModel { + val randomMutatedField = mutatedFields.keys.random() + val randomMutatedFieldGenerator = mutatedFields[randomMutatedField]!! + randomMutatedFieldGenerator.generationState = GenerationState.MODIFY + val newFieldValue = randomMutatedFieldGenerator.generateImpl(random, status) + val newModification = UtDirectSetFieldModel(cachedUtModel, randomMutatedField.fieldId, newFieldValue) + return cachedUtModel.addOrReplaceModification(newModification) + } + + private fun setField(cachedUtModel: UtAssembleModel): UtModel { + val sootClazz = clazz!!.toSootClass() ?: throw FuzzerIllegalStateException("Can't find soot class") + val randomField = clazz!!.getAllDeclaredFields().randomOrNull() ?: return cachedUtModel + val randomFieldDeclaringClass = randomField.declaringClass + val resolvedJavaType = parameterTypeContext!!.generics.resolveType(parameterTypeContext!!.type()) + val gctx = resolvedJavaType.createGenericsContext(clazz!!) + if (clazz == randomFieldDeclaringClass) { + return Mutator().regenerateFieldWithContext(gctx, cachedUtModel, randomField, generatorContext)?.let { + mutatedFields[randomField] = it.second + it.first + } ?: cachedUtModel + } else { + val chain = randomFieldDeclaringClass.toSootClass() + ?.getImplementersOfWithChain() + ?.filter { it.contains(sootClazz) } + ?.map { it.dropLastWhile { it != sootClazz } } + ?.minByOrNull { it.size } + ?.map { it.toJavaClass() } + if (chain == null || chain.isEmpty()) { + return cachedUtModel + } + val genericsContext = + QuickCheckExtensions.buildGenericsContextForInterfaceParent( + resolvedJavaType, + clazz!!, + chain.map { it!! }.reversed().drop(1) + ) ?: return cachedUtModel + return Mutator().regenerateFieldWithContext(genericsContext, cachedUtModel, randomField, generatorContext) + ?.let { + mutatedFields[randomField] = it.second + it.first + } ?: cachedUtModel + } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return if (generationState == GenerationState.MODIFY) { + modify(random, status) + } else { + regenerate(random, status) + }.also { generatedUtModel = it } + } +} diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ClassesInstanceGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ClassesInstanceGenerator.kt new file mode 100644 index 0000000000..c2bab5e679 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ClassesInstanceGenerator.kt @@ -0,0 +1,64 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.greyboxfuzzer.util.logger +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import ru.vyarus.java.generics.resolver.context.GenericsContext + +class ClassesInstanceGenerator( + private val clazz: Class<*>, + private val gctx: GenericsContext, + private val initialContext: GenericsContext?, + private val generationMethod: GenerationMethod, + private val sourceOfRandomness: SourceOfRandomness, + private val genStatus: GenerationStatus, + private val generatorContext: GeneratorContext, + private val depth: Int +): InstanceGenerator { + override fun generate(): UtModel { + val typeOfGenerations = when (generationMethod) { + GenerationMethod.CONSTRUCTOR -> mutableListOf('c') + GenerationMethod.STATIC -> mutableListOf('s') + else -> mutableListOf('c', 'c', 's') + } + while (typeOfGenerations.isNotEmpty()) { + val randomTypeOfGeneration = typeOfGenerations.randomOrNull() ?: return UtNullModel(clazz.id) + logger.debug { "Type of generation: $randomTypeOfGeneration" } + val generatedInstance = + try { + when (randomTypeOfGeneration) { + 'c' -> ConstructorBasedInstanceGenerator( + clazz, + gctx, + initialContext, + sourceOfRandomness, + genStatus, + generatorContext, + depth + ).generate() + 's' -> StaticsBasedInstanceGenerator( + clazz, + gctx, + sourceOfRandomness, + genStatus, + generatorContext, + depth + ).generate() + else -> null + } + } catch (_: Throwable) { + null + } + if (generatedInstance == null || generatedInstance is UtNullModel) { + typeOfGenerations.remove(randomTypeOfGeneration) + } else { + return generatedInstance + } + } + return UtNullModel(clazz.id) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ConstructorBasedInstanceGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ConstructorBasedInstanceGenerator.kt new file mode 100644 index 0000000000..2dbbdee4ae --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ConstructorBasedInstanceGenerator.kt @@ -0,0 +1,82 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.common.isPublic +import org.utbot.greyboxfuzzer.util.* +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import ru.vyarus.java.generics.resolver.context.GenericsContext +import java.lang.reflect.Constructor +import java.lang.reflect.Modifier +import kotlin.random.Random + +class ConstructorBasedInstanceGenerator( + private val clazz: Class<*>, + private val gctx: GenericsContext, + private val initialGenericContext: GenericsContext?, + private val sourceOfRandomness: SourceOfRandomness, + private val generationStatus: GenerationStatus, + private val generatorContext: GeneratorContext, + private val depth: Int +): InstanceGenerator { + + override fun generate(): UtModel { + val constructor = chooseRandomConstructor(clazz) ?: return UtNullModel(clazz.id) + val resolvedConstructor = + //In case if we can not resolve constructor + gctx.constructor(constructor).let { + try { + it.toString() + it + } catch (_: Throwable) { + try { + initialGenericContext?.constructor(constructor) + } catch (_: Throwable) { + return UtNullModel(clazz.id) + } + } + } + return ExecutableInvoker( + constructor, + clazz, + constructor.executableId, + resolvedConstructor, + sourceOfRandomness, + generationStatus, + generatorContext, + depth + ).invoke() + } + + private fun chooseRandomConstructor(clazz: Class<*>): Constructor<*>? { + val randomPublicConstructor = + try { + clazz.declaredConstructors + .filter { + it.isPublic || !it.hasAtLeastOneOfModifiers( + Modifier.PROTECTED, + Modifier.PRIVATE + ) + } + //Avoiding recursion +// .filter { it.parameterTypes.all { !it.name.contains(clazz.name) } } + .chooseRandomConstructor() + } catch (_: Throwable) { + null + } + val randomConstructor = + try { + clazz.declaredConstructors + .filter { it.parameterTypes.all { !it.name.contains(clazz.name) } } + .toList().chooseRandomConstructor() + } catch (_: Throwable) { + null + } + return if (Random.getTrue(75)) randomPublicConstructor ?: randomConstructor else randomConstructor + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ExecutableInvoker.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ExecutableInvoker.kt new file mode 100644 index 0000000000..e5f1da749b --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ExecutableInvoker.kt @@ -0,0 +1,42 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.greyboxfuzzer.generator.DataGenerator +import org.utbot.greyboxfuzzer.generator.createParameterContextForParameter +import org.utbot.greyboxfuzzer.util.constructAssembleModelUsingMethodInvocation +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import ru.vyarus.java.generics.resolver.context.GenericsContext +import java.lang.reflect.Executable + +class ExecutableInvoker( + private val executable: Executable, + private val clazz: Class<*>, + private val executableId: ExecutableId, + private val genericsContext: GenericsContext?, + private val sourceOfRandomness: SourceOfRandomness, + private val generationStatus: GenerationStatus, + private val generatorContext: GeneratorContext, + private val depth: Int +) { + fun invoke(): UtModel { + val parameterValues = executable.parameters.withIndex().map { indexedParameter -> + val parameterContext = + if (genericsContext != null) { + createParameterContextForParameter(indexedParameter.value, indexedParameter.index, genericsContext) + } else { + ParameterTypeContext.forParameter(indexedParameter.value) + } + DataGenerator.generateUtModel(parameterContext, depth, generatorContext, sourceOfRandomness, generationStatus) + } + return generatorContext.utModelConstructor.constructAssembleModelUsingMethodInvocation( + clazz, + executableId, + parameterValues, + generatorContext + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/GenerationMethod.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/GenerationMethod.kt new file mode 100644 index 0000000000..1ce86fbb9b --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/GenerationMethod.kt @@ -0,0 +1,5 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +enum class GenerationMethod { + CONSTRUCTOR, STATIC, STATIC_EXT, ANY +} diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/InstanceGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/InstanceGenerator.kt new file mode 100644 index 0000000000..8c00f12138 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/InstanceGenerator.kt @@ -0,0 +1,7 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.framework.plugin.api.UtModel + +interface InstanceGenerator { + fun generate(): UtModel +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/InterfaceImplementationsInstanceGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/InterfaceImplementationsInstanceGenerator.kt new file mode 100644 index 0000000000..2d9557681f --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/InterfaceImplementationsInstanceGenerator.kt @@ -0,0 +1,242 @@ +@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") + +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.common.isAbstract +import org.utbot.greyboxfuzzer.generator.QuickCheckExtensions +import org.utbot.greyboxfuzzer.util.* +import org.utbot.greyboxfuzzer.util.logger +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.greyboxfuzzer.generator.GreyBoxFuzzerGeneratorsAndSettings.generatorRepository +import org.utbot.greyboxfuzzer.generator.getOrProduceGenerator +import org.utbot.greyboxfuzzer.generator.userclasses.UserClassGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import ru.vyarus.java.generics.resolver.context.GenericsContext +import java.lang.reflect.Type +import kotlin.random.Random + +class InterfaceImplementationsInstanceGenerator( + private val resolvedType: Type, + private val typeContext: GenericsContext, + private val sourceOfRandomness: SourceOfRandomness, + private val generationStatus: GenerationStatus, + private val generatorContext: GeneratorContext, + private val depth: Int +) : InstanceGenerator { + override fun generate(): UtModel { + //Try to generate with statics with some probability + val clazz = resolvedType.toClass() ?: return UtNullModel(objectClassId) + if (clazz.name.contains("HttpOutputMessage")) { + println() + } + if (Random.getTrue(50)) { + try { + StaticsBasedInstanceGenerator( + clazz, + typeContext, + sourceOfRandomness, + generationStatus, + generatorContext, + depth + ).generate().let { + if (it !is UtNullModel) return it + } + } catch (e: Throwable) { + logger.error { "Something wrong during StaticsBasedInstanceGenerator work" } + } + } + val genericsContext = + QuickCheckExtensions.getRandomImplementerGenericContext( + clazz, + resolvedType + )// ?: return UtNullModel(clazz.id) + logger.debug { "Implementer of ${clazz.name}! It is a ${genericsContext?.currentClass()?.name}" } + if (genericsContext == null || Random.getTrue(5)) { + logger.debug { "Generate mock anyway" } + return try { + generateMock(clazz, resolvedType, typeContext, generatorContext) + } catch (e: Throwable) { + UtNullModel(clazz.id) + } + } + logger.debug { "Trying to generate implementer ${genericsContext.currentClass().name}" } + val resUtModel = + ClassesInstanceGenerator( + genericsContext.currentClass(), + genericsContext, + null, + GenerationMethod.ANY, + sourceOfRandomness, + generationStatus, + generatorContext, + depth + ).generate() + return when (resUtModel) { + is UtAssembleModel -> UtAssembleModel( + resUtModel.id, + clazz.id, + resUtModel.modelName, + resUtModel.instantiationCall, + resUtModel.origin + ) { resUtModel.modificationsChain } + is UtCompositeModel -> UtCompositeModel( + resUtModel.id, + clazz.id, + resUtModel.isMock, + resUtModel.fields, + resUtModel.mocks + ) + else -> resUtModel + } + .let { + if (it is UtNullModel && Random.getTrue(50)) { + try { + generateMock( + clazz, + resolvedType, + typeContext, + generatorContext + ) + }catch (e: Throwable) { + it + } + } + else { + it + } + } + } + + private fun generateMock( + clazz: Class<*>, + resolvedType: Type, + typeContext: GenericsContext, + generatorContext: GeneratorContext + ): UtModel { + logger.debug { "Mock generation" } + if (!clazz.isInterface) return UtNullModel(clazz.id) + val sootClazz = clazz.toSootClass() ?: return UtNullModel(clazz.id) + val constructor = generatorContext.utModelConstructor + val allNeededInterfaces = clazz.methods.map { it.declaringClass }.filter { it != clazz }.toSet() + val allChainToGenericsContext = allNeededInterfaces.map { it to ParameterTypeContext.forClass(it).generics } + (clazz to typeContext) +// if (allNeededInterfaces.all { it.typeParameters.isEmpty() }) { +// allNeededInterfaces.map { it to ParameterTypeContext.forType(it).generics } +// } else { +// //TODO debug this +// val chainToGenericsContext = allNeededInterfaces.map { cl -> +// val chain = cl.toSootClass() +// ?.getImplementersOfWithChain(onlyConcreteClasses = false, allowNotOnlyStdLib = true) +// ?.filter { it.contains(sootClazz) } +// ?.map { it.dropLastWhile { it != sootClazz } } +// ?.minByOrNull { it.size } +// ?.map { it.toJavaClass() } +// if (chain == null || chain.any { it == null }) { +// null +// } else { +// cl to QuickCheckExtensions.buildGenericsContextForInterfaceParent( +// resolvedType, +// clazz, +// chain.map { it!! }.reversed().drop(1) +// ) +// } +// } +// chainToGenericsContext + (clazz to typeContext) +// } + //val allChainToGenericsContext = chainToGenericsContext + (clazz to typeContext) + val mocks = clazz.methods + .filter { it.isAbstract } + .associateTo(mutableMapOf()) { method -> + val genericsContextForMethod = + try { + allChainToGenericsContext.find { it!!.first == method.declaringClass }?.second + } catch (e: Throwable) { + null + } + val methodReturnType = + if (genericsContextForMethod != null) { + genericsContextForMethod.method(method).resolveReturnType().let { + if (it.toClass() == null) method.returnType else it + } + } else { + method.returnType + } + val parameterTypeContext = ParameterTypeContext.forType(methodReturnType, genericsContextForMethod) + val generatedUtModelWithReturnType = + try { + generateUtModelForMock(parameterTypeContext, depth, generatorContext, sourceOfRandomness, generationStatus) + } catch (_: Throwable) { + UtNullModel(methodReturnType.toClass()!!.id) + } + method.executableId as ExecutableId to listOf(generatedUtModelWithReturnType) + } + return UtCompositeModel(constructor.computeUnusedIdAndUpdate(), clazz.id, isMock = true, mocks = mocks) + } + + private fun generateUtModelForMock( + parameterTypeContext: ParameterTypeContext, + depth: Int = 0, + generatorContext: GeneratorContext, + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val classId = parameterTypeContext.rawClass.id + logger.debug { "Trying to generate UtModel of type ${classId.name} 3 times" } + if (parameterTypeContext.getAllSubParameterTypeContexts(sourceOfRandomness).any { it.rawClass.isInterface }) { + return UtNullModel(classId) + } + var generatedInstance: UtModel? + repeat(3) { + generatedInstance = + try { + val generator = + generatorRepository.getOrProduceGenerator(parameterTypeContext, generatorContext, depth) + ?: return@repeat + generator.generateImpl(random, status) + } catch (_: Throwable) { + null + } + generatedInstance?.let { if (it !is UtNullModel) return it } + } + return UtNullModel(classId) + } + +// private fun buildGenericsContextForInterfaceParent(resolvedType: Type, clazz: Class<*>, parentChain: List>): GenericsContext? { +// val generics = mutableListOf>>() +// var curClass = clazz +// resolvedType.getActualTypeArguments().forEachIndexed { index, typeVariable -> +// if (curClass.toClass() != null) { +// generics.add(typeVariable to mutableListOf(curClass.toClass()!!.typeParameters[index])) +// } +// } +// for (parent in parentChain) { +// val parentType = curClass.let { it.genericInterfaces.toList() + listOf(it.genericSuperclass) } +// .find { it.toClass() == parent } +// val tp = curClass.typeParameters +// curClass = parent +// if (tp.isEmpty()) continue +// val newTp = parentType?.getActualTypeArguments()?.ifEmpty { return null } ?: return null +// tp.mapIndexed { index, typeVariable -> typeVariable to newTp[index] } +// .forEach { typeVar -> +// val indexOfTypeParam = generics.indexOfFirst { it.second.last() == typeVar.first } +// if (indexOfTypeParam != -1) { +// generics[indexOfTypeParam].second.add(curClass.typeParameters[indexOfTypeParam]) +// } +// } +// } +// val g = curClass.typeParameters.associate { tp -> tp.name to generics.find { it.second.last() == tp }?.first } +// val gm = LinkedHashMap() +// g.forEach { +// if (it.value != null) { +// gm[it.key] = it.value!! +// } +// } +// val m = mutableMapOf(curClass to gm) +// return GenericsContext(GenericsInfo(curClass, m), curClass) +// } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ObjectGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ObjectGenerator.kt new file mode 100644 index 0000000000..eaf705a713 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ObjectGenerator.kt @@ -0,0 +1,49 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.greyboxfuzzer.generator.GreyBoxFuzzerGeneratorsAndSettings +import org.utbot.greyboxfuzzer.generator.getOrProduceGenerator +import org.utbot.greyboxfuzzer.util.getAllTypesFromCastAndInstanceOfInstructions +import org.utbot.greyboxfuzzer.util.getTrue +import org.utbot.greyboxfuzzer.util.toSootMethod +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import ru.vyarus.java.generics.resolver.context.MethodGenericsContext +import kotlin.random.Random +import kotlin.system.exitProcess + +class ObjectGenerator( + private val parameterTypeContext: ParameterTypeContext, + private val sourceOfRandomness: SourceOfRandomness, + private val generationStatus: GenerationStatus, + private val generatorContext: GeneratorContext +) : InstanceGenerator { + override fun generate(): UtModel { + val currentSootMethod = + (parameterTypeContext.generics as? MethodGenericsContext)?.currentMethod()?.toSootMethod() + val potentialUsefulClasses = + currentSootMethod?.getAllTypesFromCastAndInstanceOfInstructions() + val potentialInterestingObjectReplacement = + if (potentialUsefulClasses?.isNotEmpty() == true && Random.getTrue(70)) { + val randomClass = potentialUsefulClasses.random() + val generator = GreyBoxFuzzerGeneratorsAndSettings.generatorRepository + .getOrProduceGenerator(randomClass, generatorContext) + ?.also { it.generatorContext = generatorContext } + generator?.generateImpl(sourceOfRandomness, generationStatus) + } else null + potentialInterestingObjectReplacement?.let { return it } + val generator = GreyBoxFuzzerGeneratorsAndSettings.generatorRepository + .getGenerators() + .toList() + .flatMap { it.second } + .filter { !it.hasComponents() } + .randomOrNull() + ?.also { it.generatorContext = generatorContext } + return generator?.generateImpl(sourceOfRandomness, generationStatus) + ?: UtNullModel(parameterTypeContext.rawClass.id) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ReflectionClassGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ReflectionClassGenerator.kt new file mode 100644 index 0000000000..7d43e8d05a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ReflectionClassGenerator.kt @@ -0,0 +1,45 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.greyboxfuzzer.util.getTrue +import org.utbot.greyboxfuzzer.util.toJavaClass +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.util.getAllTypesFromCastAndInstanceOfInstructions +import org.utbot.greyboxfuzzer.util.toSootMethod +import ru.vyarus.java.generics.resolver.context.MethodGenericsContext +import soot.Scene +import kotlin.random.Random + +class ReflectionClassGenerator( + private val parameterTypeContext: ParameterTypeContext, + private val generatorContext: GeneratorContext +) : InstanceGenerator { + override fun generate(): UtModel { + val packageName = parameterTypeContext.declarerName.substringBeforeLast('.') + val currentSootMethod = + (parameterTypeContext.generics as? MethodGenericsContext)?.currentMethod()?.toSootMethod() + val potentialUsefulClasses = + currentSootMethod?.getAllTypesFromCastAndInstanceOfInstructions() ?: setOf() + val randomClassToGenerate = + if (potentialUsefulClasses.isNotEmpty() && Random.getTrue(50)) { + potentialUsefulClasses.random() + } else { + Scene.v().classes + .filter { it.name.startsWith(packageName) } + .filterNot { it.isInnerClass } + .mapNotNull { it.toJavaClass() } + .randomOrNull() + } + if (randomClassToGenerate != null) { + return generatorContext.utModelConstructor.construct(randomClassToGenerate, Class::class.java.id) + } +// Scene.v().classes.randomOrNull()?.toJavaClass()?.let { +// if (Random.getTrue(75)) { +// return generatorContext.utModelConstructor.construct(it, Class::class.java.id) +// } +// } + return generatorContext.utModelConstructor.construct(Any::class.java, Class::class.java.id) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ReflectionTypeGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ReflectionTypeGenerator.kt new file mode 100644 index 0000000000..0c422ce542 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/ReflectionTypeGenerator.kt @@ -0,0 +1,77 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.greyboxfuzzer.util.getTrue +import org.utbot.greyboxfuzzer.util.toJavaClass +import org.utbot.greyboxfuzzer.util.toSootMethod +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.greyboxfuzzer.generator.GreyBoxFuzzerGeneratorsAndSettings +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.util.getAllTypesFromCastAndInstanceOfInstructions +import ru.vyarus.java.generics.resolver.context.MethodGenericsContext +import soot.NullType +import soot.Scene +import soot.jimple.Stmt +import java.lang.reflect.Type +import kotlin.random.Random + +class ReflectionTypeGenerator( + private val parameterTypeContext: ParameterTypeContext, + private val generatorContext: GeneratorContext + ) : InstanceGenerator { + + override fun generate(): UtModel { + val currentSootMethod = + (parameterTypeContext.generics as? MethodGenericsContext)?.currentMethod()?.toSootMethod() + val potentialUsefulClasses = + currentSootMethod?.getAllTypesFromCastAndInstanceOfInstructions() ?: setOf() + val randomTypeToGenerate = + if (Random.getTrue(50) && potentialUsefulClasses.isNotEmpty()) { + potentialUsefulClasses.random() + } else { + currentSootMethod?.activeBody?.units?.filterIsInstance() + ?.flatMap { it.useBoxes.map { it.value.type } } + ?.filter { it !is NullType } + ?.toSet() + ?.mapNotNull { + try { + it.classId.jClass + } catch (e: Throwable) { + null + } + }?.randomOrNull() + } + if (Random.getTrue(50) && randomTypeToGenerate != null) { + return generatorContext.utModelConstructor.construct(randomTypeToGenerate, Type::class.java.id) + } + + val packageName = parameterTypeContext.declarerName.substringBeforeLast('.') + val randomClassFromSamePackage = + Scene.v().classes + .filter { it.name.startsWith(packageName) } + .filterNot { it.isInnerClass } + .mapNotNull { it.toJavaClass() } + .randomOrNull() + if (randomClassFromSamePackage != null && Random.getTrue(25)) { + return generatorContext.utModelConstructor.construct(randomClassFromSamePackage, Type::class.java.id) + } + return GreyBoxFuzzerGeneratorsAndSettings.generatorRepository + .getGenerators() + .toList() + .random() + .let { + generatorContext.utModelConstructor.construct(it.first, Type::class.java.id) + } +// try { +// Scene.v().classes.randomOrNull()?.toJavaClass()?.let { +// return generatorContext.utModelConstructor.construct(it, Type::class.java.id) +// } +// } catch (e: Throwable) { +// return generatorContext.utModelConstructor.construct(Any::class.java, Type::class.java.id) +// } + return generatorContext.utModelConstructor.construct(Any::class.java, Type::class.java.id) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/StaticsBasedInstanceGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/StaticsBasedInstanceGenerator.kt new file mode 100644 index 0000000000..de5767ac5f --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/StaticsBasedInstanceGenerator.kt @@ -0,0 +1,30 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import ru.vyarus.java.generics.resolver.context.GenericsContext +import kotlin.random.Random + +open class StaticsBasedInstanceGenerator( + private val clazz: Class<*>, + private val gctx: GenericsContext, + private val sourceOfRandomness: SourceOfRandomness, + private val generationStatus: GenerationStatus, + private val generatorContext: GeneratorContext, + private val depth: Int +) : InstanceGenerator { + override fun generate(): UtModel { + val staticMethodBasedGenerator = + StaticsMethodBasedInstanceGenerator(clazz, gctx, sourceOfRandomness, generationStatus, generatorContext, depth) + val staticFieldBasedGenerator = + StaticsFieldBasedInstanceGenerator(clazz, gctx, generatorContext) + //TODO: repair StaticFieldBasedGenerator + return if (true) { + staticMethodBasedGenerator.generate() ?: staticFieldBasedGenerator.generate() + } else { + staticFieldBasedGenerator.generate() ?: staticMethodBasedGenerator.generate() + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/StaticsFieldBasedInstanceGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/StaticsFieldBasedInstanceGenerator.kt new file mode 100644 index 0000000000..52380ba084 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/StaticsFieldBasedInstanceGenerator.kt @@ -0,0 +1,71 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.* +import org.utbot.greyboxfuzzer.util.* +import ru.vyarus.java.generics.resolver.context.GenericsContext +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import java.util.* + +internal class StaticsFieldBasedInstanceGenerator( + private val clazz: Class<*>, + private val gctx: GenericsContext, + private val generatorContext: GeneratorContext +) : InstanceGenerator { + override fun generate(): UtModel = + getRandomStaticToProduceInstanceUsingSoot()?.let { fieldToProvideInstance -> + createUtModelForStaticFieldInvocation(generatorContext.utModelConstructor, fieldToProvideInstance) + } ?: UtNullModel(clazz.id) + + //In case of no Soot + private fun getStaticFieldToProduceInstance(): Field? { + val resolvedStaticFields = + try { + clazz.declaredFields.filter { it.hasModifiers(Modifier.STATIC) } + .map { it to gctx.resolveFieldType(it) } + .filter { it.first.type.toClass() == clazz } + } catch (e: Error) { + listOf() + } + return resolvedStaticFields.randomOrNull()?.first + } + + private fun getRandomStaticToProduceInstanceUsingSoot(): Field? = + SootStaticsCollector.getStaticFieldsInitializersOf(clazz).randomOrNull() + + private fun createUtModelForStaticFieldInvocation( + utModelConstructor: FuzzerUtModelConstructor, + field: Field + ): UtAssembleModel { + with(utModelConstructor) { + val classInstanceModel = construct(clazz, classClassId) as UtReferenceModel + + val fieldModelId = computeUnusedIdAndUpdate() + val fieldModel = UtAssembleModel( + fieldModelId, + Field::class.java.id, + "field_$fieldModelId", + UtExecutableCallModel( + classInstanceModel, + Class<*>::getField.executableId, + listOf(construct(field.name, stringClassId)), + ) + ) + + val generatedModelId = computeUnusedIdAndUpdate() + return UtAssembleModel( + id = generatedModelId, + classId = classIdForType(field.type), + modelName = "value_$generatedModelId", + UtExecutableCallModel( + fieldModel, + Field::get.executableId, + listOf(UtNullModel(clazz.id)), + ) + ) + } + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/StaticsMethodBasedInstanceGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/StaticsMethodBasedInstanceGenerator.kt new file mode 100644 index 0000000000..2d0564da73 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/StaticsMethodBasedInstanceGenerator.kt @@ -0,0 +1,61 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.greyboxfuzzer.util.SootStaticsCollector +import org.utbot.greyboxfuzzer.util.hasModifiers +import org.utbot.greyboxfuzzer.util.toClass +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.* +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import ru.vyarus.java.generics.resolver.context.GenericsContext +import java.lang.reflect.Method +import java.lang.reflect.Modifier + +//TODO filter not suitable methods with generics with bad bounds +//TODO make it work for subtypes +internal class StaticsMethodBasedInstanceGenerator( + private val clazz: Class<*>, + private val gctx: GenericsContext, + private val sourceOfRandomness: SourceOfRandomness, + private val generationStatus: GenerationStatus, + private val generatorContext: GeneratorContext, + private val depth: Int +) : InstanceGenerator { + override fun generate(): UtModel = + getRandomStaticToProduceInstanceUsingSoot()?.let { methodToProvideInstance -> + val resolvedMethodContext = + try { + gctx.method(methodToProvideInstance) + } catch (_: Throwable) { + null + } + ExecutableInvoker( + methodToProvideInstance, + clazz, + methodToProvideInstance.executableId, + resolvedMethodContext, + sourceOfRandomness, + generationStatus, + generatorContext, + depth + ).invoke() + } ?: UtNullModel(clazz.id) + + //In case of no Soot + private fun getRandomStaticToProduceInstance(): Method? = + try { + clazz.declaredMethods.filter { it.hasModifiers(Modifier.STATIC) } + .map { it to gctx.method(it).resolveReturnType() } + .filter { it.first.returnType.toClass() == clazz } + .filter { it.first.parameterTypes.all { !it.name.contains(clazz.name) } } + .randomOrNull()?.first + } catch (e: Error) { + null + } + + private fun getRandomStaticToProduceInstanceUsingSoot(): Method? = + SootStaticsCollector + .getStaticMethodsInitializersOf(clazz) + .randomOrNull() +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/UnsafeBasedInstanceGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/UnsafeBasedInstanceGenerator.kt new file mode 100644 index 0000000000..4dc159f44e --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/generator/userclasses/generator/UnsafeBasedInstanceGenerator.kt @@ -0,0 +1,85 @@ +package org.utbot.greyboxfuzzer.generator.userclasses.generator + +import org.utbot.greyboxfuzzer.generator.DataGenerator +import org.utbot.greyboxfuzzer.generator.QuickCheckExtensions +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import org.utbot.greyboxfuzzer.util.* +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.id +import ru.vyarus.java.generics.resolver.context.GenericsContext +import java.lang.reflect.Modifier +import java.lang.reflect.Type + +class UnsafeBasedInstanceGenerator( + private val clazz: Class<*>, + private val typeContext: GenericsContext, + private val resolvedType: Type, + private val sourceOfRandomness: SourceOfRandomness, + private val generationStatus: GenerationStatus, + private val generatorContext: GeneratorContext, + private val depth: Int +) : InstanceGenerator { + override fun generate(): UtModel { + val fields = clazz.getAllDeclaredFields() + .filterNot { it.hasModifiers(Modifier.FINAL, Modifier.STATIC) || it.hasModifiers(Modifier.STATIC) } + val sootClazz = clazz.toSootClass() ?: return UtNullModel(clazz.id) + val constructor = generatorContext.utModelConstructor + val allNeededContexts = fields.map { it.declaringClass }.filter { it != clazz }.toSet() + val chainToGenericsContext = allNeededContexts.map { cl -> + val chain = cl.toSootClass() + ?.getImplementersOfWithChain() + ?.filter { it.contains(sootClazz) } + ?.map { it.dropLastWhile { it != sootClazz } } + ?.minByOrNull { it.size } + ?.map { it.toJavaClass() } + if (chain == null || chain.any { it == null }) { + null + } else { + cl to QuickCheckExtensions.buildGenericsContextForInterfaceParent( + resolvedType, + clazz, + chain.map { it!! }.reversed().drop(1) + ) + } + } + val allChainToGenericsContext = chainToGenericsContext + (clazz to typeContext) + val fieldsMocks = fields + .associateTo(mutableMapOf()) { field -> + val genericsContextForField = + allChainToGenericsContext.find { it!!.first == field.declaringClass }?.second + val fieldType = + if (genericsContextForField != null) { + genericsContextForField.resolveFieldType(field).let { + if (it.toClass() == null) field.type else it + } + } else { + field.type + } + val parameterTypeContext = ParameterTypeContext.forType(fieldType, genericsContextForField) + val generatedUtModelWithReturnType = + try { + DataGenerator.generateUtModel( + parameterTypeContext, + depth, + generatorContext, + sourceOfRandomness, + generationStatus + ) + } catch (_: Throwable) { + UtNullModel(fieldType.toClass()!!.id) + } + field.fieldId to generatedUtModelWithReturnType + } + return UtCompositeModel(constructor.computeUnusedIdAndUpdate(), clazz.id, isMock = true, fields = fieldsMocks) + } + + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/Mutator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/Mutator.kt new file mode 100644 index 0000000000..6f7251ac50 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/Mutator.kt @@ -0,0 +1,209 @@ +package org.utbot.greyboxfuzzer.mutator + +import org.javaruntype.type.Types +import org.utbot.greyboxfuzzer.generator.* +import org.utbot.greyboxfuzzer.util.* +import org.utbot.greyboxfuzzer.util.logger +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.* +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationState +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import ru.vyarus.java.generics.resolver.context.GenericsContext +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import kotlin.random.Random + +class Mutator { + + fun mutateSeed(seed: Seed, sourceOfRandomness: SourceOfRandomness, genStatus: GenerationStatus): Seed { + val seedCopy = seed.copy() + //If seed is empty mutate this + if (seedCopy.parameters.isEmpty()) return seed + + val randomParameterIndex = Random.nextInt(0, seedCopy.parameters.size) + val randomParameter = seedCopy.parameters.getOrNull(randomParameterIndex) ?: return seed + val randomParameterGenerator = randomParameter.generator ?: return seed + randomParameterGenerator.generationState = GenerationState.MODIFY + randomParameterGenerator.generatorContext.startCheckpoint() + val newUtModel = randomParameterGenerator.generateImpl(sourceOfRandomness, genStatus) + val newFParameter = randomParameter.replaceUtModel(newUtModel) + return seedCopy.replaceFParameter(randomParameterIndex, newFParameter) + } + + private fun regenerateFields( + clazz: Class<*>, + classInstance: UtAssembleModel, + fieldsToRegenerate: List, + generatorContext: GeneratorContext + ): UtModel { + val modifications = mutableListOf() + try { + val parameterTypeContext = ParameterTypeContext.forClass(clazz) + fieldsToRegenerate.mapNotNull { + setNewFieldValue( + it, + parameterTypeContext.generics, + classInstance, + generatorContext + ) + }.forEach { modifications.add(it) } + } catch (e: Throwable) { + logger.debug { "Exception while mutation: ${e.stackTrace.joinToString("\n")}" } + } + return classInstance.addModification(modifications) + } + + fun regenerateFieldsWithContext( + genericsContext: GenericsContext, + classInstance: UtAssembleModel, + fieldsToRegenerate: List, + generatorContext: GeneratorContext + ): UtModel { + val modifications = + fieldsToRegenerate.mapNotNull { setNewFieldValue(it, genericsContext, classInstance, generatorContext) } + return classInstance.addModification(modifications) + } + + fun regenerateFieldWithContext( + genericsContext: GenericsContext, + classInstance: UtAssembleModel, + fieldToRegenerate: Field, + generatorContext: GeneratorContext, + ): Pair? = + setNewFieldValueWithGenerator( + fieldToRegenerate, + genericsContext, + classInstance, + generatorContext + )?.let { (generator, modification) -> + classInstance.addOrReplaceModification(modification) to generator + } + + private fun setNewFieldValueWithGenerator( + field: Field, + genericsContext: GenericsContext, + clazzInstance: UtAssembleModel, + generatorContext: GeneratorContext, + ): Pair? { + if (field.hasModifiers( + Modifier.STATIC, + Modifier.FINAL + ) + ) return null +// val setterForField = +// if (field.hasAtLeastOneOfModifiers(Modifier.PRIVATE, Modifier.PROTECTED)) { +// field.declaringClass.declaredMethods +// .filter { it.parameterTypes.size == 1 && it.parameterTypes[0] == field.type } +// .filter { it.name.startsWith("set") } +// .randomOrNull() ?: return null +// } else null + val fieldType = genericsContext.resolveFieldType(field) + logger.debug { "F = $field TYPE = $fieldType" } + val parameterTypeContextForResolvedType = ParameterTypeContext( + field.name, + field.annotatedType, + field.declaringClass.name, + Types.forJavaLangReflectType(fieldType), + genericsContext + ) + val generatorForField = + GreyBoxFuzzerGeneratorsAndSettings.generatorRepository.getOrProduceGenerator( + parameterTypeContextForResolvedType, + generatorContext, + 0 + ) + ?: return null + var newFieldValue: UtModel = UtNullModel(parameterTypeContextForResolvedType.rawClass.id) + for (i in 0 until 3) { + try { + generatorForField.generationState = GenerationState.REGENERATE + generatorForField.generatorContext.startCheckpoint() + newFieldValue = generatorForField.generateImpl( + GreyBoxFuzzerGeneratorsAndSettings.sourceOfRandomness, + GreyBoxFuzzerGeneratorsAndSettings.genStatus + ) + if (newFieldValue !is UtNullModel) break + } catch (e: Throwable) { + continue + } + } + logger.debug { "NEW FIELD VALUE = $newFieldValue" } +// return if (setterForField == null) { +// generatorForField to UtDirectSetFieldModel(clazzInstance, field.fieldId, newFieldValue) +// } else { +// generatorForField to UtExecutableCallModel(clazzInstance, setterForField.executableId, listOf(newFieldValue)) +// } + return generatorForField to UtDirectSetFieldModel(clazzInstance, field.fieldId, newFieldValue) + } + + private fun setNewFieldValue( + field: Field, + genericsContext: GenericsContext, + clazzInstance: UtAssembleModel, + generatorContext: GeneratorContext, + ): UtStatementModel? = + setNewFieldValueWithGenerator(field, genericsContext, clazzInstance, generatorContext)?.second + + fun mutateThisInstance( + thisInstance: ThisInstance, + fieldsToRegenerate: List, + generatorContext: GeneratorContext + ): ThisInstance { + if (thisInstance !is NormalMethodThisInstance) return thisInstance + val thisInstanceAsUtModel = thisInstance.utModel as? UtAssembleModel ?: return thisInstance + val allClassFieldsFiltered = thisInstance.classId.allDeclaredFieldIds.toList() + .mapNotNull { + try { + it.jField + } catch (e: Throwable) { + null + } + } + .shuffled() + .take(Random.nextInt(0, 4)) + val finalFieldsToRegenerate = (fieldsToRegenerate.filter { Random.getTrue(50) } + allClassFieldsFiltered).toSet() + val mutationResult = + regenerateFields( + thisInstance.classId.jClass, + thisInstanceAsUtModel, + finalFieldsToRegenerate.toList(), + generatorContext + ) + return NormalMethodThisInstance(mutationResult, thisInstance.generator, thisInstance.classId) + } + + fun mutateParameter( + fParameter: FParameter, + generatorContext: GeneratorContext, + ): FParameter { + val originalParameter = fParameter.parameter + val originalUtModel = fParameter.utModel + val randomMethod = fParameter.classId.allMethods.toList().randomOrNull() ?: return fParameter + val parametersForMethodInvocation = + randomMethod.method.parameters.mapIndexed { index, parameter -> + val resolvedParameterCtx = + originalParameter.resolveParameterTypeAndBuildParameterContext(index, randomMethod.method) + val generatorForParameter = + GreyBoxFuzzerGeneratorsAndSettings.generatorRepository.getOrProduceGenerator( + resolvedParameterCtx, + generatorContext, + 0 + ) + ?: return fParameter + DataGenerator.generate( + generatorForParameter, + parameter, + GreyBoxFuzzerGeneratorsAndSettings.sourceOfRandomness, + GreyBoxFuzzerGeneratorsAndSettings.genStatus + ).utModel + } + val callModel = + UtExecutableCallModel(fParameter.utModel as UtReferenceModel, randomMethod, parametersForMethodInvocation) + (originalUtModel as? UtAssembleModel)?.addModification(listOf(callModel)) + return FParameter(originalParameter, null, fParameter.utModel, fParameter.generator, fParameter.fields) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/ObjectMerger.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/ObjectMerger.kt new file mode 100644 index 0000000000..3a6787aa03 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/ObjectMerger.kt @@ -0,0 +1,14 @@ +package org.utbot.greyboxfuzzer.mutator + +import org.utbot.greyboxfuzzer.generator.FParameter + +class ObjectMerger { + + fun mergeObjects(obj1: FParameter, obj2: FParameter) { + val obj1SubFields = obj1.getAllSubFields() + val obj2SubFields = obj2.getAllSubFields() + val randomSubField = obj1SubFields.randomOrNull() ?: return + + return + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/Seed.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/Seed.kt new file mode 100644 index 0000000000..bf57925d9d --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/Seed.kt @@ -0,0 +1,31 @@ +package org.utbot.greyboxfuzzer.mutator + +import org.utbot.greyboxfuzzer.generator.FParameter +import org.utbot.greyboxfuzzer.generator.ThisInstance +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.Instruction + +data class Seed( + val thisInstance: ThisInstance, + val parameters: List, + val instructionCoverage: Set, + var score: Double = 0.0 +) { + + fun createEnvironmentModels(): EnvironmentModels { + return EnvironmentModels(thisInstance.utModelForExecution, parameters.map { it.utModel }, mapOf()) + } + + fun copy(): Seed { + return Seed(thisInstance.copy(), parameters.map { it.copy() }, instructionCoverage.toSet(), score) + } + + fun replaceFParameter(index: Int, newFParameter: FParameter): Seed { + return Seed( + thisInstance.copy(), + parameters.mapIndexed { ind, fParameter -> if (ind == index) newFParameter else fParameter.copy() }, + instructionCoverage.toSet(), + score + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/SeedCollector.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/SeedCollector.kt new file mode 100644 index 0000000000..aab9696a19 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/mutator/SeedCollector.kt @@ -0,0 +1,83 @@ +package org.utbot.greyboxfuzzer.mutator + +import org.utbot.framework.plugin.api.Instruction +import kotlin.random.Random + +class SeedCollector(private val maxSize: Int = 100) { + private val seeds = ArrayList(maxSize) + + fun calcSeedScore(coverage: Set): Double = + coverage.sumOf { instruction -> + val numOfSeedCoveredInstructions = seeds.count { it.instructionCoverage.contains(instruction) } + if (numOfSeedCoveredInstructions == 0) { + Double.MAX_VALUE + } else { + 1.0 / numOfSeedCoveredInstructions + } + } + + private fun recalculateSeedScores() { + seeds.forEach { seed -> + seed.score = calcSeedScore(seed.instructionCoverage) + } + } + + fun isSeedOpensNewCoverage(seed: Seed): Boolean { + val oldCoverage = seeds.flatMap { it.instructionCoverage }.toSet() + return seed.instructionCoverage.any { !oldCoverage.contains(it) } + } + + fun addSeed(seed: Seed) { + if (seeds.isEmpty()) { + seeds.add(seed) + return + } + val indexToInsert = seeds.indexOfFirst { it.score <= seed.score } + if (indexToInsert == -1) { + if (seeds.size < maxSize - 1) { + seeds.add(seed) + recalculateSeedScores() + seeds.sortByDescending { it.score } + } + return + } + seeds.add(indexToInsert, seed) + recalculateSeedScores() + seeds.sortByDescending { it.score } + while (seeds.size >= maxSize) { + seeds.removeLast() + } + } + + fun getRandomWeightedSeed(): Seed { + val topN = when (Random.nextInt(0, 100)) { + in 0..30 -> 1 + in 30..60 -> 3 + in 60..80 -> 5 + in 80..90 -> 10 + else -> maxSize + } + return seeds.take(topN).random() + } + + fun all(f: (Seed) -> Boolean) = seeds.all(f) + +// fun getRandomWeightedSeed(): Seed { +// val scoreSum = seeds.sumOf { it.score } +// val randomScore = Random.nextDouble(0.0, scoreSum) +// var scoreCounter = 0.0 +// seeds.forEach { seed -> +// scoreCounter += seed.score +// if (scoreCounter >= randomScore) { +// return seed +// } +// } +// return seeds.first() +// } + + fun getBestSeed() = seeds.first() + fun removeSeed(seed: Seed) = seeds.remove(seed) + fun seedsSize() = seeds.size + + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/NonTrackingGenerationStatus.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/NonTrackingGenerationStatus.kt new file mode 100644 index 0000000000..5030482b00 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/NonTrackingGenerationStatus.kt @@ -0,0 +1,30 @@ +package org.utbot.greyboxfuzzer.quickcheck + +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.internal.GeometricDistribution +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Provides a generation status that does not track the number of trials + * generated so far. This is useful for guided fuzzing where the burden + * of making choices is on the guidance system rather than on quickcheck. + * + * @author Rohan Padhye + */ +class NonTrackingGenerationStatus(private val random: SourceOfRandomness) : GenerationStatus { + private val geometric = GeometricDistribution() + override fun size(): Int { + return geometric.sampleWithMean(org.utbot.greyboxfuzzer.quickcheck.NonTrackingGenerationStatus.Companion.MEAN_SIZE.toDouble(), random) + } + + override fun attempts(): Int { + throw UnsupportedOperationException( + "attempts() and @ValueOf" + + " is not supported in guided mode." + ) + } + + companion object { + const val MEAN_SIZE = 10 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/ComponentizedGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/ComponentizedGenerator.kt new file mode 100644 index 0000000000..01b58eb05e --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/ComponentizedGenerator.kt @@ -0,0 +1,101 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +import org.javaruntype.type.TypeParameter +import org.utbot.greyboxfuzzer.util.FuzzerIllegalStateException +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.lang.reflect.AnnotatedType +import java.util.Collections + +/** + * Produces values for property parameters of types that have parameterizations + * that would also need generation, such as collections, maps, and predicates. + * + * @param type of property parameter to apply this generator's values to + * @param type class token for type of property parameter this generator + * is applicable to + */ +abstract class ComponentizedGenerator constructor(type: Class<*>) : Generator(type) { + private val components: MutableList = ArrayList() + + /** + * {@inheritDoc} + * + * + * Generators of this type do not get called upon to generate values + * for parameters of type [Object]. + */ + override fun canRegisterAsType(type: Class<*>): Boolean { + return Any::class.java != type + } + + override fun hasComponents(): Boolean { + return true + } + + override fun addComponentGenerators( + newComponents: List + ) { + require(newComponents.size == numberOfNeededComponents()) { + String.format( + "Needed %d components for %s, but got %d", + numberOfNeededComponents(), + javaClass, + newComponents.size + ) + } + components.clear() + components.addAll(newComponents) + } + + override fun canGenerateForParametersOfTypes( + typeParameters: List> + ): Boolean { + return numberOfNeededComponents() == typeParameters.size + } + + override fun provide(provided: org.utbot.greyboxfuzzer.quickcheck.generator.Generators) { + super.provide(provided) + for (each in components) { + each.provide(provided) + } + } + + abstract fun createModifiedUtModel(random: SourceOfRandomness, status: org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus): UtModel + protected open fun modify( + random: SourceOfRandomness, + status: org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus + ): UtModel { + val cachedModel = generatedUtModel ?: throw FuzzerIllegalStateException("Nothing to modify") + val randomNestedGenerator = nestedGeneratorsRecursiveWithoutThis().randomOrNull() ?: return cachedModel + getAllGeneratorsBetween(this, randomNestedGenerator)?.forEach { + it.generationState = org.utbot.greyboxfuzzer.quickcheck.generator.GenerationState.MODIFYING_CHAIN + } + randomNestedGenerator.generationState = + org.utbot.greyboxfuzzer.quickcheck.generator.GenerationState.REGENERATE + return createModifiedUtModel(random, status) + } + override fun configure(annotatedType: AnnotatedType?) { + super.configure(annotatedType) + val annotatedComponentTypes = Reflection.annotatedComponentTypes(annotatedType) + if (annotatedComponentTypes.size == components.size) { + for (i in components.indices) { + components[i].configure(annotatedComponentTypes[i]) + } + } + } + + /** + * @return this generator's component generators + */ + fun componentGenerators(): List { + return Collections.unmodifiableList(components) + } + + override fun copy(): Generator { + return (super.copy() as ComponentizedGenerator).also { + it.components.addAll(components.map { it.copy() }) + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/DecimalGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/DecimalGenerator.kt new file mode 100644 index 0000000000..f2b28a1f4b --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/DecimalGenerator.kt @@ -0,0 +1,13 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * Base class for generators of decimal types, such as `double` and + * [BigDecimal]. All numbers are converted to/from BigDecimal for + * processing. + * + * @param type of values this generator produces + */ +abstract class DecimalGenerator : Generator { + protected constructor(type: Class<*>) : super(type) + protected constructor(types: List>) : super(types) +} diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Distinct.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Distinct.kt new file mode 100644 index 0000000000..3200d7c25a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Distinct.kt @@ -0,0 +1,20 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * + * Mark a parameter of a + * method with this annotation to make values generated for the parameter + * distinct from each other. + * + * + * This annotation is recognized on array parameters and parameters of type + * [java.util.Collection] and [java.util.Map]. + * + * + * Using this annotation with [Size] on [java.util.Set] or + * [java.util.Map] leads to strict size constraint. + */ +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.TYPE) +@Retention(AnnotationRetention.RUNTIME) +@GeneratorConfiguration +annotation class Distinct diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Gen.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Gen.kt new file mode 100644 index 0000000000..6545212577 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Gen.kt @@ -0,0 +1,21 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Represents a strategy for generating random values. + * */ +fun interface Gen { + /** + * Generates a value, possibly influenced by a source of randomness and + * metadata about the generation. + * + * @param random source of randomness to be used when generating the value + * @param status an object that can be used to influence the generated + * value. For example, generating lists can use the [ ][GenerationStatus.size] method to generate lists with a given + * number of elements. + * @return the generated value + */ + fun generate(random: SourceOfRandomness, status: GenerationStatus): UtModel +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GenerationState.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GenerationState.kt new file mode 100644 index 0000000000..ed13bc3530 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GenerationState.kt @@ -0,0 +1,5 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +enum class GenerationState { + REGENERATE, CACHE, MODIFY, MODIFYING_CHAIN +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GenerationStatus.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GenerationStatus.kt new file mode 100644 index 0000000000..0b6e0d6c73 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GenerationStatus.kt @@ -0,0 +1,20 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * [Generator]s are fed instances of this interface on each generation + * so that, if they choose, they can use these instances to influence the + * results of a generation for a particular property parameter. + */ +interface GenerationStatus { + /** + * @return an arbitrary "size" parameter; this value (probabilistically) + * increases for every successful generation + */ + fun size(): Int + + /** + * @return how many attempts have been made to generate a value for a + * property parameter + */ + fun attempts(): Int +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Generator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Generator.kt new file mode 100644 index 0000000000..95752eb109 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Generator.kt @@ -0,0 +1,417 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +import org.javaruntype.type.TypeParameter +import org.javaruntype.type.Types +import org.javaruntype.type.WildcardTypeParameter +import org.utbot.greyboxfuzzer.generator.GeneratorConfigurator +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.* +import org.utbot.greyboxfuzzer.util.FuzzerIllegalStateException +import org.utbot.greyboxfuzzer.util.removeIfAndReturnRemovedElements +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.internal.ReflectionException +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import org.utbot.greyboxfuzzer.util.getTrue +import org.utbot.framework.plugin.api.util.* +import java.lang.reflect.AnnotatedElement +import java.lang.reflect.AnnotatedType +import java.lang.reflect.Method +import java.util.Collections +import kotlin.random.Random +import java.lang.annotation.Annotation as JavaAnnotation + +/** + * Produces values for property parameters. + * + */ +abstract class Generator protected constructor(types: List>) : Gen { + private val types: MutableList> = ArrayList() + private var repo: Generators? = null + var generatedUtModel: UtModel? = null + var generationState = GenerationState.REGENERATE + var nestedGenerators = mutableListOf() + lateinit var generatorContext: GeneratorContext + + open fun generateImpl(random: SourceOfRandomness, status: GenerationStatus): UtModel { + if (generatorContext.checkPoint()) { + return UtNullModel(objectClassId) + } + return when (generationState) { + GenerationState.REGENERATE -> { + val possibleConstant = + if (Random.getTrue(25)) { + getConstant() + } else null + (possibleConstant ?: generate(random, status)).also { + generatedUtModel = it + nestedGeneratorsRecursive().forEach { + it.generationState = GenerationState.CACHE + } + } + } + + GenerationState.CACHE -> { + generatedUtModel ?: throw FuzzerIllegalStateException("No cached model") + } + + GenerationState.MODIFY -> { + withModification { + generate(random, status).also { generatedUtModel = it } + } + } + + GenerationState.MODIFYING_CHAIN -> { + generate(random, status).also { + generatedUtModel = it + nestedGeneratorsRecursive().forEach { + it.generationState = GenerationState.CACHE + } + } + } + } + } + + private fun getConstant(): UtModel? = + if (isGeneratorContextInitialized()) { + when (this) { + is IntegerGenerator -> generatorContext.constants[intWrapperClassId]?.randomOrNull() + is PrimitiveIntGenerator -> generatorContext.constants[intClassId]?.randomOrNull() + is ByteGenerator -> generatorContext.constants[byteWrapperClassId]?.randomOrNull() + is PrimitiveByteGenerator -> generatorContext.constants[byteClassId]?.randomOrNull() + is CharacterGenerator -> generatorContext.constants[charWrapperClassId]?.randomOrNull() + is PrimitiveCharGenerator -> generatorContext.constants[charClassId]?.randomOrNull() + is DoubleGenerator -> generatorContext.constants[doubleWrapperClassId]?.randomOrNull() + is PrimitiveDoubleGenerator -> generatorContext.constants[doubleClassId]?.randomOrNull() + is LongGenerator -> generatorContext.constants[longWrapperClassId]?.randomOrNull() + is PrimitiveLongGenerator -> generatorContext.constants[longClassId]?.randomOrNull() + is FloatGenerator -> generatorContext.constants[floatWrapperClassId]?.randomOrNull() + is PrimitiveFloatGenerator -> generatorContext.constants[floatClassId]?.randomOrNull() + is ShortGenerator -> generatorContext.constants[shortWrapperClassId]?.randomOrNull() + is PrimitiveShortGenerator -> generatorContext.constants[shortClassId]?.randomOrNull() + is StringGenerator -> generatorContext.constants[stringClassId]?.randomOrNull() + else -> null + } + } else null + + private fun flattenedTo(destination: MutableList) { + destination.add(this) + nestedGenerators.forEach { it.flattenedTo(destination) } + } + + fun nestedGeneratorsRecursive(): List { + val allGenerators = mutableListOf() + this.flattenedTo(allGenerators) + return allGenerators + } + + fun nestedGeneratorsRecursiveWithoutThis() = nestedGeneratorsRecursive().filter { it != this } + + private fun genRandomNestedGenerator(): Generator? { + val queue = ArrayDeque() + val res = mutableListOf() + queue.add(this) + while (queue.isNotEmpty()) { + val el = queue.removeFirst() + res.add(el) + } + return res.randomOrNull() + } + + protected fun getAllGeneratorsBetween(start: Generator, end: Generator): List? { + val res = mutableListOf(mutableListOf(start)) + val queue = ArrayDeque() + queue.add(start) + while (queue.isNotEmpty()) { + val curGenerator = queue.removeFirst() + if (curGenerator == end) break + val nestedGenerators = curGenerator.nestedGenerators + if (nestedGenerators.isEmpty()) continue + val oldLists = res.removeIfAndReturnRemovedElements { it.last() == curGenerator } + for (implementer in nestedGenerators) { + queue.add(implementer) + oldLists.forEach { res.add((it + listOf(implementer)).toMutableList()) } + } + } + return res.find { it.last() == end }?.drop(1)?.dropLast(1) + } + + private fun getAllGeneratorsBetween(currentPath: MutableList, end: Generator) { + + } + + /** + * @param type class token for type of property parameter this generator is + * applicable to + */ + protected constructor(type: Class<*>) : this(listOf(type)) + + /** + * Used for generators of primitives and their wrappers. For example, a + * `Generator` can be used for property parameters of type + * `Integer` or `int`. + * + * @param types class tokens for type of property parameter this generator + * is applicable to + */ + init { + this.types.addAll(types) + } + + /** + * @return class tokens for the types of property parameters this generator + * is applicable to + */ + fun types(): List> { + return Collections.unmodifiableList(types) + } + + /** + * Tells whether this generator is allowed to be used for property + * parameters of the given type. + * + * @param type type against which to test this generator + * @return `true` if the generator is allowed to participate in + * generating values for property parameters of `type` + */ + open fun canRegisterAsType(type: Class<*>): Boolean { + return true + } + + /** + * + * This is intended for use only by junit-quickcheck itself, and not by + * creators of custom generators. + * + * @return whether this generator has component generators, such as for + * those generators that produce lists or maps + * @see .addComponentGenerators + */ + open fun hasComponents(): Boolean { + return false + } + + /** + * + * This is intended for use only by junit-quickcheck itself, and not by + * creators of custom generators. + * + * @return how many component generators this generator needs + * @see .addComponentGenerators + */ + open fun numberOfNeededComponents(): Int { + return 0 + } + + /** + * + * Adds component generators to this generator. + * + * + * Some generators need component generators to create proper values. + * For example, list generators require a single component generator in + * order to generate elements that have the type of the list parameter's + * type argument. + * + * + * This is intended for use only by junit-quickcheck itself, and not by + * creators of custom generators. + * + * @param newComponents component generators to add + */ + open fun addComponentGenerators(newComponents: List) { + // do nothing by default + } + + /** + * @param typeParameters a list of generic type parameters + * @return whether this generator can be considered for generating values + * for property parameters that have the given type parameters in their + * signatures + */ + open fun canGenerateForParametersOfTypes(typeParameters: List>): Boolean { + return true + } + + /** + * + * Configures this generator using annotations from a given annotated + * type. + * + * + * This method considers only annotations that are themselves marked + * with [GeneratorConfiguration]. + * + * + * By default, the generator will configure itself using this + * procedure: + * + * * For each of the given annotations: + * + * * Find a `public` method on the generator named + * `configure`, that accepts a single parameter of the + * annotation type + * * Invoke the `configure` method reflectively, passing the + * annotation as the argument + * + * + * + * + * @param annotatedType a type usage + * @throws GeneratorConfigurationException if the generator does not + * "understand" one of the generation configuration annotations on + * the annotated type + */ + open fun configure(annotatedType: AnnotatedType?) { + configureStrict(collectConfigurationJavaAnnotations(annotatedType)) + } + + /** + * @param element an annotated program element + */ + open fun configure(element: AnnotatedElement?) { + configureLenient(collectConfigurationJavaAnnotations(element)) + } + + /** + * + * Supplies the available generators to this one. + * + * + * This is intended for use only by junit-quickcheck itself, and not by + * creators of custom generators. + * + * @param provided repository of available generators + */ + open fun provide(provided: Generators) { + repo = provided + } + + fun isGeneratorContextInitialized() = this::generatorContext.isInitialized + + /** + * Used by the framework to make a copy of the receiver. + * + * @return a copy of the receiver + */ + open fun copy(): Generator { + return Reflection.instantiate(javaClass).also { + it.generatedUtModel = generatedUtModel + it.generationState = generationState + it.nestedGenerators = nestedGenerators.map { it.copy() }.toMutableList() + if (isGeneratorContextInitialized()) { + it.generatorContext = generatorContext + } + GeneratorConfigurator.configureGenerator(it, 95) + } + } + + /** + * @return an access point for the available generators + */ + protected fun gen(): Generators? { + return repo + } + + /** + * @param random a source of randomness used when locating other + * generators + * @return an access point for the available generators + */ + protected fun gen(random: SourceOfRandomness?): Generators { + return repo!!.withRandom(random!!) + } + + private fun collectConfigurationJavaAnnotations( + element: AnnotatedElement? + ): Map, JavaAnnotation> { + if (element == null) return emptyMap() + val configs = configurationAnnotationsOn(element) + return configs.associateBy { it.annotationType() as Class } + } + + private fun configureStrict(byType: Map, JavaAnnotation>) { + for ((key, value) in byType) configureStrict(key, value) + } + + private fun configureStrict( + annotationType: Class, + configuration: JavaAnnotation + ) { + configure( + annotationType, + configuration + ) { ex: ReflectionException? -> + throw GeneratorConfigurationException( + String.format( + "Generator %s does not understand configuration annotation %s", + javaClass.name, + annotationType.name + ), + ex + ) + } + } + + private fun configureLenient(byType: Map, JavaAnnotation>) { + for ((key, value) in byType) configureLenient(key, value) + } + + private fun configureLenient( + annotationType: Class, + configuration: JavaAnnotation + ) { + configure(annotationType, configuration) { ex: ReflectionException? -> } + } + + private fun configure( + annotationType: Class, + configuration: JavaAnnotation, + exceptionHandler: (ReflectionException) -> Unit + ) { + var configurer: Method? = null + try { + configurer = Reflection.findMethod(javaClass, "configure", annotationType) + } catch (ex: ReflectionException) { + exceptionHandler(ex) + } + if (configurer != null) Reflection.invoke(configurer, this, configuration) + } + + protected fun withModification(block: () -> T): T { + generationState = GenerationState.MODIFY + return block.invoke().also { generationState = GenerationState.CACHE } + } + + companion object { + /** + * @param parameter a generic type parameter + * @param clazz a type + * @return whether the type is compatible with the generic type parameter + * @see .canGenerateForParametersOfTypes + */ + protected fun compatibleWithTypeParameter( + parameter: TypeParameter<*>, + clazz: Class<*>? + ): Boolean { + return (parameter is WildcardTypeParameter + || parameter.type.isAssignableFrom(Types.forJavaLangReflectType(clazz))) + } + + /** + * Gives a list of the [GeneratorConfiguration] annotations present + * on the given program element. + * + * @param element an annotated program element + * @return what configuration annotations are present on that element + */ + @JvmStatic + protected fun configurationAnnotationsOn(element: AnnotatedElement): List = + Reflection.allAnnotations(element) + .filter { + it.annotationType().isAnnotationPresent( + GeneratorConfiguration::class.java + ) + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GeneratorConfiguration.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GeneratorConfiguration.kt new file mode 100644 index 0000000000..f34a8adde0 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GeneratorConfiguration.kt @@ -0,0 +1,20 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * + * Apply this annotation to an annotation that marks property parameters, + * in order that the marked annotation can be used to configure + * [generators][Generator] for values of the parameter's type. + * + * + * If a generator has a public instance method named `configure`, + * with a single parameter whose type is an annotation that has this annotation + * applied, then when a property that has a parameter marked with method that + * annotation is verified, the generator that generates the value for that + * parameter will have its `configure` method called with the annotation + * as the argument. + */ +@Target(AnnotationTarget.ANNOTATION_CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +annotation class GeneratorConfiguration diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GeneratorConfigurationException.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GeneratorConfigurationException.kt new file mode 100644 index 0000000000..78511ddda3 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GeneratorConfigurationException.kt @@ -0,0 +1,16 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * Raised if a problem arises when attempting to configure a generator with + * annotations from a property parameter. + * + * @see Generator.configure + */ +class GeneratorConfigurationException : RuntimeException { + constructor(message: String) : super(message) + constructor(message: String, cause: Throwable?) : super(message, cause) + + companion object { + private const val serialVersionUID = 1L + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GeneratorContext.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GeneratorContext.kt new file mode 100644 index 0000000000..dac79dcfc7 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/GeneratorContext.kt @@ -0,0 +1,23 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.util.FuzzerUtModelConstructor + +data class GeneratorContext( + val utModelConstructor: FuzzerUtModelConstructor, + val constants: Map>, + val timeoutInMillis: Long = 5000L, +) { + var timeOfGenerationStart = 0L + var timeToFinishGeneration = 0L + + fun startCheckpoint() { + timeOfGenerationStart = System.currentTimeMillis() + timeToFinishGeneration = timeOfGenerationStart + timeoutInMillis + } + + fun checkPoint(): Boolean { + return System.currentTimeMillis() > timeToFinishGeneration + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Generators.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Generators.kt new file mode 100644 index 0000000000..40598f6850 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Generators.kt @@ -0,0 +1,67 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.lang.reflect.Field +import java.lang.reflect.Parameter + +/** + * An access point for available generators. + */ +interface Generators { + /** + * + * Gives a generator that can produce values of the given type, + * parameterized by the given "component" types. + * + * @param type of objects produced by the resulting generator + * @param type a type + * @param componentTypes types for the "components" of the type, if any + * @return generator that can produce values of that type + * @see ComponentizedGenerator + */ + fun type(type: Class, vararg componentTypes: Class<*>): Generator + + /** + * + * Gives a generator that can produce instances of the type of the + * given reflected method parameter. + * + * + * If the parameter is marked with an annotation that influences the + * generation of its value, that annotation will be applied to the + * generation of values for that parameter's type. + * + * @param parameter a reflected method parameter + * @return generator that can produce values of the parameter's type + */ + fun parameter(parameter: Parameter): Generator + + /** + * + * Gives a generator that can produce instances of the type of the + * given reflected field. + * + * + * If the field is marked with an annotation that influences the + * generation of its value, that annotation will be applied to the + * generation of values for that field's type. + * + * @param field a reflected field + * @return generator that can produce values of the field's type + */ + fun field(field: Field): Generator + + /** + * + * Makes a generator access point just like the receiver, but which + * uses the given source of randomness for making random choices. + * + * + * Intended for use by junit-quickcheck. + * + * @param random a source of randomness + * @return a generator access point that has the source of randomness + * available to it + */ + fun withRandom(random: SourceOfRandomness): Generators +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/InRange.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/InRange.kt new file mode 100644 index 0000000000..02402b0b06 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/InRange.kt @@ -0,0 +1,91 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * + * Mark a parameter of a + * method with this annotation to constrain the values generated for the + * parameter to a given range. + * + * + * Different generators may use different min/max attribute pairs. + * Generators that produce primitive values or values of their wrapper types + * will likely want to use the attribute pairs of corresponding type. + * Otherwise, a generator can use [.min] and [.max], and + * take on the responsibility of converting their string values to values of + * the desired type. + */ +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.TYPE) +@Retention(AnnotationRetention.RUNTIME) +@GeneratorConfiguration +annotation class InRange( + /** + * @return a minimum `byte` value + */ + val minByte: Byte = Byte.MIN_VALUE, + /** + * @return a maximum `byte` value + */ + val maxByte: Byte = Byte.MAX_VALUE, + /** + * @return a minimum `short` value + */ + val minShort: Short = Short.MIN_VALUE, + /** + * @return a maximum `short` value + */ + val maxShort: Short = Short.MAX_VALUE, + /** + * @return a minimum `char` value + */ + val minChar: Char = Character.MIN_VALUE, + /** + * @return a maximum `char` value + */ + val maxChar: Char = Character.MAX_VALUE, + /** + * @return a minimum `int` value + */ + val minInt: Int = Int.MIN_VALUE, + /** + * @return a maximum `int` value + */ + val maxInt: Int = Int.MAX_VALUE, + /** + * @return a minimum `long` value + */ + val minLong: Long = Long.MIN_VALUE, + /** + * @return a maximum `long` value + */ + val maxLong: Long = Long.MAX_VALUE, + /** + * @return a minimum `float` value + */ + val minFloat: Float = 0f, + /** + * @return a maximum `float` value + */ + val maxFloat: Float = 1f, + /** + * @return a minimum `double` value + */ + val minDouble: Double = 0.0, + /** + * @return a maximum `double` value + */ + val maxDouble: Double = 1.0, + /** + * @return a minimum value, represented in string form + */ + val min: String = "", + /** + * @return a maximum value, represented in string form + */ + val max: String = "", + /** + * @return a formatting hint, such as a + * [date format string][java.text.SimpleDateFormat], that + * generators can use when converting values from strings + */ + val format: String = "" +) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/IntegralGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/IntegralGenerator.kt new file mode 100644 index 0000000000..04a1aaf042 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/IntegralGenerator.kt @@ -0,0 +1,14 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * Base class for generators of integral types, such as `int` and + * [BigInteger]. All numbers are converted to/from BigInteger for + * processing. + * + * @param type of values this generator produces + */ +abstract class IntegralGenerator : Generator { + protected constructor(type: Class<*>) : super(type) + protected constructor(types: List>) : super(types) + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Lambdas.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Lambdas.kt new file mode 100644 index 0000000000..f537fbbd65 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Lambdas.kt @@ -0,0 +1,115 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +import org.utbot.greyboxfuzzer.quickcheck.internal.DefaultMethodHandleMaker +import org.utbot.greyboxfuzzer.quickcheck.internal.GeometricDistribution +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.internal.generator.SimpleGenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method +import java.lang.reflect.Proxy +import java.util.Random + +/** + * Helper class for creating instances of "functional interfaces". + */ +class Lambdas private constructor() { + init { + throw UnsupportedOperationException() + } + + private class LambdaInvocationHandler internal constructor( + private val lambdaType: Class, + private val returnValueGenerator: Generator, + private val attempts: Int + ) : InvocationHandler { + private val methodHandleMaker = DefaultMethodHandleMaker() + + override fun invoke( + proxy: Any, + method: Method, + args: Array + ): Any { + if (Any::class.java == method.declaringClass) return handleObjectMethod(proxy, method, args) + if (method.isDefault) return handleDefaultMethod(proxy, method, args) + val source = SourceOfRandomness(Random()) + source.setSeed(args.contentHashCode().toLong()) + val status: GenerationStatus = SimpleGenerationStatus( + GeometricDistribution(), + source, + attempts + ) + return returnValueGenerator.generateImpl(source, status) + } + + private fun handleObjectMethod( + proxy: Any, + method: Method, + args: Array + ): Any { + if ("equals" == method.name) return proxy === args[0] + return if ("hashCode" == method.name) System.identityHashCode(proxy) else handleToString() + } + + private fun handleToString(): String { + return "a randomly generated instance of $lambdaType" + } + + private fun handleDefaultMethod( + proxy: Any, + method: Method, + args: Array + ): Any { + val handle = methodHandleMaker.handleForSpecialMethod(method) + return handle.bindTo(proxy).invokeWithArguments(*args) + } + } + + companion object { + /** + * + * Creates an instance of a given "functional interface" type, whose + * single abstract method returns values of the type produced by the given + * generator. The arguments to the lambda's single method will be used to + * seed a random generator that will be used to generate the return value + * of that method. + * + * + * junit-quickcheck uses this to create random values for property + * parameters whose type is determined to be a + * [functional interface][FunctionalInterface]. Custom generators + * for functional interface types can use this also. + * + * @param lambdaType a functional interface type token + * @param returnValueGenerator a generator for the return type of the + * functional interface's single method + * @param status an object to be passed along to the generator that will + * produce the functional interface's method return value + * @param the functional interface type token + * @param the type of the generated return value of the functional + * interface method + * @return an instance of the functional interface type, whose single + * method will return a generated value + * @throws IllegalArgumentException if `lambdaType` is not a + * functional interface type + */ + @JvmStatic + fun makeLambda( + lambdaType: Class, + returnValueGenerator: Generator, + status: GenerationStatus + ): T { + requireNotNull(Reflection.singleAbstractMethodOf(lambdaType)) { "$lambdaType is not a functional interface type" } + return lambdaType.cast( + Proxy.newProxyInstance( + lambdaType.classLoader, arrayOf>(lambdaType), + LambdaInvocationHandler( + lambdaType, + returnValueGenerator, + status.attempts() + ) + ) + ) + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/NullAllowed.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/NullAllowed.kt new file mode 100644 index 0000000000..53bf5b8f9a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/NullAllowed.kt @@ -0,0 +1,16 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * + * Mark a parameter of a + * method with this annotation to indicate that the parameter is nullable, and + * to optionally configure the probability of generating a null value. + */ +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.TYPE) +@Retention(AnnotationRetention.RUNTIME) +annotation class NullAllowed( + /** + * @return probability of generating `null`, in the range [0,1] + */ + val probability: Double = 0.2 +) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Precision.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Precision.kt new file mode 100644 index 0000000000..34cfbd4ec0 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Precision.kt @@ -0,0 +1,18 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * + * Mark a parameter of a + * method with this annotation to constrain the values generated for the + * parameter to a given precision. + */ +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.TYPE) +@Retention(AnnotationRetention.RUNTIME) +@GeneratorConfiguration +annotation class Precision( + /** + * @return desired [scale][java.math.BigDecimal.scale] of the + * generated values + */ + val scale: Int +) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Size.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Size.kt new file mode 100644 index 0000000000..d9d0f84f32 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/Size.kt @@ -0,0 +1,16 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +/** + * + * Mark a parameter of a + * method with this annotation to constrain the size of values generated for + * the parameter. + * + * + * This annotation is recognized on array parameters and parameters of type + * [Collection][java.util.Collection.size] and [ ][java.util.Map.size]. + */ +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.TYPE) +@Retention(AnnotationRetention.RUNTIME) +@GeneratorConfiguration +annotation class Size(val min: Int = 0, val max: Int) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/VoidGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/VoidGenerator.kt new file mode 100644 index 0000000000..e49a3722e4 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/VoidGenerator.kt @@ -0,0 +1,23 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator + +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values for property parameters of type `void` or + * [Void]. + */ +class VoidGenerator : Generator(listOf(Void::class.java, Void.TYPE)) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return UtNullModel(classIdForType(Void::class.java)) + } + + override fun canRegisterAsType(type: Class<*>): Boolean { + return Any::class.java != type + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/AbstractStringGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/AbstractStringGenerator.kt new file mode 100644 index 0000000000..85f6e5663f --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/AbstractStringGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Size +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * + * Base class for generators of values of type [String]. + * + * + * The generated values will have [String.length] decided by + * [GenerationStatus.size]. + */ +abstract class AbstractStringGenerator : Generator(String::class.java) { + protected open var lengthRange: IntRange? = null + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(generateValue(random, status), stringClassId) + } + + fun generateValue( + random: SourceOfRandomness, + status: GenerationStatus + ): String { + val codePoints = IntArray(lengthRange?.randomOrNull() ?: status.size()) + for (i in codePoints.indices) codePoints[i] = nextCodePoint(random) + return String(codePoints, 0, codePoints.size) + } + + protected abstract fun nextCodePoint(random: SourceOfRandomness): Int +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/BooleanGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/BooleanGenerator.kt new file mode 100644 index 0000000000..81527d1a4c --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/BooleanGenerator.kt @@ -0,0 +1,24 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.booleanWrapperClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `boolean` or [Boolean]. + */ +class BooleanGenerator : Generator( + listOf( + Boolean::class.javaObjectType + ) +) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextBoolean(), booleanWrapperClassId) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/ByteGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/ByteGenerator.kt new file mode 100644 index 0000000000..cf1cc84631 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/ByteGenerator.kt @@ -0,0 +1,38 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.byteWrapperClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.IntegralGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `byte` or [Byte]. + */ +class ByteGenerator : IntegralGenerator(listOf(Byte::class.javaObjectType)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minByte") as Byte + private var max = Reflection.defaultValueOf(InRange::class.java, "maxByte") as Byte + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minByte] and [InRange.maxByte], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minByte else range.min.toByte() + max = if (range.max.isEmpty()) range.maxByte else range.max.toByte() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextByte(min, max), byteWrapperClassId) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/CharacterGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/CharacterGenerator.kt new file mode 100644 index 0000000000..5889a2c5ba --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/CharacterGenerator.kt @@ -0,0 +1,43 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.charWrapperClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `char` or [Character]. + */ +class CharacterGenerator : Generator( + listOf( + Char::class.javaObjectType + ) +) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minChar") as Char + private var max = Reflection.defaultValueOf(InRange::class.java, "maxChar") as Char + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minChar] and [InRange.maxChar], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minChar else range.min[0] + max = if (range.max.isEmpty()) range.maxChar else range.max[0] + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextChar(min, max), charWrapperClassId) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/DoubleGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/DoubleGenerator.kt new file mode 100644 index 0000000000..4c068ae990 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/DoubleGenerator.kt @@ -0,0 +1,48 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.doubleWrapperClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.DecimalGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values for property parameters of type `double` or + * [Double]. + */ +class DoubleGenerator : DecimalGenerator(listOf(Double::class.javaObjectType)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minDouble") as Double + private var max = Reflection.defaultValueOf(InRange::class.java, "maxDouble") as Double + + /** + * Tells this generator to produce values within a specified minimum + * (inclusive) and/or maximum (exclusive) with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minDouble] and [InRange.maxDouble], + * if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minDouble else range.min.toDouble() + max = if (range.max.isEmpty()) range.maxDouble else range.max.toDouble() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(generateValue(random, status), doubleWrapperClassId) + } + + fun generateValue( + random: SourceOfRandomness, + status: GenerationStatus? + ): Double { + return random.nextDouble(min, max) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/Encoded.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/Encoded.kt new file mode 100644 index 0000000000..49e75641ee --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/Encoded.kt @@ -0,0 +1,47 @@ +//package org.utbot.quickcheck.generator.java.lang +// +//import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorConfiguration +//import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.strings.CodePoints +//import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.strings.CodePoints.Companion.forCharset +//import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +//import java.nio.charset.Charset +// +///** +// * +// * Produces [String]s whose code points correspond to code points in +// * a given [Charset] +// * ([by default][Charset.defaultCharset]). +// */ +//class Encoded : AbstractStringGenerator() { +// private var charsetPoints: CodePoints? = null +// +// init { +// initialize(Charset.defaultCharset()) +// } +// +// /** +// * Tells this generator to emit strings in the given charset. +// * +// * @param charset a charset to use as the source for characters of +// * generated strings +// */ +// fun configure(charset: InCharset) { +// initialize(Charset.forName(charset.value)) +// } +// +// private fun initialize(charset: Charset) { +// charsetPoints = forCharset(charset) +// } +// +// override fun nextCodePoint(random: SourceOfRandomness): Int { +// return charsetPoints!!.at(random.nextInt(0, charsetPoints!!.size() - 1)) +// } +// +// /** +// * Names a [Charset]. +// */ +// @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.TYPE) +// @Retention(AnnotationRetention.RUNTIME) +// @GeneratorConfiguration +// annotation class InCharset(val value: String) +//} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/FloatGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/FloatGenerator.kt new file mode 100644 index 0000000000..0c2b53452f --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/FloatGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.floatWrapperClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.DecimalGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `float` or [Float]. + */ +class FloatGenerator : DecimalGenerator(listOf(Float::class.javaObjectType)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minFloat") as Float + private var max = Reflection.defaultValueOf(InRange::class.java, "maxFloat") as Float + + /** + * Tells this generator to produce values within a specified minimum + * (inclusive) and/or maximum (exclusive) with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minFloat] and [InRange.maxFloat], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minFloat else range.min.toFloat() + max = if (range.max.isEmpty()) range.maxFloat else range.max.toFloat() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextFloat(min, max), floatWrapperClassId) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/IntegerGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/IntegerGenerator.kt new file mode 100644 index 0000000000..0656adad90 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/IntegerGenerator.kt @@ -0,0 +1,45 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.intWrapperClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.IntegralGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `int` or [Integer]. + */ +class IntegerGenerator : IntegralGenerator(listOf(Int::class.javaObjectType)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minInt") as Int + private var max = Reflection.defaultValueOf(InRange::class.java, "maxInt") as Int + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minInt] and [InRange.maxInt], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minInt else range.min.toInt() + max = if (range.max.isEmpty()) range.maxInt else range.max.toInt() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(generateValue(random), intWrapperClassId) + } + + fun generateValue( + random: SourceOfRandomness + ): Int { + return random.nextInt(min, max) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/LongGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/LongGenerator.kt new file mode 100644 index 0000000000..9bd7ed8d84 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/LongGenerator.kt @@ -0,0 +1,46 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.longWrapperClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.IntegralGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `long` or [Long]. + */ +class LongGenerator : IntegralGenerator(listOf(Long::class.javaObjectType)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minLong") as Long + private var max = Reflection.defaultValueOf(InRange::class.java, "maxLong") as Long + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minLong] and [InRange.maxLong], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minLong else range.min.toLong() + max = if (range.max.isEmpty()) range.maxLong else range.max.toLong() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(generateValue(random, status), longWrapperClassId) + } + + fun generateValue( + random: SourceOfRandomness, + status: GenerationStatus? + ): Long { + return random.nextLong(min, max) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveBooleanGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveBooleanGenerator.kt new file mode 100644 index 0000000000..9b97d38e58 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveBooleanGenerator.kt @@ -0,0 +1,20 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `boolean` or [Boolean]. + */ +class PrimitiveBooleanGenerator : Generator(listOf(Boolean::class.javaPrimitiveType!!)) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextBoolean(), booleanClassId) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveByteGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveByteGenerator.kt new file mode 100644 index 0000000000..aaf749bc4a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveByteGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.IntegralGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `byte` or [Byte]. + */ +class PrimitiveByteGenerator : IntegralGenerator(listOf(Byte::class.javaPrimitiveType!!)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minByte") as Byte + private var max = Reflection.defaultValueOf(InRange::class.java, "maxByte") as Byte + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minByte] and [InRange.maxByte], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minByte else range.min.toByte() + max = if (range.max.isEmpty()) range.maxByte else range.max.toByte() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextByte(min, max), byteClassId) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveCharGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveCharGenerator.kt new file mode 100644 index 0000000000..7dc2202725 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveCharGenerator.kt @@ -0,0 +1,40 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `char` or [Character]. + */ +class PrimitiveCharGenerator : Generator(listOf(Char::class.javaPrimitiveType!!)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minChar") as Char + private var max = Reflection.defaultValueOf(InRange::class.java, "maxChar") as Char + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minChar] and [InRange.maxChar], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minChar else range.min[0] + max = if (range.max.isEmpty()) range.maxChar else range.max[0] + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextChar(min, max), charClassId) + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveDoubleGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveDoubleGenerator.kt new file mode 100644 index 0000000000..b50f44d3cb --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveDoubleGenerator.kt @@ -0,0 +1,48 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.DecimalGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values for property parameters of type `double` or + * [Double]. + */ +class PrimitiveDoubleGenerator : DecimalGenerator(listOf(Double::class.javaPrimitiveType!!)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minDouble") as Double + private var max = Reflection.defaultValueOf(InRange::class.java, "maxDouble") as Double + + /** + * Tells this generator to produce values within a specified minimum + * (inclusive) and/or maximum (exclusive) with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minDouble] and [InRange.maxDouble], + * if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minDouble else range.min.toDouble() + max = if (range.max.isEmpty()) range.maxDouble else range.max.toDouble() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(generateValue(random, status), doubleClassId) + } + + fun generateValue( + random: SourceOfRandomness, + status: GenerationStatus? + ): Double { + return random.nextDouble(min, max) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveFloatGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveFloatGenerator.kt new file mode 100644 index 0000000000..e55130c32c --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveFloatGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.DecimalGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `float` or [Float]. + */ +class PrimitiveFloatGenerator : DecimalGenerator(listOf(Float::class.javaPrimitiveType!!)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minFloat") as Float + private var max = Reflection.defaultValueOf(InRange::class.java, "maxFloat") as Float + + /** + * Tells this generator to produce values within a specified minimum + * (inclusive) and/or maximum (exclusive) with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minFloat] and [InRange.maxFloat], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minFloat else range.min.toFloat() + max = if (range.max.isEmpty()) range.maxFloat else range.max.toFloat() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextFloat(min, max), floatClassId) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveIntGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveIntGenerator.kt new file mode 100644 index 0000000000..d0e9600315 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveIntGenerator.kt @@ -0,0 +1,46 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.IntegralGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `int` or [Integer]. + */ +class PrimitiveIntGenerator : IntegralGenerator(listOf(Int::class.javaPrimitiveType!!)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minInt") as Int + private var max = Reflection.defaultValueOf(InRange::class.java, "maxInt") as Int + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minInt] and [InRange.maxInt], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minInt else range.min.toInt() + max = if (range.max.isEmpty()) range.maxInt else range.max.toInt() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(generateValue(random, status), intClassId) + } + + fun generateValue( + random: SourceOfRandomness, + status: GenerationStatus? + ): Int { + return random.nextInt(min, max) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveLongGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveLongGenerator.kt new file mode 100644 index 0000000000..88a7754e98 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveLongGenerator.kt @@ -0,0 +1,46 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.IntegralGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `long` or [Long]. + */ +class PrimitiveLongGenerator : IntegralGenerator(listOf(Long::class.javaPrimitiveType!!)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minLong") as Long + private var max = Reflection.defaultValueOf(InRange::class.java, "maxLong") as Long + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minLong] and [InRange.maxLong], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minLong else range.min.toLong() + max = if (range.max.isEmpty()) range.maxLong else range.max.toLong() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(generateValue(random, status), longClassId) + } + + fun generateValue( + random: SourceOfRandomness, + status: GenerationStatus? + ): Long { + return random.nextLong(min, max) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveShortGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveShortGenerator.kt new file mode 100644 index 0000000000..b22c9dbe92 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/PrimitiveShortGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.IntegralGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `short` or [Short]. + */ +class PrimitiveShortGenerator : IntegralGenerator(listOf(Short::class.javaPrimitiveType!!)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minShort") as Short + private var max = Reflection.defaultValueOf(InRange::class.java, "maxShort") as Short + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minShort] and [InRange.maxShort], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minShort else range.min.toShort() + max = if (range.max.isEmpty()) range.maxShort else range.max.toShort() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextShort(min, max), shortClassId) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/ShortGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/ShortGenerator.kt new file mode 100644 index 0000000000..78b3529761 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/ShortGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.shortWrapperClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.IntegralGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * Produces values of type `short` or [Short]. + */ +class ShortGenerator : IntegralGenerator(listOf(Short::class.javaObjectType)) { + private var min = Reflection.defaultValueOf(InRange::class.java, "minShort") as Short + private var max = Reflection.defaultValueOf(InRange::class.java, "maxShort") as Short + + /** + * Tells this generator to produce values within a specified minimum and/or + * maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minShort] and [InRange.maxShort], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + min = if (range.min.isEmpty()) range.minShort else range.min.toShort() + max = if (range.max.isEmpty()) range.maxShort else range.max.toShort() + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextShort(min, max), shortWrapperClassId) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/StringGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/StringGenerator.kt new file mode 100644 index 0000000000..d4e62457ce --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/StringGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang + +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import kotlin.random.Random + +/** + * + * Produces [String]s whose characters are in the interval + * `[0x0000, 0xD7FF]`. + */ +class StringGenerator : AbstractStringGenerator() { + + private var codePoints = setOf(0 until Character.MIN_SURROGATE.code) + override var lengthRange: IntRange? = null + + override fun nextCodePoint(random: SourceOfRandomness): Int { + return codePoints.random().random() + val codePoint = chooseRandomCodePoint() + return codePoint.random() + } + + private fun chooseRandomCodePoint(): IntRange { +// val size = codePoints.sumOf { it.last - it.first } +// val randomIntToSize = Random.nextInt(size) +// var curSum = 0 +// for (codePoint in codePoints) { +// val codePointSize = codePoint.last - codePoint.first +// curSum += codePointSize +// if (curSum >= randomIntToSize) { +// return codePoint +// } +// } + return codePoints.random() + } + fun configure(codePoints: Set, lengthRange: IntRange) { + this.codePoints = codePoints + this.lengthRange = lengthRange + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/strings/CodePoints.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/strings/CodePoints.kt new file mode 100644 index 0000000000..0761c36a1e --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/lang/strings/CodePoints.kt @@ -0,0 +1,142 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.strings + +import java.nio.charset.Charset +import java.nio.charset.CharsetEncoder + +/** + * Maps ordinal values to corresponding Unicode code points in a + * [Charset]. + */ +class CodePoints internal constructor() { + private val ranges: MutableList + + init { + ranges = ArrayList() + } + + /** + * @param index index to look up + * @return this code point set's `index`'th code point + * @throws IndexOutOfBoundsException if there is no such code point + */ + fun at(index: Int): Int { + if (index < 0) { + throw IndexOutOfBoundsException( + "illegal negative index: $index" + ) + } + var min = 0 + var max = ranges.size - 1 + while (min <= max) { + val midpoint = min + (max - min) / 2 + val current = ranges[midpoint] + if (index >= current.previousCount + && index < current.previousCount + current.size() + ) { + return current.low + index - current.previousCount + } else if (index < current.previousCount) { + max = midpoint - 1 + } else { + min = midpoint + 1 + } + } + throw IndexOutOfBoundsException(index.toString()) + } + + /** + * @return how many code points are in this code point set + */ + fun size(): Int { + if (ranges.isEmpty()) return 0 + val last = ranges[ranges.size - 1] + return last.previousCount + last.size() + } + + /** + * @param codePoint a code point + * @return whether this code point set contains the given code point + */ + operator fun contains(codePoint: Int): Boolean { + return ranges.stream().anyMatch { r: CodePointRange -> r.contains(codePoint) } + } + + fun add(range: CodePointRange) { + ranges.add(range) + } + + class CodePointRange(low: Int, high: Int, previousCount: Int) { + val low: Int + val high: Int + val previousCount: Int + + init { + require(low <= high) { String.format("%d > %d", low, high) } + this.low = low + this.high = high + this.previousCount = previousCount + } + + operator fun contains(codePoint: Int): Boolean { + return codePoint >= low && codePoint <= high + } + + fun size(): Int { + return high - low + 1 + } + } + + companion object { + private val ENCODABLES: MutableMap = HashMap() + + /** + * Gives a set of the code points in the given charset. + * + * @param c a charset + * @return the set of code points in the charset + */ + @JvmStatic + fun forCharset(c: Charset): CodePoints? { + if (ENCODABLES.containsKey(c)) return ENCODABLES[c] + val points = load(c) + ENCODABLES[c] = points + return points + } + + private fun load(c: Charset): CodePoints { + require(c.canEncode()) { "Charset " + c.name() + " does not support encoding" } + return encodableCodePoints(c.newEncoder()) + } + + private fun encodableCodePoints(encoder: CharsetEncoder): CodePoints { + val points = CodePoints() + var start = 0 + var inRange = false + var current = 0 + var previousCount = 0 + val buffer = IntArray(1) + while (current <= Character.MAX_CODE_POINT) { + encoder.reset() + buffer[0] = current + val s = String(buffer, 0, 1) + if (encoder.canEncode(s)) { + if (!inRange) { + inRange = true + start = current + } + } else if (inRange) { + inRange = false + val range = CodePointRange(start, current - 1, previousCount) + points.add(range) + previousCount += range.size() + } + ++current + } + if (inRange) { + points.add( + CodePointRange(start, current - 1, previousCount) + ) + } + return points + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/math/BigDecimalGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/math/BigDecimalGenerator.kt new file mode 100644 index 0000000000..056acd3d31 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/math/BigDecimalGenerator.kt @@ -0,0 +1,108 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.math + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.DecimalGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.Precision +import org.utbot.greyboxfuzzer.quickcheck.internal.Ranges +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.math.BigDecimal +import java.math.BigInteger + +/** + * + * Produces values of type [BigDecimal]. + * + * + * With no additional configuration, the generated values are chosen from + * a range with a magnitude decided by + * [GenerationStatus.size]. + */ +class BigDecimalGenerator : DecimalGenerator(BigDecimal::class.java) { + private var min: BigDecimal? = null + private var max: BigDecimal? = null + private var precision: Precision? = null + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] (inclusive) and/or + * [maximum][InRange.max] (exclusive), with uniform + * distribution. + * + * + * If an endpoint of the range is not specified, its value takes on + * a magnitude influenced by + * [GenerationStatus.size]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = BigDecimal(range.min) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = BigDecimal(range.max) + if (min != null && max != null) Ranges.checkRange(Ranges.Type.FLOAT, min, max) + } + + /** + * + * Tells this generator to produce values that have a specified + * [scale][Precision.scale]. + * + * + * With no precision constraint and no [ min/max constraint][.configure], the scale of the generated values is + * unspecified. + * + * + * Otherwise, the scale of the generated values is set as + * `max(0, precision.scale, range.min.scale, range.max.scale)`. + * + * @param configuration annotation that gives the desired scale of the + * generated values + */ + fun configure(configuration: Precision?) { + precision = configuration + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + var minToUse = min + var maxToUse = max + val power = status.size() + 1 + if (minToUse == null && maxToUse == null) { + maxToUse = BigDecimal.TEN.pow(power) + minToUse = maxToUse.negate() + } + when { + minToUse == null -> minToUse = maxToUse!!.subtract(BigDecimal.TEN.pow(power)) + maxToUse == null -> maxToUse = minToUse.add(BigDecimal.TEN.pow(power)) + } + val scale = decideScale() + val minShifted = minToUse!!.movePointRight(scale) + val maxShifted = maxToUse!!.movePointRight(scale) + val range = maxShifted.toBigInteger().subtract(minShifted.toBigInteger()) + var generated: BigInteger + do { + generated = random.nextBigInteger(range.bitLength()) + } while (generated >= range) + + return generatorContext.utModelConstructor.construct( + minShifted.add(BigDecimal(generated)).movePointLeft(scale), + BigDecimal::class.id + ) + } + + private fun decideScale(): Int { + var scale = Int.MIN_VALUE + if (min != null) scale = scale.coerceAtLeast(min!!.scale()) + if (max != null) scale = scale.coerceAtLeast(max!!.scale()) + if (precision != null) scale = scale.coerceAtLeast(precision!!.scale) + return scale.coerceAtLeast(0) + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/math/BigIntegerGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/math/BigIntegerGenerator.kt new file mode 100644 index 0000000000..2c8822e113 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/math/BigIntegerGenerator.kt @@ -0,0 +1,66 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.math + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.IntegralGenerator +import org.utbot.greyboxfuzzer.quickcheck.internal.Ranges +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.math.BigInteger + +/** + * + * Produces values of type [BigInteger]. + * + * + * With no additional configuration, the generated values are chosen from + * a range with a magnitude decided by + * [GenerationStatus.size]. + */ +class BigIntegerGenerator : IntegralGenerator(BigInteger::class.java) { + private var min: BigInteger? = null + private var max: BigInteger? = null + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or + * [maximum][InRange.max] inclusive, with uniform + * distribution. + * + * + * If an endpoint of the range is not specified, its value takes on + * a magnitude influenced by + * [GenerationStatus.size]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = BigInteger(range.min) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = BigInteger(range.max) + if (min != null && max != null) Ranges.checkRange(Ranges.Type.INTEGRAL, min, max) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val numberOfBits = status.size() + 1 + if (min == null && max == null) + return generatorContext.utModelConstructor.construct( + random.nextBigInteger(numberOfBits), + BigInteger::class.id + ) + + val minToUse = min ?: max!!.subtract(BigInteger.TEN.pow(numberOfBits)) + val maxToUse = max ?: minToUse.add(BigInteger.TEN.pow(numberOfBits)) + return generatorContext.utModelConstructor.construct( + Ranges.choose(random, minToUse, maxToUse), + BigInteger::class.id + ) + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/nio/charset/CharsetGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/nio/charset/CharsetGenerator.kt new file mode 100644 index 0000000000..6cb06f878a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/nio/charset/CharsetGenerator.kt @@ -0,0 +1,34 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.nio.charset + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.nio.charset.Charset + +/** + * Produces values of type [Charset]. + */ +class CharsetGenerator : Generator(Charset::class.java) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val charsetName = random.choose(Charset.availableCharsets().keys) + val charsetNameModel = generatorContext.utModelConstructor.construct(charsetName, stringClassId) + val charsetForNameId = Charset::forName.executableId + val modelId = generatorContext.utModelConstructor.computeUnusedIdAndUpdate() + return UtAssembleModel( + modelId, + Charset::class.id, + charsetForNameId.name + "#" + modelId, + UtExecutableCallModel(null, charsetForNameId, listOf(charsetNameModel)), + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ClockGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ClockGenerator.kt new file mode 100644 index 0000000000..9c558290cf --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ClockGenerator.kt @@ -0,0 +1,73 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.Clock +import java.time.Instant +import java.time.ZoneId + +/** + * Produces values of type [Clock]. + */ +class ClockGenerator : Generator(Clock::class.java) { + private var min = Instant.MIN + private var max = Instant.MAX + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution, down to the + * nanosecond. + * + * + * Instances of this class are configured using [Instant] + * strings. + * + * + * If an endpoint of the range is not specified, the generator will use + * instants with values of either [Instant.MIN] or + * [Instant.MAX] as appropriate. + * + * + * [InRange.format] is ignored. Instants are always + * parsed using [java.time.format.DateTimeFormatter.ISO_INSTANT]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = Instant.parse(range.min) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = Instant.parse(range.max) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val instant = random.nextInstant(min, max) + val zoneId = UTC_ZONE_ID + val instantModel = generatorContext.utModelConstructor.construct(instant, Instant::class.id) + val zoneIdModel = generatorContext.utModelConstructor.construct(zoneId, ZoneId::class.id) + val modelId = generatorContext.utModelConstructor.computeUnusedIdAndUpdate() + val constructorId = Clock::fixed.executableId + return UtAssembleModel( + id = modelId, + classId = Clock::class.id, + modelName = constructorId.name + "#" + modelId, + instantiationCall = UtExecutableCallModel(null, constructorId, listOf(instantModel, zoneIdModel)), + ) + } + + companion object { + private val UTC_ZONE_ID = ZoneId.of("UTC") + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/DurationGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/DurationGenerator.kt new file mode 100644 index 0000000000..8ec6950cbe --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/DurationGenerator.kt @@ -0,0 +1,49 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.Duration + +/** + * Produces values of type [Duration]. + */ +class DurationGenerator : Generator(Duration::class.java) { + private var min = Duration.ofSeconds(Long.MIN_VALUE, 0) + private var max = Duration.ofSeconds(Long.MAX_VALUE, 999999999) + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution, down to the + * nanosecond. + * + * + * If an endpoint of the range is not specified, the generator will use + * durations with second values of either [Long.MIN_VALUE] or + * [Long.MAX_VALUE] (with nanoseconds set to 999,999,999) as + * appropriate. + * + * + * [InRange.format] is ignored. Durations are always + * parsed using formats based on the ISO-8601 duration format + * `PnDTnHnMn.nS` with days considered to be exactly 24 hours. + * + * @see Duration.parse + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = Duration.parse(range.min) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = Duration.parse(range.max) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generatorContext.utModelConstructor.construct(random.nextDuration(min, max), Duration::class.id) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/InstantGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/InstantGenerator.kt new file mode 100644 index 0000000000..5aa2145117 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/InstantGenerator.kt @@ -0,0 +1,49 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.Instant + +/** + * Produces values of type [Instant]. + */ +class InstantGenerator : Generator(Instant::class.java) { + private var min = Instant.MIN + private var max = Instant.MAX + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution, down to the + * nanosecond. + * + * + * If an endpoint of the range is not specified, the generator will use + * instants with values of either [Instant.MIN] or + * [Instant.MAX] as appropriate. + * + * + * [InRange.format] is ignored. Instants are always + * parsed using [java.time.format.DateTimeFormatter.ISO_INSTANT]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = Instant.parse(range.min) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = Instant.parse(range.max) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct(random.nextInstant(min, max), Instant::class.id) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/LocalDateGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/LocalDateGenerator.kt new file mode 100644 index 0000000000..79e73c0072 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/LocalDateGenerator.kt @@ -0,0 +1,57 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +/** + * Produces values of type [LocalDate]. + */ +class LocalDateGenerator : Generator(LocalDate::class.java) { + private var min = LocalDate.MIN + private var max = LocalDate.MAX + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution. + * + * + * If an endpoint of the range is not specified, the generator will use + * dates with values of either [LocalDate.MIN] or + * [LocalDate.MAX] as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][DateTimeFormatter.ofPattern]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = DateTimeFormatter.ofPattern(range.format) + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = + LocalDate.parse(range.min, formatter) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = + LocalDate.parse(range.max, formatter) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + LocalDate.ofEpochDay( + random.nextLong(min.toEpochDay(), max.toEpochDay()) + ), + LocalDate::class.id + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/LocalDateTimeGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/LocalDateTimeGenerator.kt new file mode 100644 index 0000000000..836203a8a4 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/LocalDateTimeGenerator.kt @@ -0,0 +1,72 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +/** + * Produces values of type [LocalDateTime]. + */ +class LocalDateTimeGenerator : Generator( + LocalDateTime::class.java +) { + private var min = LocalDateTime.MIN + private var max = LocalDateTime.MAX + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution, down to the + * nanosecond. + * + * + * If an endpoint of the range is not specified, the generator will use + * dates with values of either [LocalDateTime.MIN] or + * [LocalDateTime.MAX] as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][DateTimeFormatter.ofPattern]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = DateTimeFormatter.ofPattern(range.format) + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = + LocalDateTime.parse(range.min, formatter) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = + LocalDateTime.parse(range.max, formatter) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + + /* Project the LocalDateTime to an Instant for easy long-based generation. + Any zone id will do as long as we use the same one throughout. */ + return generatorContext.utModelConstructor.construct( + LocalDateTime.ofInstant( + random.nextInstant( + min.atZone(UTC_ZONE_ID).toInstant(), + max.atZone(UTC_ZONE_ID).toInstant() + ), + UTC_ZONE_ID + ), + LocalDateTime::class.id + ) + } + + companion object { + private val UTC_ZONE_ID = ZoneId.of("UTC") + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/LocalTimeGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/LocalTimeGenerator.kt new file mode 100644 index 0000000000..e1f93aade3 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/LocalTimeGenerator.kt @@ -0,0 +1,58 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.LocalTime +import java.time.format.DateTimeFormatter + +/** + * Produces values of type [LocalTime]. + */ +class LocalTimeGenerator : Generator(LocalTime::class.java) { + private var min = LocalTime.MIN + private var max = LocalTime.MAX + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution, down to the + * nanosecond. + * + * + * If an endpoint of the range is not specified, the generator will use + * times with values of either [LocalTime.MIN] or + * [LocalTime.MAX] as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][DateTimeFormatter.ofPattern]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = DateTimeFormatter.ofPattern(range.format) + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = + LocalTime.parse(range.min, formatter) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = + LocalTime.parse(range.max, formatter) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + LocalTime.ofNanoOfDay( + random.nextLong(min.toNanoOfDay(), max.toNanoOfDay()) + ), + LocalTime::class.id + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/MonthDayGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/MonthDayGenerator.kt new file mode 100644 index 0000000000..66d5c44d2a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/MonthDayGenerator.kt @@ -0,0 +1,62 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.LocalDate +import java.time.MonthDay +import java.time.format.DateTimeFormatter + +/** + * Produces values of type [MonthDay]. + */ +class MonthDayGenerator : Generator(MonthDay::class.java) { + private var min = MonthDay.of(1, 1) + private var max = MonthDay.of(12, 31) + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution. + * + * + * If an endpoint of the range is not specified, the generator will use + * dates with values of either `MonthDay(1, 1)` or + * `MonthDay(12, 31)` as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][DateTimeFormatter.ofPattern]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = DateTimeFormatter.ofPattern(range.format) + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = + MonthDay.parse(range.min, formatter) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = + MonthDay.parse(range.max, formatter) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + + /* Project the MonthDay to a LocalDate for easy long-based generation. + Any leap year will do here. */ + val minEpochDay = min.atYear(2000).toEpochDay() + val maxEpochDay = max.atYear(2000).toEpochDay() + val date = LocalDate.ofEpochDay(random.nextLong(minEpochDay, maxEpochDay)) + return generatorContext.utModelConstructor.construct( + MonthDay.of(date.monthValue, date.dayOfMonth), + MonthDay::class.id + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/OffsetDateTimeGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/OffsetDateTimeGenerator.kt new file mode 100644 index 0000000000..54a46785e3 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/OffsetDateTimeGenerator.kt @@ -0,0 +1,68 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.OffsetDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +/** + * Produces values of type [OffsetDateTime]. + */ +class OffsetDateTimeGenerator : Generator( + OffsetDateTime::class.java +) { + private var min = OffsetDateTime.MIN + private var max = OffsetDateTime.MAX + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution, down to the + * nanosecond. + * + * + * If an endpoint of the range is not specified, the generator will use + * dates with values of either [OffsetDateTime.MIN] or + * [OffsetDateTime.MAX] as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][DateTimeFormatter.ofPattern]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = DateTimeFormatter.ofPattern(range.format) + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = + OffsetDateTime.parse(range.min, formatter) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = + OffsetDateTime.parse(range.max, formatter) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + + // Project the OffsetDateTime to an Instant for easy long-based generation. + return generatorContext.utModelConstructor.construct( + OffsetDateTime.ofInstant( + random.nextInstant(min.toInstant(), max.toInstant()), + UTC_ZONE_ID + ), + OffsetDateTime::class.id + ) + } + + companion object { + private val UTC_ZONE_ID = ZoneId.of("UTC") + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/OffsetTimeGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/OffsetTimeGenerator.kt new file mode 100644 index 0000000000..0a20bb983b --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/OffsetTimeGenerator.kt @@ -0,0 +1,68 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.LocalTime +import java.time.OffsetTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter + +/** + * Produces values of type [OffsetTime]. + */ +class OffsetTimeGenerator : Generator(OffsetTime::class.java) { + private var min = OffsetTime.MIN + private var max = OffsetTime.MAX + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution, down to the + * nanosecond. + * + * + * If an endpoint of the range is not specified, the generator will use + * times with values of either [OffsetTime.MIN] or + * [OffsetTime.MAX] as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][DateTimeFormatter.ofPattern]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = DateTimeFormatter.ofPattern(range.format) + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = + OffsetTime.parse(range.min, formatter) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = + OffsetTime.parse(range.max, formatter) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val time = LocalTime.ofNanoOfDay( + random.nextLong( + min.withOffsetSameInstant(ZoneOffset.UTC) + .toLocalTime() + .toNanoOfDay(), + max.withOffsetSameInstant(ZoneOffset.UTC) + .toLocalTime() + .toNanoOfDay() + ) + ) + return generatorContext.utModelConstructor.construct( + OffsetTime.of(time, ZoneOffset.UTC), + OffsetTime::class.id + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/PeriodGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/PeriodGenerator.kt new file mode 100644 index 0000000000..17881d428b --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/PeriodGenerator.kt @@ -0,0 +1,81 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Ranges +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.math.BigInteger +import java.time.Period +import java.time.Year + +/** + * Produces values of type [Period]. + */ +class PeriodGenerator : Generator(Period::class.java) { + private var min = Period.of(Year.MIN_VALUE, -12, -31) + private var max = Period.of(Year.MAX_VALUE, 12, 31) + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution. + * + * + * If an endpoint of the range is not specified, the generator will use + * Periods with values of either `Period(Year#MIN_VALUE, -12, -31)` + * or `Period(Year#MAX_VALUE, 12, 31)` as appropriate. + * + * + * [InRange.format] is ignored. Periods are always parsed + * using formats based on the ISO-8601 period formats `PnYnMnD` and + * `PnW`. + * + * @see Period.parse + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = Period.parse(range.min) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = Period.parse(range.max) + require(toBigInteger(min) <= toBigInteger(max)) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + fromBigInteger( + Ranges.choose(random, toBigInteger(min), toBigInteger(max)) + ), + Period::class.id + ) + } + + private fun toBigInteger(period: Period): BigInteger { + return BigInteger.valueOf(period.years.toLong()) + .multiply(TWELVE) + .add(BigInteger.valueOf(period.months.toLong())) + .multiply(THIRTY_ONE) + .add(BigInteger.valueOf(period.days.toLong())) + } + + private fun fromBigInteger(period: BigInteger): Period { + val monthsAndDays = period.divideAndRemainder(THIRTY_ONE) + val yearsAndMonths = monthsAndDays[0].divideAndRemainder(TWELVE) + return Period.of( + yearsAndMonths[0].intValueExact(), + yearsAndMonths[1].intValueExact(), + monthsAndDays[1].intValueExact() + ) + } + + companion object { + private val TWELVE = BigInteger.valueOf(12) + private val THIRTY_ONE = BigInteger.valueOf(31) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/YearGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/YearGenerator.kt new file mode 100644 index 0000000000..ed0d66a66a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/YearGenerator.kt @@ -0,0 +1,53 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.Year +import java.time.format.DateTimeFormatter + +/** + * Produces values of type [Year]. + */ +class YearGenerator : Generator(Year::class.java) { + private var min = Year.of(Year.MIN_VALUE) + private var max = Year.of(Year.MAX_VALUE) + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution. + * + * + * If an endpoint of the range is not specified, the generator will use + * Years with values of either `Year#MIN_VALUE` or + * `Year#MAX_VALUE` as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][DateTimeFormatter.ofPattern]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = DateTimeFormatter.ofPattern(range.format) + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = Year.parse(range.min, formatter) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = Year.parse(range.max, formatter) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + Year.of(random.nextInt(min.value, max.value)), + Year::class.id + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/YearMonthGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/YearMonthGenerator.kt new file mode 100644 index 0000000000..f4a32e3554 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/YearMonthGenerator.kt @@ -0,0 +1,64 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.Year +import java.time.YearMonth +import java.time.format.DateTimeFormatter +import kotlin.math.abs + +/** + * Produces values of type [YearMonth]. + */ +class YearMonthGenerator : Generator(YearMonth::class.java) { + private var min = YearMonth.of(Year.MIN_VALUE, 1) + private var max = YearMonth.of(Year.MAX_VALUE, 12) + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution. + * + * + * If an endpoint of the range is not specified, the generator will use + * dates with values of either `YearMonth(Year#MIN_VALUE, 1)` or + * `YearMonth(Year#MAX_VALUE, 12)` as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][DateTimeFormatter.ofPattern]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = DateTimeFormatter.ofPattern(range.format) + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = + YearMonth.parse(range.min, formatter) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = + YearMonth.parse(range.max, formatter) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val generated = random.nextLong( + min.year * 12L + min.monthValue - 1, + max.year * 12L + max.monthValue - 1 + ) + return generatorContext.utModelConstructor.construct( + YearMonth.of( + (generated / 12).toInt(), + abs(generated % 12).toInt() + 1 + ), + YearMonth::class.id + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ZoneIdGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ZoneIdGenerator.kt new file mode 100644 index 0000000000..b51eacda26 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ZoneIdGenerator.kt @@ -0,0 +1,24 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.ZoneId + +/** + * Produces values of type [ZoneId]. + */ +class ZoneIdGenerator : Generator(ZoneId::class.java) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + ZoneId.of(random.choose(ZoneId.getAvailableZoneIds())), + ZoneId::class.id + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ZoneOffsetGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ZoneOffsetGenerator.kt new file mode 100644 index 0000000000..112d047831 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ZoneOffsetGenerator.kt @@ -0,0 +1,63 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.ZoneOffset + +/** + * Produces values of type [ZoneOffset]. + */ +class ZoneOffsetGenerator : Generator(ZoneOffset::class.java) { + /* The way ZoneOffsets work, ZoneOffset.MAX (-18:00) is actually + the lower end of the seconds range, whereas ZoneOffset.MIN (+18:00) + is the upper end. */ + private var min = ZoneOffset.MAX + private var max = ZoneOffset.MIN + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution. + * + * + * If an endpoint of the range is not specified, the generator will use + * ZoneOffsets with values of either `ZoneOffset#MIN` or + * `ZoneOffset#MAX` as appropriate. + * + * + * [InRange.format] is ignored. ZoneOffsets are always + * parsed using their zone id. + * + * @see ZoneOffset.of + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = ZoneOffset.of(range.min) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = ZoneOffset.of(range.max) + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val minSeconds = min.totalSeconds + val maxSeconds = max.totalSeconds + + return generatorContext.utModelConstructor.construct( + ZoneOffset.ofTotalSeconds( + if (minSeconds <= maxSeconds) random.nextInt(minSeconds, maxSeconds) else random.nextInt( + maxSeconds, + minSeconds + ) + ), + ZoneOffset::class.id + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ZonedDateTimeGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ZonedDateTimeGenerator.kt new file mode 100644 index 0000000000..8f3c99f81f --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/time/ZonedDateTimeGenerator.kt @@ -0,0 +1,74 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.time + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.time.Year +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +/** + * Produces values of type [ZonedDateTime]. + */ +class ZonedDateTimeGenerator : Generator(ZonedDateTime::class.java) { + private var min = ZonedDateTime.of(Year.MIN_VALUE, 1, 1, 0, 0, 0, 0, UTC_ZONE_ID) + private var max = ZonedDateTime.of( + Year.MAX_VALUE, 12, 31, 23, 59, 59, 999999999, UTC_ZONE_ID + ) + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution, down to the + * nanosecond. + * + * + * If an endpoint of the range is not specified, the generator will use + * dates with values of either [java.time.Instant.MIN] or + * [java.time.Instant.MAX] and UTC zone as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][DateTimeFormatter.ofPattern]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = DateTimeFormatter.ofPattern(range.format) + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) { + min = ZonedDateTime.parse(range.min, formatter) + .withZoneSameInstant(UTC_ZONE_ID) + } + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) { + max = ZonedDateTime.parse(range.max, formatter) + .withZoneSameInstant(UTC_ZONE_ID) + } + require(min <= max) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + + // Project the ZonedDateTime to an Instant for easy long-based + // generation. + return generatorContext.utModelConstructor.construct( + ZonedDateTime.ofInstant( + random.nextInstant(min.toInstant(), max.toInstant()), + UTC_ZONE_ID + ), + ZonedDateTime::class.id + ) + } + + companion object { + private val UTC_ZONE_ID = ZoneId.of("UTC") + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/ArrayListGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/ArrayListGenerator.kt new file mode 100644 index 0000000000..295a36c4ed --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/ArrayListGenerator.kt @@ -0,0 +1,6 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +/** + * Produces values of type [ArrayList]. + */ +class ArrayListGenerator : ListGenerator(ArrayList::class.java) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/BitSetGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/BitSetGenerator.kt new file mode 100644 index 0000000000..a81931d74e --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/BitSetGenerator.kt @@ -0,0 +1,26 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.BitSet + +/** + * Produces values of type [BitSet]. + */ +class BitSetGenerator : Generator(BitSet::class.java) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val size = status.size() + val bits = BitSet(size) + for (i in 0 until size) { + bits[i] = random.nextBoolean() + } + return generatorContext.utModelConstructor.construct(bits, BitSet::class.id) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/CollectionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/CollectionGenerator.kt new file mode 100644 index 0000000000..5cf42cfe92 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/CollectionGenerator.kt @@ -0,0 +1,130 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.util.FuzzerIllegalStateException +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.* +import org.utbot.greyboxfuzzer.quickcheck.internal.Ranges +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * + * Base class for generators of [Collection]s. + * + * + * The generated collection has a number of elements limited by + * [GenerationStatus.size], or else by the attributes of a [Size] + * marking. The individual elements will have a type corresponding to the + * collection's type argument. + * + * @param the type of collection generated + */ +abstract class CollectionGenerator(type: Class<*>) : ComponentizedGenerator(type) { + private var sizeRange: Size? = null + private var distinct = false + + /** + * + * Tells this generator to add elements to the generated collection + * a number of times within a specified minimum and/or maximum, inclusive, + * chosen with uniform distribution. + * + * + * Note that some kinds of collections disallow duplicates, so the + * number of elements added may not be equal to the collection's + * [Collection.size]. + * + * @param size annotation that gives the size constraints + */ + open fun configure(size: Size) { + sizeRange = size + Ranges.checkRange(Ranges.Type.INTEGRAL, size.min, size.max) + } + + /** + * Tells this generator to add elements which are distinct from each other. + * + * @param distinct Generated elements will be distinct if this param is + * not null + */ + fun configure(distinct: Distinct?) { + setDistinct(distinct != null) + } + + protected fun setDistinct(distinct: Boolean) { + this.distinct = distinct + } + + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + val cachedModel = generatedUtModel ?: throw FuzzerIllegalStateException("Nothing to modify") + val collectionClassId = types().single().id + val collectionConstructorId = ConstructorId(collectionClassId, emptyList()) + val genId = cachedModel.getIdOrThrow() + return UtAssembleModel( + genId, + cachedModel.classId, + collectionConstructorId.name + "#" + genId, + UtExecutableCallModel(null, collectionConstructorId, emptyList()) + ) { + val addMethodId = MethodId(classId, "add", booleanClassId, listOf(objectClassId)) + (0 until nestedGenerators.size).map { ind -> + val generator = nestedGenerators[ind] + val item = generator.generateImpl(random, status) + generator.generationState = GenerationState.CACHE + UtExecutableCallModel( + this, + addMethodId, + listOf(item) + ) + } + } + } + + private fun regenerate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + nestedGenerators.clear() + val collectionClassId = types().single().id + val collectionConstructorId = ConstructorId(collectionClassId, emptyList()) + val genId = generatorContext.utModelConstructor.computeUnusedIdAndUpdate() + return UtAssembleModel( + genId, + collectionClassId, + collectionConstructorId.name + "#" + genId, + UtExecutableCallModel(null, collectionConstructorId, emptyList()), + ) { + val size = size(random, status) + (0..size).map { + val addMethodId = MethodId(classId, "add", booleanClassId, listOf(objectClassId)) + val generator = componentGenerators().first().copy().also { nestedGenerators.add(it) } + val item = generator.generateImpl(random, status) + UtExecutableCallModel(this, addMethodId, listOf(item)) + } + } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel = + when (generationState) { + GenerationState.REGENERATE -> regenerate(random, status) + GenerationState.MODIFY -> modify(random, status) + GenerationState.MODIFYING_CHAIN -> createModifiedUtModel(random, status) + GenerationState.CACHE -> generatedUtModel ?: throw FuzzerIllegalStateException("No cached model") + } + + + override fun numberOfNeededComponents(): Int { + return 1 + } + + private fun size(random: SourceOfRandomness, status: GenerationStatus): Int { + return if (sizeRange != null) random.nextInt(sizeRange!!.min, sizeRange!!.max) else status.size() + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/DateGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/DateGenerator.kt new file mode 100644 index 0000000000..d5077b66f8 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/DateGenerator.kt @@ -0,0 +1,60 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Date + +/** + * Produces values of type [Date]. + */ +class DateGenerator : Generator(Date::class.java) { + private var min: Date = Date(Long.MIN_VALUE) + private var max = Date(Long.MAX_VALUE) + + /** + * + * Tells this generator to produce values within a specified + * [minimum][InRange.min] and/or [ maximum][InRange.max], inclusive, with uniform distribution, down to the + * millisecond. + * + * + * If an endpoint of the range is not specified, the generator will use + * dates with milliseconds-since-the-epoch values of either + * [Integer.MIN_VALUE] or [Long.MAX_VALUE] as appropriate. + * + * + * [InRange.format] describes + * [how the generator is to][SimpleDateFormat.parse]. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange) { + val formatter = SimpleDateFormat(range.format) + formatter.isLenient = false + try { + if (Reflection.defaultValueOf(InRange::class.java, "min") != range.min) min = formatter.parse(range.min) + if (Reflection.defaultValueOf(InRange::class.java, "max") != range.max) max = formatter.parse(range.max) + } catch (e: ParseException) { + throw IllegalArgumentException(e) + } + require(min.time <= max.time) { String.format("bad range, %s > %s", min, max) } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + Date(random.nextLong(min.time, max.time)), + classIdForType(Date::class.java) + ) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/HashMapGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/HashMapGenerator.kt new file mode 100644 index 0000000000..2269d06745 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/HashMapGenerator.kt @@ -0,0 +1,6 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +/** + * Produces values of type [HashMap]. + */ +class HashMapGenerator : MapGenerator(HashMap::class.java) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/HashSetGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/HashSetGenerator.kt new file mode 100644 index 0000000000..13d0a41d5d --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/HashSetGenerator.kt @@ -0,0 +1,6 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +/** + * Produces values of type [HashSet]. + */ +class HashSetGenerator : SetGenerator(HashSet::class.java) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/HashtableGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/HashtableGenerator.kt new file mode 100644 index 0000000000..7484022431 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/HashtableGenerator.kt @@ -0,0 +1,12 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import java.util.Hashtable + +/** + * Produces values of type [Hashtable]. + */ +class HashtableGenerator : MapGenerator(Hashtable::class.java) { + override fun okToAdd(key: Any?, value: Any?): Boolean { + return key != null && value != null + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LinkedHashMapGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LinkedHashMapGenerator.kt new file mode 100644 index 0000000000..d957087fd3 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LinkedHashMapGenerator.kt @@ -0,0 +1,6 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +/** + * Produces values of type [LinkedHashMap]. + */ +class LinkedHashMapGenerator : MapGenerator(LinkedHashMap::class.java) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LinkedHashSetGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LinkedHashSetGenerator.kt new file mode 100644 index 0000000000..957d5b1ee0 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LinkedHashSetGenerator.kt @@ -0,0 +1,6 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +/** + * Produces values of type [LinkedHashSet]. + */ +class LinkedHashSetGenerator : SetGenerator(LinkedHashSet::class.java) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LinkedListGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LinkedListGenerator.kt new file mode 100644 index 0000000000..3b833c9a90 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LinkedListGenerator.kt @@ -0,0 +1,8 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import java.util.LinkedList + +/** + * Produces values of type [LinkedList]. + */ +class LinkedListGenerator : ListGenerator(LinkedList::class.java) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/ListGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/ListGenerator.kt new file mode 100644 index 0000000000..bf5d1195fc --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/ListGenerator.kt @@ -0,0 +1,8 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +/** + * Base class for generators of [List]s. + * + * @param the type of list generated + */ +abstract class ListGenerator protected constructor(type: Class<*>) : CollectionGenerator(type) diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LocaleGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LocaleGenerator.kt new file mode 100644 index 0000000000..b282d28cb1 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/LocaleGenerator.kt @@ -0,0 +1,28 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.Locale + +/** + * Produces values of type [Locale]. + */ +class LocaleGenerator : Generator(Locale::class.java) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + random.choose(AVAILABLE_LOCALES), + classIdForType(Locale::class.java) + ) + } + + companion object { + private val AVAILABLE_LOCALES = Locale.getAvailableLocales() + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/MapGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/MapGenerator.kt new file mode 100644 index 0000000000..a691b2f840 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/MapGenerator.kt @@ -0,0 +1,132 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.util.FuzzerIllegalStateException +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.* +import org.utbot.greyboxfuzzer.quickcheck.internal.Ranges +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +/** + * + * Base class for generators of [Map]s. + * + * + * The generated map has a number of entries limited by + * [GenerationStatus.size], or else by the attributes of a [Size] + * marking. The individual keys and values will have types corresponding to the + * property parameter's type arguments. + * + * @param the type of map generated + */ +abstract class MapGenerator protected constructor(type: Class<*>) : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(type) { + private var sizeRange: Size? = null + private var distinct = false + + /** + * + * Tells this generator to add key-value pairs to the generated map a + * number of times within a specified minimum and/or maximum, inclusive, + * chosen with uniform distribution. + * + * + * Note that maps disallow duplicate keys, so the number of pairs added + * may not be equal to the map's [Map.size]. + * + * @param size annotation that gives the size constraints + */ + fun configure(size: Size) { + sizeRange = size + Ranges.checkRange(Ranges.Type.INTEGRAL, size.min, size.max) + } + + /** + * Tells this generator to add entries whose keys are distinct from + * each other. + * + * @param distinct Keys of generated entries will be distinct if this + * param is not null + */ + fun configure(distinct: Distinct?) { + this.distinct = distinct != null + } + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + val cachedUtModel = generatedUtModel ?: throw FuzzerIllegalStateException("Nothing to modify") + val size = nestedGenerators.size / 2 + val classId = types().single().id + + val modelId = cachedUtModel.getIdOrThrow() + val constructorId = ConstructorId(classId, emptyList()) + return UtAssembleModel( + modelId, + classId, + constructorId.name + "#" + modelId, + UtExecutableCallModel(null, constructorId, emptyList()), + ) { + val putMethodId = MethodId(classId, "put", objectClassId, listOf(objectClassId, objectClassId)) + (0 until size).map { ind -> + val keyGenerator = nestedGenerators[ind * 2] + val valueGenerator = nestedGenerators[ind * 2 + 1] + val key = keyGenerator.generateImpl(random, status) + val value = valueGenerator.generateImpl(random, status) + keyGenerator.generationState = GenerationState.CACHE + valueGenerator.generationState = GenerationState.CACHE + UtExecutableCallModel(this, putMethodId, listOf(key, value)) + } + } + } + private fun regenerate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val size = size(random, status) + val classId = types().single().id + + val generatedModelId = generatorContext.utModelConstructor.computeUnusedIdAndUpdate() + val constructorId = ConstructorId(classId, emptyList()) + nestedGenerators.clear() + return UtAssembleModel( + generatedModelId, + classId, + constructorId.name + "#" + generatedModelId, + UtExecutableCallModel(null, constructorId, emptyList()), + ) { + val putMethodId = MethodId(classId, "put", objectClassId, listOf(objectClassId, objectClassId)) + (0..size).map { + val keyGenerator = componentGenerators()[0].copy().also { nestedGenerators.add(it) } + val valueGenerator = componentGenerators()[1].copy().also { nestedGenerators.add(it) } + val key = keyGenerator.generateImpl(random, status) + val value = valueGenerator.generateImpl(random, status) + key to value + UtExecutableCallModel(this, putMethodId, listOf(key, value)) + } + } + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel = + when (generationState) { + GenerationState.REGENERATE -> regenerate(random, status) + GenerationState.MODIFY -> modify(random, status) + GenerationState.MODIFYING_CHAIN -> createModifiedUtModel(random, status) + GenerationState.CACHE -> generatedUtModel ?: throw FuzzerIllegalStateException("No cached model") + } + + override fun numberOfNeededComponents(): Int { + return 2 + } + + protected open fun okToAdd(key: Any?, value: Any?): Boolean { + return true + } + + private fun size(random: SourceOfRandomness, status: GenerationStatus): Int { + return if (sizeRange != null) random.nextInt(sizeRange!!.min, sizeRange!!.max) else status.size() + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalDoubleGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalDoubleGenerator.kt new file mode 100644 index 0000000000..d3871d1eb7 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalDoubleGenerator.kt @@ -0,0 +1,43 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.DoubleGenerator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.OptionalDouble + +/** + * Produces values of type [OptionalDouble]. + */ +class OptionalDoubleGenerator : Generator(OptionalDouble::class.java) { + private val doubles = DoubleGenerator() + + /** + * Tells this generator to produce values, when + * [present][OptionalDouble.isPresent], within a specified minimum + * (inclusive) and/or maximum (exclusive) with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minDouble] and [InRange.maxDouble], + * if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange?) { + doubles.configure(range!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val trial = random.nextDouble() + val generated = + if (trial < 0.25) OptionalDouble.empty() else OptionalDouble.of(doubles.generateValue(random, status)) + return generatorContext.utModelConstructor.construct(generated, classIdForType(OptionalDouble::class.java)) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalGenerator.kt new file mode 100644 index 0000000000..e2db35a95f --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalGenerator.kt @@ -0,0 +1,47 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.methodId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.Optional + +/** + * Produces values of type [Optional]. + */ +class OptionalGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(Optional::class.java) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val trial = random.nextDouble() + val classId = Optional::class.id + if (trial < 0.25) { + return generatorContext.utModelConstructor.construct( + Optional.empty(), + classId + ) + } + val value = componentGenerators().first().generateImpl(random, status) + val constructorId = methodId(classId, "of", classId, objectClassId) + val generatedModelId = generatorContext.utModelConstructor.computeUnusedIdAndUpdate() + return UtAssembleModel( + generatedModelId, + classId, + constructorId.name + "#" + generatedModelId, + UtExecutableCallModel(null, constructorId, listOf(value)), + ) + } + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel = generate(random, status) + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalIntGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalIntGenerator.kt new file mode 100644 index 0000000000..3e501500f0 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalIntGenerator.kt @@ -0,0 +1,42 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.IntegerGenerator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.OptionalInt + +/** + * Produces values of type [OptionalInt]. + */ +class OptionalIntGenerator : Generator(OptionalInt::class.java) { + private val integers = IntegerGenerator() + + /** + * Tells this generator to produce values, when + * [present][OptionalInt.isPresent], within a specified minimum + * and/or maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minInt] and [InRange.maxInt], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange?) { + integers.configure(range!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val trial = random.nextDouble() + val generated = + if (trial < 0.25) OptionalInt.empty() else OptionalInt.of(integers.generateValue(random)) + return generatorContext.utModelConstructor.construct(generated, classIdForType(OptionalInt::class.java)) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalLongGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalLongGenerator.kt new file mode 100644 index 0000000000..28c1437fb4 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/OptionalLongGenerator.kt @@ -0,0 +1,41 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.InRange +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.LongGenerator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.OptionalLong + +/** + * Produces values of type [OptionalLong]. + */ +class OptionalLongGenerator : Generator(OptionalLong::class.java) { + private val longs = LongGenerator() + + /** + * Tells this generator to produce values, when + * [present][OptionalLong.isPresent], within a specified minimum + * and/or maximum, inclusive, with uniform distribution. + * + * [InRange.min] and [InRange.max] take precedence over + * [InRange.minLong] and [InRange.maxLong], if non-empty. + * + * @param range annotation that gives the range's constraints + */ + fun configure(range: InRange?) { + longs.configure(range!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val trial = random.nextDouble() + val generated = if (trial < 0.25) OptionalLong.empty() else OptionalLong.of(longs.generateValue(random, status)) + return generatorContext.utModelConstructor.construct(generated, classIdForType(OptionalLong::class.java)) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/PropertiesGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/PropertiesGenerator.kt new file mode 100644 index 0000000000..557c894c98 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/PropertiesGenerator.kt @@ -0,0 +1,68 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.methodId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.greyboxfuzzer.generator.GreyBoxFuzzerGeneratorsAndSettings +import org.utbot.greyboxfuzzer.generator.getOrProduceGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.AbstractStringGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.StringGenerator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.Dictionary +import java.util.Hashtable +import java.util.Properties + +/** + * Produces values of type [Properties]. + */ +class PropertiesGenerator : Generator(Properties::class.java) { +// fun configure(charset: InCharset?) { +// val encoded = Encoded() +// encoded.configure(charset!!) +// stringGenerator = encoded +// } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val size = status.size() + val classId = Properties::class.id + val stringGenerator = GreyBoxFuzzerGeneratorsAndSettings.generatorRepository.getOrProduceGenerator( + String::class.java, + generatorContext + )!! + val generatedModelId = generatorContext.utModelConstructor.computeUnusedIdAndUpdate() + val constructorId = ConstructorId(classId, emptyList()) + return UtAssembleModel( + generatedModelId, + classId, + constructorId.name + "#" + generatedModelId, + UtExecutableCallModel(null, constructorId, emptyList()), + ) { + val setPropertyMethodId = methodId(classId, "setProperty", objectClassId, objectClassId, objectClassId) + (0..size).map { + val key = stringGenerator.generateImpl(random, status) + val value = stringGenerator.generateImpl(random, status) + UtExecutableCallModel(this, setPropertyMethodId, listOf(key, value)) + } + } + } + + override fun canRegisterAsType(type: Class<*>): Boolean { + val exclusions = setOf( + Any::class.java, + Hashtable::class.java, + MutableMap::class.java, + Dictionary::class.java + ) + return !exclusions.contains(type) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/RFC4122.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/RFC4122.kt new file mode 100644 index 0000000000..1a1928d9e2 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/RFC4122.kt @@ -0,0 +1,183 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorConfiguration +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.StringGenerator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.util.UUID + +/** + * Home for machinery to produce [UUID]s according to + * [RFC 4122](http://www.ietf.org/rfc/rfc4122.txt). + */ +object RFC4122 { + + abstract class AbstractUUIDGenerator : Generator(UUID::class.java) { + protected fun setVersion(bytes: ByteArray, mask: Byte) { + bytes[6] = (bytes[6].toInt() and 0x0F).toByte() + bytes[6] = (bytes[6].toInt() or mask.toInt()).toByte() + } + + protected fun setVariant(bytes: ByteArray) { + bytes[8] = (bytes[8].toInt() and 0x3F).toByte() + bytes[8] = (bytes[8].toInt() or 0x80).toByte() + } + + protected fun newUUID(bytes: ByteArray?): UUID { + val bytesIn = ByteBuffer.wrap(bytes) + return UUID(bytesIn.long, bytesIn.long) + } + } + + abstract class NameBasedUUIDGenerator(hashAlgorithmName: String?, private val versionMask: Int) : + AbstractUUIDGenerator() { + private val strings = StringGenerator() + private val digest: MessageDigest + private var namespace: Namespace? = null + + init { + digest = MessageDigests[hashAlgorithmName] + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + digest.reset() + val namespaces = if (namespace == null) Namespaces.URL else namespace!!.value + digest.update(namespaces.bytes) + digest.update( + strings.generateValue(random, status) + .toByteArray(StandardCharsets.UTF_8) + ) + val hash = digest.digest() + setVersion(hash, versionMask.toByte()) + setVariant(hash) + val generatedUUID = newUUID(hash) + return generatorContext.utModelConstructor.construct(generatedUUID, classIdForType(UUID::class.java)) + } + + protected fun setNamespace(namespace: Namespace?) { + this.namespace = namespace + } + } + + internal class MessageDigests private constructor() { + init { + throw UnsupportedOperationException() + } + + companion object { + operator fun get(algorithmName: String?): MessageDigest { + return try { + MessageDigest.getInstance(algorithmName) + } catch (shouldNeverHappen: NoSuchAlgorithmException) { + throw IllegalStateException(shouldNeverHappen) + } + } + } + } + + /** + * Produces values of type [UUID] that are RFC 4122 Version 3 + * identifiers. + */ + class Version3 : NameBasedUUIDGenerator("MD5", 0x30) { + /** + * Tells this generator to prepend the given "namespace" UUID to the + * names it generates for UUID production. + * + * @param namespace a handle for a "namespace" UUID + */ + fun configure(namespace: Namespace?) { + setNamespace(namespace) + } + } + + /** + * Produces values of type [UUID] that are RFC 4122 Version 4 + * identifiers. + */ + class Version4 : AbstractUUIDGenerator() { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val bytes = random.nextBytes(16) + setVersion(bytes, 0x40.toByte()) + setVariant(bytes) + val generatedUUID = newUUID(bytes) + return generatorContext.utModelConstructor.construct(generatedUUID, classIdForType(UUID::class.java)) + } + } + + /** + * Produces values of type [UUID] that are RFC 4122 Version 5 + * identifiers. + */ + class Version5 : NameBasedUUIDGenerator("SHA-1", 0x50) { + /** + * Tells this generator to prepend the given "namespace" UUID to the + * names it generates for UUID production. + * + * @param namespace a handle for a "namespace" UUID + */ + fun configure(namespace: Namespace?) { + setNamespace(namespace) + } + } + + /** + * Used in version 3 and version 5 UUID generation to specify a + * "namespace" UUID for use in generation. + */ + @Target( + AnnotationTarget.VALUE_PARAMETER, + AnnotationTarget.FIELD, + AnnotationTarget.ANNOTATION_CLASS, + AnnotationTarget.TYPE + ) + @Retention(AnnotationRetention.RUNTIME) + @GeneratorConfiguration + annotation class Namespace( + /** + * @return a handle on a "namespace" UUID to use in generation + */ + val value: Namespaces = Namespaces.URL + ) + + /** + * Well-known "namespace" UUIDs. + */ + enum class Namespaces(difference: Int) { + /** Fully-qualified DNS name. */ + DNS(0x10), + + /** URL. */ + URL(0x11), + + /** ISO object identifier. */ + ISO_OID(0x12), + + /** X.500 distinguished name. */ + X500_DN(0x14); + + val bytes: ByteArray + + init { + bytes = byteArrayOf( + 0x6B, 0xA7.toByte(), 0xB8.toByte(), difference.toByte(), 0x9D.toByte(), 0xAD.toByte(), + 0x11, 0xD1.toByte(), 0x80.toByte(), 0xB4.toByte(), + 0x00, 0xC0.toByte(), 0x4F, 0xD4.toByte(), 0x30, 0xC8.toByte() + ) + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/SetGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/SetGenerator.kt new file mode 100644 index 0000000000..b11357905c --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/SetGenerator.kt @@ -0,0 +1,14 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.Size + +/** + * Base class for generators of [Set]s. + * + * */ +abstract class SetGenerator constructor(type: Class<*>) : CollectionGenerator(type) { + override fun configure(size: Size) { + super.configure(size) + setDistinct(true) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/StackGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/StackGenerator.kt new file mode 100644 index 0000000000..466a1d6597 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/StackGenerator.kt @@ -0,0 +1,8 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import java.util.Stack + +/** + * Produces values of type [Stack]. + */ +class StackGenerator : ListGenerator(Stack::class.java) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/TimeZoneGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/TimeZoneGenerator.kt new file mode 100644 index 0000000000..89936c6a76 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/TimeZoneGenerator.kt @@ -0,0 +1,28 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.TimeZone + +/** + * Produces values of type [TimeZone]. + */ +class TimeZoneGenerator : Generator(TimeZone::class.java) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + TimeZone.getTimeZone(random.choose(AVAILABLE_IDS)), + classIdForType(TimeZone::class.java) + ) + } + + companion object { + private val AVAILABLE_IDS = TimeZone.getAvailableIDs() + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/VectorGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/VectorGenerator.kt new file mode 100644 index 0000000000..b7af182a04 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/VectorGenerator.kt @@ -0,0 +1,8 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util + +import java.util.Vector + +/** + * Produces values of type [Vector]. + */ +class VectorGenerator : ListGenerator(Vector::class.java) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/concurrent/CallableGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/concurrent/CallableGenerator.kt new file mode 100644 index 0000000000..e591313fe6 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/concurrent/CallableGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.concurrent + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.concurrent.Callable + +/** + * Produces values of type `Callable`. + * + * @param the type of the values produced by the generated instances + */ +class CallableGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(Callable::class.java) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + Callable::class.java, + componentGenerators()[0], + status + ), + Callable::class.id + ) + } + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/BiFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/BiFunctionGenerator.kt new file mode 100644 index 0000000000..f447989cb6 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/BiFunctionGenerator.kt @@ -0,0 +1,42 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.BiFunction + +/** + * Produces values of type [BiFunction]. + * + * @param type of first parameter of produced function + * @param type of second parameter of produced function + * @param return type of produced function + */ +class BiFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(BiFunction::class.java) { + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + BiFunction::class.java, + componentGenerators()[2], + status + ), + classIdForType(BiFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 3 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/BiPredicateGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/BiPredicateGenerator.kt new file mode 100644 index 0000000000..e729c93517 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/BiPredicateGenerator.kt @@ -0,0 +1,45 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.BiPredicate + +/** + * Produces values of type [BiPredicate]. + * + * @param type of first parameter of produced predicate + * @param type of second parameter of produced predicate + */ +class BiPredicateGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(BiPredicate::class.java) { + private var generator: Generator? = null + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun provide(provided: Generators) { + super.provide(provided) + generator = gen()!!.type(Boolean::class.javaPrimitiveType!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda(BiPredicate::class.java, generator!!, status), + classIdForType(BiPredicate::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 2 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/BinaryOperatorGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/BinaryOperatorGenerator.kt new file mode 100644 index 0000000000..6147bf71f7 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/BinaryOperatorGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.BinaryOperator + +/** + * Produces values of type [BinaryOperator]. + * + * @param parameters type and return type of produced operator + */ +class BinaryOperatorGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(BinaryOperator::class.java) { + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + BinaryOperator::class.java, + componentGenerators()[0], + status + ), classIdForType(BinaryOperator::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/DoubleFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/DoubleFunctionGenerator.kt new file mode 100644 index 0000000000..27be498699 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/DoubleFunctionGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.DoubleFunction + +/** + * Produces values of type [DoubleFunction]. + * + * @param return type of produced function + */ +class DoubleFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(DoubleFunction::class.java) { + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + DoubleFunction::class.java, + componentGenerators()[0], + status + ), classIdForType(DoubleFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/FunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/FunctionGenerator.kt new file mode 100644 index 0000000000..714980ff70 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/FunctionGenerator.kt @@ -0,0 +1,40 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.Function + +/** + * Produces values of type [Function]. + * + * @param type of parameter of produced function + * @param return type of produced function + */ +class FunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(Function::class.java) { + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + Function::class.java, + componentGenerators()[1], + status + ), classIdForType(Function::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 2 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/IntFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/IntFunctionGenerator.kt new file mode 100644 index 0000000000..d29c87975a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/IntFunctionGenerator.kt @@ -0,0 +1,38 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.IntFunction + +/** + * Produces values of type [IntFunction]. + * + * @param return type of produced function + */ +class IntFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(IntFunction::class.java) { + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + IntFunction::class.java, + componentGenerators()[0], + status + ), classIdForType(IntFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/LongFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/LongFunctionGenerator.kt new file mode 100644 index 0000000000..cb102b9b50 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/LongFunctionGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.LongFunction + +/** + * Produces values of type [LongFunction]. + * + * @param return type of produced function + */ +class LongFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(LongFunction::class.java) { + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + LongFunction::class.java, + componentGenerators()[0], + status + ), classIdForType(LongFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/PredicateGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/PredicateGenerator.kt new file mode 100644 index 0000000000..fdc222b24c --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/PredicateGenerator.kt @@ -0,0 +1,46 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.Predicate + +/** + * Produces values of type [Predicate]. + * + * @param type of parameter of produced predicate + */ +class PredicateGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(Predicate::class.java) { + private var generator: Generator? = null + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun provide(provided: Generators) { + super.provide(provided) + generator = gen()!!.type(Boolean::class.javaPrimitiveType!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + Predicate::class.java, generator!!, status + ), + classIdForType(Predicate::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/SupplierGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/SupplierGenerator.kt new file mode 100644 index 0000000000..4dae9e940f --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/SupplierGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.Supplier + +/** + * Produces values of type `Supplier`. + * + * @param the type of the values produced by the generated instances + */ +class SupplierGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(Supplier::class.java) { + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + Supplier::class.java, + componentGenerators()[0], + status + ), classIdForType(Supplier::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToDoubleBiFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToDoubleBiFunctionGenerator.kt new file mode 100644 index 0000000000..0fff3f8557 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToDoubleBiFunctionGenerator.kt @@ -0,0 +1,46 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.ToDoubleBiFunction + +/** + * Produces values of type [ToDoubleBiFunction]. + * + * @param type of first parameter of produced function + * @param type of second parameter of produced function + */ +class ToDoubleBiFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(ToDoubleBiFunction::class.java) { + private var generator: Generator? = null + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun provide(provided: Generators) { + super.provide(provided) + generator = gen()!!.type(Double::class.javaPrimitiveType!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + ToDoubleBiFunction::class.java, generator!!, status + ), classIdForType(ToDoubleBiFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 2 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToDoubleFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToDoubleFunctionGenerator.kt new file mode 100644 index 0000000000..6f9b322a83 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToDoubleFunctionGenerator.kt @@ -0,0 +1,45 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.ToDoubleFunction + +/** + * Produces values of type [ToDoubleFunction]. + * + * @param type of parameter of produced function + */ +class ToDoubleFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(ToDoubleFunction::class.java) { + private var generator: Generator? = null + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun provide(provided: Generators) { + super.provide(provided) + generator = gen()!!.type(Double::class.javaPrimitiveType!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + ToDoubleFunction::class.java, generator!!, status + ), classIdForType(ToDoubleFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToIntBiFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToIntBiFunctionGenerator.kt new file mode 100644 index 0000000000..8ebe58fe64 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToIntBiFunctionGenerator.kt @@ -0,0 +1,46 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.ToIntBiFunction + +/** + * Produces values of type [ToIntBiFunction]. + * + * @param type of first parameter of produced function + * @param type of second parameter of produced function + */ +class ToIntBiFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(ToIntBiFunction::class.java) { + private var generator: Generator? = null + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun provide(provided: Generators) { + super.provide(provided) + generator = gen()!!.type(Int::class.javaPrimitiveType!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + ToIntBiFunction::class.java, generator!!, status + ), classIdForType(ToIntBiFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 2 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToIntFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToIntFunctionGenerator.kt new file mode 100644 index 0000000000..744e34f96b --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToIntFunctionGenerator.kt @@ -0,0 +1,44 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.ToIntFunction + +/** + * Produces values of type [ToIntFunction]. + * + * @param type of parameter of produced function + */ +class ToIntFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(ToIntFunction::class.java) { + private var generator: Generator? = null + override fun provide(provided: Generators) { + super.provide(provided) + generator = gen()!!.type(Int::class.javaPrimitiveType!!) + } + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + ToIntFunction::class.java, generator!!, status + ), classIdForType(ToIntFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToLongBiFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToLongBiFunctionGenerator.kt new file mode 100644 index 0000000000..de8d07607c --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToLongBiFunctionGenerator.kt @@ -0,0 +1,46 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.ToLongBiFunction + +/** + * Produces values of type [ToLongBiFunction]. + * + * @param type of first parameter of produced function + * @param type of second parameter of produced function + */ +class ToLongBiFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(ToLongBiFunction::class.java) { + private var generator: Generator? = null + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun provide(provided: Generators) { + super.provide(provided) + generator = gen()!!.type(Long::class.javaPrimitiveType!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + ToLongBiFunction::class.java, generator!!, status + ), classIdForType(ToLongBiFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 2 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToLongFunctionGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToLongFunctionGenerator.kt new file mode 100644 index 0000000000..7bfb920203 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/ToLongFunctionGenerator.kt @@ -0,0 +1,45 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.ToLongFunction + +/** + * Produces values of type [ToLongFunction]. + * + * @param type of parameter of produced function + */ +class ToLongFunctionGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(ToLongFunction::class.java) { + private var generator: Generator? = null + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun provide(provided: Generators) { + super.provide(provided) + generator = gen()!!.type(Long::class.javaPrimitiveType!!) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + ToLongFunction::class.java, generator!!, status + ), classIdForType(ToLongFunction::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/UnaryOperatorGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/UnaryOperatorGenerator.kt new file mode 100644 index 0000000000..361c405e28 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/generator/java/util/function/UnaryOperatorGenerator.kt @@ -0,0 +1,39 @@ +package org.utbot.greyboxfuzzer.quickcheck.generator.java.util.function + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Lambdas.Companion.makeLambda +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.util.function.UnaryOperator + +/** + * Produces values of type [UnaryOperator]. + * + * @param type of parameter and return type of produced operator + */ +class UnaryOperatorGenerator : org.utbot.greyboxfuzzer.quickcheck.generator.ComponentizedGenerator(UnaryOperator::class.java) { + + override fun createModifiedUtModel(random: SourceOfRandomness, status: GenerationStatus): UtModel { + return generate(random, status) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + makeLambda( + UnaryOperator::class.java, + componentGenerators()[0], + status + ), classIdForType(UnaryOperator::class.java) + ) + } + + override fun numberOfNeededComponents(): Int { + return 1 + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/DefaultMethodHandleMaker.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/DefaultMethodHandleMaker.kt new file mode 100644 index 0000000000..6b4da66459 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/DefaultMethodHandleMaker.kt @@ -0,0 +1,48 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType +import java.lang.reflect.Constructor +import java.lang.reflect.Method + +class DefaultMethodHandleMaker { + fun handleForSpecialMethod(method: Method): MethodHandle { + return if (Reflection.jdk9OrBetter()) jdk9OrBetterMethodHandle(method) else jdk8MethodHandleForDefault(method) + } + + private fun jdk9OrBetterMethodHandle(method: Method): MethodHandle { + return MethodHandles.lookup() + .findSpecial( + method.declaringClass, + method.name, + MethodType.methodType( + method.returnType, + method.parameterTypes + ), + method.declaringClass + ) + } + + private fun jdk8MethodHandleForDefault(method: Method): MethodHandle { + val lookup = methodLookupCtorJDK8()!! + .newInstance( + method.declaringClass, + MethodHandles.Lookup.PRIVATE + ) + return lookup.unreflectSpecial(method, method.declaringClass) + } + + companion object { + @Volatile + private var methodLookupCtorJDK8: Constructor? = null + private fun methodLookupCtorJDK8(): Constructor? { + if (methodLookupCtorJDK8 == null) { + methodLookupCtorJDK8 = Reflection.findDeclaredConstructor( + MethodHandles.Lookup::class.java, Class::class.java, Int::class.javaPrimitiveType + ) + } + return methodLookupCtorJDK8 + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/FakeAnnotatedTypeFactory.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/FakeAnnotatedTypeFactory.kt new file mode 100644 index 0000000000..c5f9eef9df --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/FakeAnnotatedTypeFactory.kt @@ -0,0 +1,73 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +import java.lang.reflect.AnnotatedArrayType +import java.lang.reflect.AnnotatedType +import java.lang.reflect.Type + +object FakeAnnotatedTypeFactory { + + private class FakeAnnotatedArrayType(private val type: Class<*>) : AnnotatedArrayType { + override fun getAnnotatedGenericComponentType(): AnnotatedType { + return makeFrom(type.componentType) + } + + // Not introduced until JDK 9 -- not marking as... + // @Override + override fun getAnnotatedOwnerType(): AnnotatedType? { + return null + } + + override fun getType(): Type { + return type + } + + override fun getAnnotation( + annotationClass: Class + ): T? { + return null + } + + override fun getAnnotations(): Array { + return arrayOfNulls(0) + } + + override fun getDeclaredAnnotations(): Array { + return arrayOfNulls(0) + } + } + + private class FakeAnnotatedType(private val type: Class<*>) : AnnotatedType { + override fun getType(): Type { + return type + } + + override fun getAnnotation( + annotationClass: Class + ): T? { + return null + } + + override fun getAnnotations(): Array { + return arrayOfNulls(0) + } + + override fun getDeclaredAnnotations(): Array { + return arrayOfNulls(0) + } + } + + + @JvmStatic + fun makeFrom(clazz: Class<*>): AnnotatedType { + return if (clazz.isArray) makeArrayType(clazz) else makePlainType(clazz) + } + + private fun makeArrayType(type: Class<*>): AnnotatedArrayType { + return FakeAnnotatedArrayType(type) + } + + private fun makePlainType(type: Class<*>): AnnotatedType { + return FakeAnnotatedType(type) + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/FakeAnnotatedTypeFactoryWithType.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/FakeAnnotatedTypeFactoryWithType.kt new file mode 100644 index 0000000000..fc56e6eed7 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/FakeAnnotatedTypeFactoryWithType.kt @@ -0,0 +1,92 @@ +/* + The MIT License + + Copyright (c) 2010-2021 Paul R. Holser, Jr. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +package org.utbot.greyboxfuzzer.quickcheck.internal + +import java.lang.reflect.AnnotatedArrayType +import java.lang.reflect.AnnotatedType +import java.lang.reflect.Type + +object FakeAnnotatedTypeFactoryWithType { + + + private class FakeAnnotatedArrayType internal constructor(private val type: Type) : AnnotatedArrayType { + override fun getAnnotatedGenericComponentType(): AnnotatedType { + return makeFrom(type.javaClass.componentType) + } + + // Not introduced until JDK 9 -- not marking as... + // @Override + override fun getAnnotatedOwnerType(): AnnotatedType? { + return null + } + + override fun getType(): Type { + return type + } + + override fun getAnnotation(annotationClass: Class): T? { + return null + } + + override fun getAnnotations(): Array { + return arrayOfNulls(0) + } + + override fun getDeclaredAnnotations(): Array { + return arrayOfNulls(0) + } + } + + private class FakeAnnotatedType internal constructor(private val type: Type) : AnnotatedType { + override fun getType(): Type { + return type + } + + override fun getAnnotation(annotationClass: Class): T? { + return null + } + + override fun getAnnotations(): Array { + return arrayOfNulls(0) + } + + override fun getDeclaredAnnotations(): Array { + return arrayOfNulls(0) + } + } + + fun makeFrom(type: Type): AnnotatedType { + return if (type.javaClass.isArray) makeArrayType(type) else makePlainType(type) + } + + private fun makeArrayType(type: Type): AnnotatedArrayType { + return FakeAnnotatedArrayType(type) + } + + private fun makePlainType(type: Type): AnnotatedType { + return FakeAnnotatedType(type) + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/GeometricDistribution.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/GeometricDistribution.kt new file mode 100644 index 0000000000..c571b250ef --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/GeometricDistribution.kt @@ -0,0 +1,27 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import kotlin.math.ceil +import kotlin.math.ln + +class GeometricDistribution { + fun sampleWithMean(mean: Double, random: SourceOfRandomness): Int { + return sample(probabilityOfMean(mean), random) + } + + fun sample(p: Double, random: SourceOfRandomness): Int { + ensureProbability(p) + if (p == 1.0) return 0 + val uniform = random.nextDouble() + return ceil(ln(1 - uniform) / ln(1 - p)).toInt() + } + + fun probabilityOfMean(mean: Double): Double { + require(mean > 0) { "Need a positive mean, got $mean" } + return 1 / mean + } + + private fun ensureProbability(p: Double) { + require(!(p <= 0 || p > 1)) { "Need a probability in (0, 1], got $p" } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Items.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Items.kt new file mode 100644 index 0000000000..995fd28877 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Items.kt @@ -0,0 +1,34 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +object Items { + @JvmStatic + fun choose(items: Collection, random: SourceOfRandomness): T { + val size = items.size + require(size != 0) { "Collection is empty, can't pick an element from it" } + if (items is RandomAccess && items is List<*>) { + val list = items as List + return if (size == 1) list[0] else list[random.nextInt(size)] + } + if (size == 1) { + return items.iterator().next() + } + return items.toList()[random.nextInt(items.size)] + } + + fun chooseWeighted( + items: Collection>, + random: SourceOfRandomness + ): T { + if (items.size == 1) return items.iterator().next().item + val range = items.stream().mapToInt { i: Weighted -> i.weight }.sum() + val sample = random.nextInt(range) + var threshold = 0 + for (each in items) { + threshold += each.weight + if (sample < threshold) return each.item + } + throw AssertionError(String.format("sample = %d, range = %d", sample, range)) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/ParameterTypeContext.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/ParameterTypeContext.kt new file mode 100644 index 0000000000..7e372e3063 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/ParameterTypeContext.kt @@ -0,0 +1,393 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +import org.javaruntype.type.* +import org.javaruntype.type.Type +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.internal.FakeAnnotatedTypeFactory.makeFrom +import org.utbot.greyboxfuzzer.quickcheck.internal.Items.choose +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import ru.vyarus.java.generics.resolver.GenericsResolver +import ru.vyarus.java.generics.resolver.context.GenericsContext +import ru.vyarus.java.generics.resolver.context.MethodGenericsContext +import java.lang.reflect.* + +class ParameterTypeContext( + private val parameterName: String, + private val parameterType: AnnotatedType, + internal val declarerName: String, + val resolved: Type<*>, + val generics: GenericsContext, + private val parameterIndex: Int = -1 +) { + private val explicits = mutableListOf>() + private var annotatedElement: AnnotatedElement? = null + private var allowMixedTypes = false + + fun annotate(element: AnnotatedElement?): ParameterTypeContext { + annotatedElement = element + return this + } + + fun allowMixedTypes(value: Boolean): ParameterTypeContext { + allowMixedTypes = value + return this + } + + fun allowMixedTypes(): Boolean { + return allowMixedTypes + } + + /** + * Gives a context for generation of the return type of a lambda method. + * + * @param method method whose return type we want to resolve + * @return an associated parameter context + */ + fun methodReturnTypeContext(method: Method): ParameterTypeContext { + check(generics is MethodGenericsContext) { "invoking methodReturnTypeContext in present of $generics" } + val argMethodGenerics = generics.parameterType(parameterIndex).method(method) + return ParameterTypeContext( + "return value", + method.annotatedReturnType, + method.name, + Types.forJavaLangReflectType(argMethodGenerics.resolveReturnType()), + argMethodGenerics + ) + } + + private fun makeGenerator( + generatorType: Class + ): Generator { + val ctor = try { + // for Ctor/Fields + Reflection.findConstructor(generatorType, Class::class.java) + } catch (ex: ReflectionException) { + return Reflection.instantiate(generatorType) + } + return Reflection.instantiate(ctor, rawParameterType()) + } + + private fun rawParameterType(): Class<*> { + return when { + type() is ParameterizedType -> resolved.rawClass + type() is TypeVariable<*> -> resolved.rawClass + else -> type() as Class<*> + } + } + + fun name(): String { + return "$declarerName:$parameterName" + } + + fun annotatedType(): AnnotatedType { + return parameterType + } + + fun type(): java.lang.reflect.Type { + return parameterType.type + } + + /** + * @see [ + * this issue](https://github.com/pholser/junit-quickcheck/issues/77) + * + * @return the annotated program element this context represents + */ + @Deprecated( + """This will likely go away when languages whose compilers + and interpreters produce class files that support annotations on type + uses. + """ + ) + fun annotatedElement(): AnnotatedElement? { + return annotatedElement + } + + /** + * @see [ + * this issue](https://github.com/pholser/junit-quickcheck/issues/77) + * + * @return the annotated program element this context represents + */ + @Deprecated( + """This will likely go away when languages whose compilers + and interpreters produce class files that support annotations on type + uses. + """ + ) + fun topLevel(): Boolean { + return (annotatedElement is Parameter || annotatedElement is Field) + } + + fun explicitGenerators(): List> { + return explicits + } + + private fun addParameterTypeContextToDeque(deque: ArrayDeque, ptx: ParameterTypeContext) { + if (ptx.resolved.name == Zilch::class.java.name) return + deque.add(ptx) + } + + fun getAllSubParameterTypeContexts(sourceOfRandomness: SourceOfRandomness?): List { + val res = mutableListOf(this) + val deque = ArrayDeque() + if (isArray) { + addParameterTypeContextToDeque(deque, arrayComponentContext()) + } + typeParameterContexts(sourceOfRandomness).forEach { ptx: ParameterTypeContext -> + addParameterTypeContextToDeque(deque, ptx) + } + while (!deque.isEmpty()) { + val ptx = deque.removeFirst() + res.add(ptx) + if (ptx.isArray) { + addParameterTypeContextToDeque(deque, ptx.arrayComponentContext()) + } + ptx.typeParameterContexts(sourceOfRandomness).forEach { ptxNested: ParameterTypeContext -> + addParameterTypeContextToDeque(deque, ptxNested) + } + } + return res + } + + fun arrayComponentContext(): ParameterTypeContext { + val component = Types.arrayComponentOf(resolved as Type>) + val annotatedComponent = annotatedArrayComponent(component) + return ParameterTypeContext( + annotatedComponent.type.typeName, + annotatedComponent, + parameterType.type.typeName, + component, + generics + ).annotate(annotatedComponent).allowMixedTypes(true) + } + + private fun annotatedArrayComponent(component: Type<*>): AnnotatedType { + return if (parameterType is AnnotatedArrayType) { + parameterType.annotatedGenericComponentType + } else { + makeFrom(component.componentClass) + } + } + + val isArray: Boolean + get() = resolved.isArray + val rawClass: Class<*> + get() = resolved.rawClass + val isEnum: Boolean + get() = rawClass.isEnum + val typeParameters: List> + get() = resolved.typeParameters + + fun typeParameterContexts(random: SourceOfRandomness?): List { + val typeParamContexts = mutableListOf() + val typeParameters = typeParameters + val annotatedTypeParameters = Reflection.annotatedComponentTypes(annotatedType()) + for (i in typeParameters.indices) { + val p = typeParameters[i] + val a = if (annotatedTypeParameters.size > i) annotatedTypeParameters[i] else zilch() + when (p) { + is StandardTypeParameter<*> -> addStandardTypeParameterContext(typeParamContexts, p, a) + is WildcardTypeParameter -> addWildcardTypeParameterContext(typeParamContexts, a) + is ExtendsTypeParameter<*> -> addExtendsTypeParameterContext(typeParamContexts, p, a) + else -> { + // must be "? super X" + addSuperTypeParameterContext(random, typeParamContexts, p, a) + } + } + } + return typeParamContexts + } + + private fun addStandardTypeParameterContext( + typeParameterContexts: MutableList, + p: TypeParameter<*>, + a: AnnotatedType + ) { + typeParameterContexts.add( + ParameterTypeContext( + p.type.name, + a, + annotatedType().type.typeName, + p.type, + generics + ).allowMixedTypes(a !is TypeVariable<*>).annotate(a) + ) + } + + private fun addWildcardTypeParameterContext( + typeParameterContexts: MutableList, + a: AnnotatedType + ) { + typeParameterContexts.add( + ParameterTypeContext( + "Zilch", + a, + annotatedType().type.typeName, + Types.forJavaLangReflectType(Zilch::class.java), + GenericsResolver.resolve(Zilch::class.java) + ).allowMixedTypes(true).annotate(a) + ) + } + + private fun addExtendsTypeParameterContext( + typeParameterContexts: MutableList, + p: TypeParameter<*>, + a: AnnotatedType + ) { + typeParameterContexts.add( + ParameterTypeContext( + p.type.name, + Reflection.annotatedComponentTypes(a)[0], + annotatedType().type.typeName, + p.type, + generics + ).allowMixedTypes(false).annotate(a) + ) + } + + private fun addSuperTypeParameterContext( + random: SourceOfRandomness?, + typeParameterContexts: MutableList, + p: TypeParameter<*>, + a: AnnotatedType + ) { + val supertypes = Reflection.supertypes(p.type) + val choice = choose(supertypes, random!!) + typeParameterContexts.add( + ParameterTypeContext( + p.type.name, + Reflection.annotatedComponentTypes(a)[0], + annotatedType().type.typeName, + choice, + generics + ).allowMixedTypes(false).annotate(a) + ) + } + + companion object { + @Suppress("unused") + @JvmStatic + private val zilch: Zilch = Zilch + + fun forType(type: java.lang.reflect.Type): ParameterTypeContext { + return forType(type, null) + } + + fun forType(type: java.lang.reflect.Type, generics: GenericsContext?): ParameterTypeContext { + val gctx: GenericsContext = generics ?: GenericsResolver.resolve(type.javaClass) + return ParameterTypeContext( + type.typeName, + FakeAnnotatedTypeFactoryWithType.makeFrom(type), + type.typeName, + Types.forJavaLangReflectType(type), + gctx + ) + } + + fun forClass(clazz: Class<*>): ParameterTypeContext { + return ParameterTypeContext( + clazz.typeName, + makeFrom(clazz), + clazz.typeName, + Types.forJavaLangReflectType(clazz), + GenericsResolver.resolve(clazz) + ) + } + + fun forField(field: Field): ParameterTypeContext { + val generics = GenericsResolver.resolve(field.declaringClass) + return ParameterTypeContext( + field.name, + field.annotatedType, + field.declaringClass.name, + Types.forJavaLangReflectType(generics.resolveFieldType(field)), + generics + ) + } + fun forField(field: Field, generics: GenericsContext): ParameterTypeContext { + return ParameterTypeContext( + field.name, + field.annotatedType, + field.declaringClass.name, + Types.forJavaLangReflectType(generics.resolveFieldType(field)), + generics + ) + } + fun forParameter(parameter: Parameter): ParameterTypeContext { + val exec = parameter.declaringExecutable + val clazz = exec.declaringClass + val declarerName = clazz.name + '.' + exec.name + val parameterIndex = parameterIndex(exec, parameter) + val generics: GenericsContext + val resolved: Type<*> + when (exec) { + is Method -> { + val methodGenerics = GenericsResolver.resolve(clazz).method(exec) + resolved = Types.forJavaLangReflectType( + methodGenerics.resolveParameterType(parameterIndex) + ) + generics = methodGenerics + } + + is Constructor<*> -> { + val constructorGenerics = GenericsResolver.resolve(clazz).constructor(exec) + resolved = Types.forJavaLangReflectType( + constructorGenerics.resolveParameterType(parameterIndex) + ) + generics = constructorGenerics + } + + else -> { + throw IllegalStateException("Unrecognized subtype of Executable") + } + } + return ParameterTypeContext( + parameter.name, + parameter.annotatedType, + declarerName, + resolved, + generics, + parameterIndex + ) + } + + fun forParameter( + parameter: Parameter, + generics: MethodGenericsContext + ): ParameterTypeContext { + val exec = parameter.declaringExecutable + val clazz = exec.declaringClass + val declarerName = clazz.name + '.' + exec.name + val parameterIndex = parameterIndex(exec, parameter) + return ParameterTypeContext( + parameter.name, + parameter.annotatedType, + declarerName, + Types.forJavaLangReflectType( + generics.resolveParameterType(parameterIndex) + ), + generics, + parameterIndex + ) + } + + private fun parameterIndex(exec: Executable, parameter: Parameter): Int { + val parameters = exec.parameters + for (i in parameters.indices) { + if (parameters[i] == parameter) return i + } + throw IllegalStateException( + "Cannot find parameter $parameter on $exec" + ) + } + + private fun zilch(): AnnotatedType { + return try { + ParameterTypeContext::class.java.getDeclaredField("zilch").annotatedType + } catch (e: NoSuchFieldException) { + throw AssertionError(e) + } + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Ranges.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Ranges.kt new file mode 100644 index 0000000000..bbc5c1f1e1 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Ranges.kt @@ -0,0 +1,80 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.math.BigInteger + +object Ranges { + enum class Type(val pattern: String) { + CHARACTER("c"), INTEGRAL("d"), FLOAT("f"), STRING("s"); + } + + fun ?> checkRange(type: Type, min: T, max: T): Int { + val comparison = min!!.compareTo(max) + require(comparison <= 0) { + String.format( + "bad range, %" + type.pattern + " > %" + type.pattern, + min, + max + ) + } + return comparison + } + + fun choose( + random: SourceOfRandomness, + min: BigInteger?, + max: BigInteger + ): BigInteger { + val range = max.subtract(min).add(BigInteger.ONE) + var generated: BigInteger + do { + generated = random.nextBigInteger(range.bitLength()) + } while (generated.compareTo(range) >= 0) + return generated.add(min) + } + + fun choose(random: SourceOfRandomness, min: Long, max: Long): Long { + checkRange(Type.INTEGRAL, min, max) + + /* There are some edges cases with integer overflows, for instance, + when (max - min) exceeds Long.MAX_VALUE. These cases should be + relatively rare under the assumption that choosing + [Long.MIN_VALUE, Long.MAX_VALUE] can be simplified to choosing any + random long. Thus, the optimization here only deals with the common + situation that no overflows are possible (maybe the heuristic to + detect that could be improved). + */ + val noOverflowIssues = max < 1L shl 62 && min > -(1L shl 62) + return if (noOverflowIssues) { + // fast path: use long computations + val range = max - min + 1 + val mask = findNextPowerOfTwoLong(range) - 1 + + // loop to avoid distribution bias (as would be the case + // with modulo division) + var generated: Long + do { + generated = Math.abs(random.nextLong()) and mask + } while (generated >= range) + generated + min + } else { + // slow path: fall back to BigInteger to avoid any surprises + choose( + random, + BigInteger.valueOf(min), + BigInteger.valueOf(max) + ) + .toLong() + } + } + + fun findNextPowerOfTwoLong(positiveLong: Long): Long { + return if (isPowerOfTwoLong(positiveLong)) positiveLong else 1L shl 64 - java.lang.Long.numberOfLeadingZeros( + positiveLong + ) + } + + private fun isPowerOfTwoLong(positiveLong: Long): Boolean { + return positiveLong and positiveLong - 1 == 0L + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Reflection.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Reflection.kt new file mode 100644 index 0000000000..44b8010a1e --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Reflection.kt @@ -0,0 +1,274 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +import org.javaruntype.type.Type +import java.lang.reflect.AnnotatedArrayType +import java.lang.reflect.AnnotatedElement +import java.lang.reflect.AnnotatedParameterizedType +import java.lang.reflect.AnnotatedType +import java.lang.reflect.AnnotatedWildcardType +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import java.security.AccessController +import java.security.PrivilegedAction +import java.util.Arrays +import java.lang.annotation.Annotation as JavaAnnotation + +object Reflection { + private val PRIMITIVES = HashMap, Class<*>>(16) + + init { + PRIMITIVES[java.lang.Boolean.TYPE] = Boolean::class.java + PRIMITIVES[java.lang.Byte.TYPE] = Byte::class.java + PRIMITIVES[Character.TYPE] = Char::class.java + PRIMITIVES[java.lang.Double.TYPE] = Double::class.java + PRIMITIVES[java.lang.Float.TYPE] = Float::class.java + PRIMITIVES[Integer.TYPE] = Int::class.java + PRIMITIVES[java.lang.Long.TYPE] = Long::class.java + PRIMITIVES[java.lang.Short.TYPE] = Short::class.java + } + + fun maybeWrap(clazz: Class<*>): Class<*> { + val wrapped = PRIMITIVES[clazz] + return wrapped ?: clazz + } + + fun findConstructor( + type: Class, + vararg parameterTypes: Class<*>? + ): Constructor { + return try { + type.getConstructor(*parameterTypes) + } catch (ex: Exception) { + throw reflectionException(ex) + } + } + + fun findDeclaredConstructor( + type: Class, + vararg parameterTypes: Class<*>? + ): Constructor { + return try { + val ctor = type.getDeclaredConstructor(*parameterTypes) + ctor.isAccessible = true + ctor + } catch (ex: Exception) { + throw reflectionException(ex) + } + } + + fun singleAccessibleConstructor( + type: Class + ): Constructor { + val constructors = type.constructors + if (constructors.size != 1) { + throw ReflectionException( + "$type needs a single accessible constructor" + ) + } + return constructors[0] as Constructor + } + + fun instantiate(clazz: Class): T { + return try { + clazz.newInstance() + } catch (ex: Exception) { + throw reflectionException(ex) + } + } + + fun instantiate(ctor: Constructor, vararg args: Any?): T { + return try { + ctor.newInstance(*args) + } catch (ex: Exception) { + throw reflectionException(ex) + } + } + + fun supertypes(bottom: Type<*>): Set> { + val supertypes: MutableSet> = HashSet() + supertypes.add(bottom) + supertypes.addAll(bottom.allTypesAssignableFromThis) + return supertypes + } + + fun defaultValueOf( + annotationType: Class, + attribute: String + ): Any { + return try { + annotationType.getMethod(attribute).defaultValue + } catch (ex: Exception) { + throw reflectionException(ex) + } + } + + fun allAnnotations(e: AnnotatedElement): List { + val thisAnnotations = nonSystemAnnotations(e) + val annotations = ArrayList() + for (each in thisAnnotations) { + annotations.add(each) + annotations.addAll(allAnnotations(each.annotationType())) + } + return annotations + } + + fun allAnnotationsByType( + e: AnnotatedElement, + type: Class? + ): List { + val annotations = ArrayList(e.getAnnotationsByType(type).toList()) + val thisAnnotations = nonSystemAnnotations(e) + for (each in thisAnnotations) { + annotations.addAll(allAnnotationsByType(each.annotationType(), type)) + } + return annotations + } + + fun findMethod( + target: Class<*>, + methodName: String, + vararg argTypes: Class<*>? + ): Method { + return try { + target.getMethod(methodName, *argTypes) + } catch (ex: Exception) { + throw reflectionException(ex) + } + } + + operator fun invoke(method: Method, target: Any?, vararg args: Any?): Any { + return try { + method.invoke(target, *args) + } catch (ex: Exception) { + throw reflectionException(ex) + } + } + + fun findField(type: Class<*>, fieldName: String): Field { + return try { + type.getDeclaredField(fieldName) + } catch (ex: NoSuchFieldException) { + throw reflectionException(ex) + } + } + + fun allDeclaredFieldsOf(type: Class<*>?): List { + val allFields = ArrayList() + var c = type + while (c != null) { + allFields.addAll(c.declaredFields) + c = c.superclass + } + val results = allFields.filter { !it.isSynthetic } + results.forEach { it.isAccessible = true } + return results + } + + fun setField( + field: Field, + target: Any?, + value: Any?, + suppressProtection: Boolean + ) { + AccessController.doPrivileged(PrivilegedAction { + field.isAccessible = suppressProtection + null + }) + try { + field[target] = value + } catch (ex: Exception) { + throw reflectionException(ex) + } + } + + fun jdk9OrBetter(): Boolean { + return try { + Runtime::class.java.getMethod("version") + true + } catch (e: NoSuchMethodException) { + false + } + } + + fun singleAbstractMethodOf(rawClass: Class<*>): Method? { + if (!rawClass.isInterface) return null + var abstractCount = 0 + var singleAbstractMethod: Method? = null + for (each in rawClass.methods) { + if (Modifier.isAbstract(each.modifiers) + && !overridesJavaLangObjectMethod(each) + ) { + singleAbstractMethod = each + ++abstractCount + } + } + return if (abstractCount == 1) singleAbstractMethod else null + } + + fun isMarkerInterface(clazz: Class<*>): Boolean { + return if (!clazz.isInterface) false else Arrays.stream(clazz.methods) + .filter { m: Method -> !m.isDefault } + .allMatch { method: Method -> overridesJavaLangObjectMethod(method) } + } + + private fun overridesJavaLangObjectMethod(method: Method): Boolean { + return isEquals(method) || isHashCode(method) || isToString(method) + } + + private fun isEquals(method: Method): Boolean { + return "equals" == method.name && method.parameterTypes.size == 1 && Any::class.java == method.parameterTypes[0] + } + + private fun isHashCode(method: Method): Boolean { + return "hashCode" == method.name && method.parameterTypes.size == 0 + } + + private fun isToString(method: Method): Boolean { + return "toString" == method.name && method.parameterTypes.size == 0 + } + + fun reflectionException(ex: Exception?): RuntimeException { + if (ex is InvocationTargetException) { + return ReflectionException( + ex.targetException + ) + } + return if (ex is RuntimeException) ex else ReflectionException( + ex!! + ) + } + + private fun nonSystemAnnotations(e: AnnotatedElement): List { + return e.annotations.map { it as JavaAnnotation } + .filter { !it.annotationType().name.startsWith("java.lang.annotation.") } + .filter { !it.annotationType().name.startsWith("kotlin.") } + } + + fun annotatedComponentTypes( + annotatedType: AnnotatedType? + ): List { + if (annotatedType is AnnotatedParameterizedType) { + return Arrays.asList( + *annotatedType + .annotatedActualTypeArguments + ) + } + if (annotatedType is AnnotatedArrayType) { + return listOf( + annotatedType + .annotatedGenericComponentType + ) + } + if (annotatedType is AnnotatedWildcardType) { + val wildcard = annotatedType + return if (wildcard.annotatedLowerBounds.size > 0) listOf(wildcard.annotatedLowerBounds[0]) else Arrays.asList( + *wildcard.annotatedUpperBounds + ) + } + return emptyList() + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/ReflectionException.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/ReflectionException.kt new file mode 100644 index 0000000000..da1e3f164a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/ReflectionException.kt @@ -0,0 +1,6 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +class ReflectionException : RuntimeException { + constructor(message: String) : super(message) + constructor(cause: Throwable) : super(cause.toString()) +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Weighted.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Weighted.kt new file mode 100644 index 0000000000..bf87843ae5 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Weighted.kt @@ -0,0 +1,12 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +class Weighted(item: T, weight: Int) { + val item: T + val weight: Int + + init { + require(weight > 0) { "non-positive weight: $weight" } + this.item = item + this.weight = weight + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Zilch.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Zilch.kt new file mode 100644 index 0000000000..84ee41ba05 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/Zilch.kt @@ -0,0 +1,3 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal + +object Zilch diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/AbstractGenerationStatus.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/AbstractGenerationStatus.kt new file mode 100644 index 0000000000..bfd8e5dadf --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/AbstractGenerationStatus.kt @@ -0,0 +1,18 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.internal.GeometricDistribution +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +abstract class AbstractGenerationStatus( + private val distro: GeometricDistribution, + private val random: SourceOfRandomness +) : GenerationStatus { + override fun size(): Int { + return distro.sampleWithMean((attempts() + 1).toDouble(), random) + } + + protected fun random(): SourceOfRandomness { + return random + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/ArrayGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/ArrayGenerator.kt new file mode 100644 index 0000000000..d4ccb88813 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/ArrayGenerator.kt @@ -0,0 +1,169 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.utbot.greyboxfuzzer.generator.GeneratorConfigurator +import org.utbot.greyboxfuzzer.util.FuzzerIllegalStateException +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.getIdOrThrow +import org.utbot.framework.plugin.api.util.* +import org.utbot.greyboxfuzzer.quickcheck.generator.* +import org.utbot.greyboxfuzzer.quickcheck.internal.Ranges +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.lang.reflect.AnnotatedType + +class ArrayGenerator(private val componentType: Class<*>, val component: Generator) : Generator(Any::class.java) { + private var lengthRange: Size? = null + private var distinct = false + + /** + * Tells this generator to produce values with a length within a specified + * minimum and/or maximum, inclusive, chosen with uniform distribution. + * + * @param size annotation that gives the length constraints + */ + fun configure(size: Size) { + lengthRange = size + Ranges.checkRange(Ranges.Type.INTEGRAL, size.min, size.max) + } + + /** + * Tells this generator to produce values which are distinct from each + * other. + * + * @param distinct Generated values will be distinct if this param is not + * null. + */ + fun configure(distinct: Distinct?) { + this.distinct = distinct != null + } + + private fun modify( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val cachedModel = generatedUtModel ?: throw FuzzerIllegalStateException("Nothing to modify") + val randomNestedGenerator = nestedGeneratorsRecursiveWithoutThis().randomOrNull() ?: return cachedModel + getAllGeneratorsBetween(this, randomNestedGenerator)?.forEach { + it.generationState = GenerationState.MODIFYING_CHAIN + } + randomNestedGenerator.generationState = GenerationState.REGENERATE + return createModifiedUtModel(random, status) + } + + private fun createModifiedUtModel( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val cachedModel = generatedUtModel ?: throw FuzzerIllegalStateException("Nothing to modify") + val length = nestedGenerators.size + val componentTypeId = classIdForType(componentType) + val modelId = cachedModel.getIdOrThrow() + return UtArrayModel( + modelId, + getClassIdForArrayType(componentType), + length, + componentTypeId.defaultValueModel(), + (0 until length).associateWithTo(hashMapOf()) { ind -> + val generator = nestedGenerators[ind] + val item = generator.generateImpl(random, status) + generator.generationState = GenerationState.CACHE + item + } + ) + } + + private fun regenerate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + nestedGenerators.clear() + val length = length(random, status) + val componentTypeId = classIdForType(componentType) + val modelId = generatorContext.utModelConstructor.computeUnusedIdAndUpdate() + return UtArrayModel( + modelId, + getClassIdForArrayType(componentType), + length, + componentTypeId.defaultValueModel(), + (0 until length).associateWithTo(hashMapOf()) { + val generator = component.copy() + nestedGenerators.add(generator) + generator.generateImpl(random, status) + } + ) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel = + when (generationState) { + GenerationState.REGENERATE -> regenerate(random, status) + GenerationState.MODIFY -> modify(random, status) + GenerationState.MODIFYING_CHAIN -> createModifiedUtModel(random, status) + GenerationState.CACHE -> generatedUtModel ?: throw FuzzerIllegalStateException("No cached model") + } + +// override fun generate( +// random: SourceOfRandomness, +// status: GenerationStatus +// ): UtModel { +// return if (generationState == GenerationState.MODIFY) { +// modify(random, status) +// } else { +// regenerate(random, status) +// } +// } + + private fun getClassIdForArrayType(componentType: Class<*>): ClassId = when (componentType) { + Int::class.javaPrimitiveType -> intArrayClassId + Boolean::class.javaPrimitiveType -> booleanArrayClassId + Byte::class.javaPrimitiveType -> byteArrayClassId + Char::class.javaPrimitiveType -> charArrayClassId + Double::class.javaPrimitiveType -> doubleArrayClassId + Float::class.javaPrimitiveType -> floatArrayClassId + Long::class.javaPrimitiveType -> longArrayClassId + Short::class.javaPrimitiveType -> shortArrayClassId + else -> + if (componentType.isArray) { + val arrayComponentType = getClassIdForArrayType(componentType.componentType) + ClassId("[${arrayComponentType.name}", arrayComponentType) + } else { + ClassId("[L${classIdForType(componentType)};", classIdForType(componentType)) + } + } + + override fun provide(provided: Generators) { + super.provide(provided) + component.provide(provided) + } + + override fun configure(annotatedType: AnnotatedType?) { + super.configure(annotatedType) + val annotated = Reflection.annotatedComponentTypes(annotatedType) + if (annotated.isNotEmpty()) { + component.configure(annotated[0]) + } + } + + private fun length(random: SourceOfRandomness, status: GenerationStatus): Int { + return if (lengthRange != null) random.nextInt(lengthRange!!.min, lengthRange!!.max) else status.size() + } + + override fun copy(): Generator { + val gen = Reflection.instantiate(ArrayGenerator::class.java.constructors.first(), componentType, component.copy()) as ArrayGenerator + return gen.also { + it.generatedUtModel = generatedUtModel + it.generationState = generationState + if (isGeneratorContextInitialized()) { + it.generatorContext = generatorContext + } + it.nestedGenerators = nestedGenerators.map { it.copy() }.toMutableList() + GeneratorConfigurator.configureGenerator(it, 100) + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/CompositeGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/CompositeGenerator.kt new file mode 100644 index 0000000000..d5ce796982 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/CompositeGenerator.kt @@ -0,0 +1,143 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.utbot.greyboxfuzzer.generator.GeneratorConfigurator +import org.utbot.greyboxfuzzer.util.FuzzerIllegalStateException +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.* +import org.utbot.greyboxfuzzer.quickcheck.internal.Items +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.internal.Weighted +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.lang.reflect.AnnotatedElement +import java.lang.reflect.AnnotatedType + +class CompositeGenerator(composed: List>) : Generator(Any::class.java) { + val composed: MutableList> + private var previousChosenGenerator: Generator? = null + + init { + this.composed = ArrayList(composed) + } + + private fun regenerate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val choice = Items.chooseWeighted(composed, random) + previousChosenGenerator = choice + return choice.generateImpl(random, status) + } + + private fun modify( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + previousChosenGenerator ?: throw FuzzerIllegalStateException("Nothing to modify") + return previousChosenGenerator!!.generateImpl(random, status) + } + + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return if (generationState == GenerationState.MODIFY) { + modify(random, status) + } else { + regenerate(random, status) + } + } + + fun composed(index: Int): Generator { + return composed[index].item + } + + fun numberOfComposedGenerators(): Int { + return composed.size + } + + override fun provide(provided: Generators) { + super.provide(provided) + for (each in composed) each.item.provide(provided) + } + + override fun configure(annotatedType: AnnotatedType?) { + val candidates = composed.mapNotNull { + try { + it.item.configure(annotatedType) + it + } catch (e: GeneratorConfigurationException) { + null + } + } + installCandidates(candidates, annotatedType) + } + + override fun configure(element: AnnotatedElement?) { + val candidates = composed.mapNotNull { + try { + it.item.configure(element) + it + } catch (e: GeneratorConfigurationException) { + null + } + } + installCandidates(candidates, element) + } + + override fun addComponentGenerators(newComponents: List) { + for (each in composed) { + each.item.addComponentGenerators(newComponents) + } + } + + private fun installCandidates( + candidates: List>, + element: AnnotatedElement? + ) { + if (element == null) return + if (candidates.isEmpty()) { + throw GeneratorConfigurationException( + String.format( + "None of the candidate generators %s" + + " understands all of the configuration annotations %s", + candidateGeneratorDescriptions(), + configurationAnnotationNames(element) + ) + ) + } + composed.clear() + composed.addAll(candidates) + } + + private fun candidateGeneratorDescriptions(): String { + return composed.joinToString { it.item.javaClass.name } + } + +// override fun copy(): Generator { +// return (super.copy() as CompositeGenerator).also { +// it.previousChosenGenerator = previousChosenGenerator +// it.composed.addAll(composed) +// } +// } + + override fun copy(): Generator { + val composedCopies = composed.map { Weighted(it.item.copy(), it.weight) }.toMutableList() + val gen = Reflection.instantiate(CompositeGenerator::class.java.constructors.first(), composedCopies) as CompositeGenerator + return gen.also { + it.generatedUtModel = generatedUtModel + it.generationState = generationState + if (isGeneratorContextInitialized()) { + it.generatorContext = generatorContext + } + it.nestedGenerators = nestedGenerators.map { it.copy() }.toMutableList() + GeneratorConfigurator.configureGenerator(it, 100) + } + } + + companion object { + private fun configurationAnnotationNames( + element: AnnotatedElement + ): List = configurationAnnotationsOn(element) + .map { a -> a.annotationType().name } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/EnumGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/EnumGenerator.kt new file mode 100644 index 0000000000..320366933e --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/EnumGenerator.kt @@ -0,0 +1,31 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.utbot.greyboxfuzzer.generator.GeneratorConfigurator +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +class EnumGenerator(private val enumType: Class<*>) : Generator(Enum::class.java) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + val values = enumType.enumConstants + val index = random.nextInt(0, values.size - 1) + return generatorContext.utModelConstructor.construct(values[index], Enum::class.id) + } + + override fun copy(): Generator { + return EnumGenerator(enumType).also { + it.generatedUtModel = generatedUtModel + it.generationState = generationState + it.nestedGenerators = nestedGenerators.map { it.copy() }.toMutableList() + if (isGeneratorContextInitialized()) { + it.generatorContext = generatorContext + } + GeneratorConfigurator.configureGenerator(it, 95) + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/GeneratorRepository.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/GeneratorRepository.kt new file mode 100644 index 0000000000..4adb6b8ff4 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/GeneratorRepository.kt @@ -0,0 +1,285 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.NullAllowed +import org.utbot.greyboxfuzzer.quickcheck.internal.Items.choose +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.internal.Weighted +import org.utbot.greyboxfuzzer.quickcheck.internal.Zilch +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.lang.reflect.AnnotatedElement +import java.lang.reflect.Field +import java.lang.reflect.Parameter +import java.lang.reflect.Type +import java.lang.annotation.Annotation as JavaAnnotation + +open class GeneratorRepository private constructor( + private val random: SourceOfRandomness, + private val generators: MutableMap, MutableSet> +) : Generators { + constructor(random: SourceOfRandomness) : this(random, hashMapOf()) + + fun getGenerators(): Map, MutableSet> { + return generators + } + + fun addUserClassGenerator(forClass: Class<*>, source: Generator) { + generators[forClass] = mutableSetOf(source) + } + + fun removeGenerator(forClass: Class<*>) { + generators.remove(forClass) + } + + fun removeGeneratorForObjectClass() { + generators.remove(Any::class.java) + } + + fun register(source: Generator): GeneratorRepository { + registerTypes(source) + return this + } + + fun register(source: Iterable): GeneratorRepository { + for (each in source) registerTypes(each) + return this + } + + private fun registerTypes(generator: Generator) { + for (each in generator.types()) registerHierarchy(each, generator) + } + + private fun registerHierarchy(type: Class<*>, generator: Generator) { + maybeRegisterGeneratorForType(type, generator) + when { + type.superclass != null -> registerHierarchy(type.superclass, generator) + type.isInterface -> registerHierarchy(Any::class.java, generator) + } + for (each in type.interfaces) registerHierarchy(each, generator) + } + + private fun maybeRegisterGeneratorForType(type: Class<*>, generator: Generator) { + if (generator.canRegisterAsType(type) && type != Zilch::class.java) { + registerGeneratorForType(type, generator) + } + } + + private fun registerGeneratorForType(type: Class<*>, generator: Generator) { + val forType = generators.getOrPut(type) { mutableSetOf() } + forType.add(generator) + } + + override fun type(type: Class, vararg componentTypes: Class<*>): Generator { + val generator = produceGenerator( + ParameterTypeContext.forClass(type) + ) + generator.addComponentGenerators(componentTypes.map { type(it) }) + return generator + } + + override fun parameter(parameter: Parameter): Generator { + return produceGenerator( + ParameterTypeContext.forParameter(parameter).annotate(parameter) + ) + } + + override fun field(field: Field): Generator { + return produceGenerator( + ParameterTypeContext.forField(field).annotate(field) + ) + } + + override fun withRandom(other: SourceOfRandomness): Generators { + return GeneratorRepository(other, generators) + } + + fun produceGenerator(parameter: ParameterTypeContext): Generator { + var generator = generatorFor(parameter) + if (!isPrimitiveType(parameter.annotatedType().type) + && hasNullableAnnotation(parameter.annotatedElement()) + ) { + generator = NullableGenerator(generator) + } + generator.provide(this) + //generator.configure(parameter.annotatedType()) + //if (parameter.topLevel()) generator.configure(parameter.annotatedElement()) + return generator + } + + open fun generatorFor(parameter: ParameterTypeContext): Generator { + return when { + parameter.explicitGenerators().isNotEmpty() -> composeWeighted(parameter, parameter.explicitGenerators()) + parameter.isArray -> generatorForArrayType(parameter) + else -> if (parameter.isEnum) { + EnumGenerator(parameter.rawClass) + } else { + compose(parameter, matchingGenerators(parameter)) + } + } + } + + open fun generatorForArrayType( + parameter: ParameterTypeContext + ): ArrayGenerator { + val component = parameter.arrayComponentContext() + return ArrayGenerator(component.rawClass, generatorFor(component)).copy() as ArrayGenerator + } + + private fun matchingGenerators( + parameter: ParameterTypeContext + ): List { + val matches = mutableListOf() + if (!hasGeneratorsFor(parameter)) { + maybeAddGeneratorByNamingConvention(parameter, matches) + maybeAddLambdaGenerator(parameter, matches) + maybeAddMarkerInterfaceGenerator(parameter, matches) + } else { + maybeAddGeneratorsFor(parameter, matches) + } + require(matches.isNotEmpty()) { + ("Cannot find generator for " + parameter.name() + + " of type " + parameter.type().typeName) + } + return matches + } + + private fun maybeAddGeneratorByNamingConvention( + parameter: ParameterTypeContext, + matches: MutableList + ) { + val genClass = try { + Class.forName(parameter.rawClass.name + "Gen") + } catch (noGenObeyingConvention: ClassNotFoundException) { + return + } + if (Generator::class.java.isAssignableFrom(genClass)) { + try { + val generator = genClass.newInstance() as Generator + if (generator.types().contains(parameter.rawClass)) { + matches += generator + } + } catch (e: IllegalAccessException) { + throw IllegalStateException( + "Cannot instantiate " + genClass.name + + " using default constructor" + ) + } catch (e: InstantiationException) { + throw IllegalStateException( + "Cannot instantiate " + genClass.name + + " using default constructor" + ) + } + } + } + + private fun maybeAddLambdaGenerator( + parameter: ParameterTypeContext, + matches: MutableList + ) { + val method = Reflection.singleAbstractMethodOf(parameter.rawClass) + if (method != null) { + val returnType = parameter.methodReturnTypeContext(method) + val returnTypeGenerator = generatorFor(returnType) + matches += LambdaGenerator(parameter.rawClass, returnTypeGenerator).copy() as LambdaGenerator<*> + } + } + + private fun maybeAddMarkerInterfaceGenerator( + parameter: ParameterTypeContext, + matches: MutableList + ) { + val rawClass = parameter.rawClass + if (Reflection.isMarkerInterface(rawClass)) { + matches += MarkerInterfaceGenerator(parameter.rawClass) + } + } + + private fun maybeAddGeneratorsFor( + parameter: ParameterTypeContext, + matches: MutableList + ) { + val candidates = generatorsFor(parameter) + val typeParameters = parameter.typeParameters + if (typeParameters.isEmpty()) { + matches.addAll(candidates) + } else { + for (each in candidates) { + if (each.canGenerateForParametersOfTypes(typeParameters)) matches.add(each) + } + } + } + + private fun compose( + parameter: ParameterTypeContext, + matches: List + ): Generator { + val weightings = matches.map { Weighted(it, 1) } + return composeWeighted(parameter, weightings) + } + + private fun composeWeighted( + parameter: ParameterTypeContext, + matches: List> + ): Generator { + val forComponents = mutableListOf() + for (c in parameter.typeParameterContexts(random)) forComponents.add(generatorFor(c)) + for (each in matches) applyComponentGenerators(each.item, forComponents) + return if (matches.size == 1) matches[0].item else CompositeGenerator(matches) + } + + private fun applyComponentGenerators( + generator: Generator, + componentGenerators: List + ) { + if (!generator.hasComponents()) return + if (componentGenerators.isEmpty()) { + val substitutes = mutableListOf() + val zilch = generatorFor( + ParameterTypeContext.forClass(Zilch::class.java) + .allowMixedTypes(true) + ) + for (i in 0 until generator.numberOfNeededComponents()) { + substitutes.add(zilch) + } + generator.addComponentGenerators(substitutes) + } else { + generator.addComponentGenerators(componentGenerators) + } + } + + open fun generatorsFor(parameter: ParameterTypeContext): List { + var matches = generators[parameter.rawClass] + ?: error("No generator for type: ${parameter.rawClass}") + if (!parameter.allowMixedTypes()) { + val match = choose(matches, random) + matches = mutableSetOf(match) + } + return matches.map { it.copy() } + } + + private fun hasGeneratorsFor(parameter: ParameterTypeContext): Boolean { + return generators[parameter.rawClass] != null + } + + companion object { + private val NULLABLE_ANNOTATIONS = setOf( + "javax.annotation.Nullable", // JSR-305 + NullAllowed::class.java.canonicalName + ) + + + private fun isPrimitiveType(type: Type): Boolean { + return type is Class<*> && type.isPrimitive + } + + private fun hasNullableAnnotation(element: AnnotatedElement?): Boolean { + if (element == null) return false + return element.annotations.map { it as JavaAnnotation } + .map { it.annotationType() } + .map { it.canonicalName } + .any { NULLABLE_ANNOTATIONS.contains(it) } + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/LambdaGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/LambdaGenerator.kt new file mode 100644 index 0000000000..7096b55e4f --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/LambdaGenerator.kt @@ -0,0 +1,22 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +class LambdaGenerator internal constructor( + private val lambdaType: Class, + private val returnValueGenerator: Generator +) : Generator( + lambdaType +) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return UtNullModel(objectClassId) //makeLambda(lambdaType, returnValueGenerator, status); + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/MarkerInterfaceGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/MarkerInterfaceGenerator.kt new file mode 100644 index 0000000000..9c9bd344fd --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/MarkerInterfaceGenerator.kt @@ -0,0 +1,60 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import org.utbot.greyboxfuzzer.util.classIdForType +import org.utbot.framework.plugin.api.UtModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.internal.DefaultMethodHandleMaker +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method +import java.lang.reflect.Proxy + +class MarkerInterfaceGenerator(private val markerType: Class<*>) : Generator(markerType) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return generatorContext.utModelConstructor.construct( + markerType.cast( + Proxy.newProxyInstance( + markerType.classLoader, arrayOf(markerType), + MarkerInvocationHandler(markerType) + ) + ), + classIdForType(markerType) + ) + } + + private class MarkerInvocationHandler(private val markerType: Class<*>) : InvocationHandler { + private val methodHandleMaker = DefaultMethodHandleMaker() + + override fun invoke(proxy: Any, method: Method, args: Array): Any? { + if (Any::class.java == method.declaringClass) return handleObjectMethod(proxy, method, args) + return if (method.isDefault) handleDefaultMethod(proxy, method, args) else null + } + + private fun handleObjectMethod( + proxy: Any, + method: Method, + args: Array + ): Any { + if ("equals" == method.name) return proxy === args[0] + return if ("hashCode" == method.name) System.identityHashCode(proxy) else handleToString() + } + + private fun handleDefaultMethod( + proxy: Any, + method: Method, + args: Array + ): Any { + val handle = methodHandleMaker.handleForSpecialMethod(method) + return handle.bindTo(proxy).invokeWithArguments(*args) + } + + private fun handleToString(): String { + return "a synthetic instance of $markerType" + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/NullableGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/NullableGenerator.kt new file mode 100644 index 0000000000..cf0bdf72c7 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/NullableGenerator.kt @@ -0,0 +1,75 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.javaruntype.type.TypeParameter +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.generator.Generators +import org.utbot.greyboxfuzzer.quickcheck.generator.NullAllowed +import org.utbot.greyboxfuzzer.quickcheck.internal.Reflection +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness +import org.utbot.greyboxfuzzer.util.classIdForType +import java.lang.reflect.AnnotatedElement +import java.lang.reflect.AnnotatedType +import java.util.Optional + +internal class NullableGenerator(private val delegate: Generator) : Generator(delegate.types()) { + private var probabilityOfNull = Reflection.defaultValueOf(NullAllowed::class.java, "probability") as Double + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return if (random.nextFloat(0f, 1f) < probabilityOfNull) { + UtNullModel(classIdForType(types()[0])) + } else { + delegate.generateImpl(random, status) + } + } + + override fun canRegisterAsType(type: Class<*>): Boolean { + return delegate.canRegisterAsType(type) + } + + override fun hasComponents(): Boolean { + return delegate.hasComponents() + } + + override fun numberOfNeededComponents(): Int { + return delegate.numberOfNeededComponents() + } + + override fun addComponentGenerators( + newComponents: List + ) { + delegate.addComponentGenerators(newComponents) + } + + override fun canGenerateForParametersOfTypes(typeParameters: List>): Boolean { + return delegate.canGenerateForParametersOfTypes(typeParameters) + } + + override fun configure(annotatedType: AnnotatedType?) { + Optional.ofNullable(annotatedType!!.getAnnotation(NullAllowed::class.java)) + .ifPresent { allowed: NullAllowed -> this.configure(allowed) } + delegate.configure(annotatedType) + } + + override fun configure(element: AnnotatedElement?) { + delegate.configure(element) + } + + override fun provide(provided: Generators) { + delegate.provide(provided) + } + + private fun configure(allowed: NullAllowed) { + if (allowed.probability >= 0 && allowed.probability <= 1) { + probabilityOfNull = allowed.probability + } else { + throw IllegalArgumentException( + "NullAllowed probability must be in the range [0, 1]" + ) + } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/SimpleGenerationStatus.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/SimpleGenerationStatus.kt new file mode 100644 index 0000000000..1fc51c9533 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/SimpleGenerationStatus.kt @@ -0,0 +1,14 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.utbot.greyboxfuzzer.quickcheck.internal.GeometricDistribution +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +class SimpleGenerationStatus( + distro: GeometricDistribution, + random: SourceOfRandomness, + private val attempts: Int +) : AbstractGenerationStatus(distro, random) { + override fun attempts(): Int { + return attempts + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/ZilchGenerator.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/ZilchGenerator.kt new file mode 100644 index 0000000000..74cb6ece3e --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/internal/generator/ZilchGenerator.kt @@ -0,0 +1,18 @@ +package org.utbot.greyboxfuzzer.quickcheck.internal.generator + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.greyboxfuzzer.quickcheck.generator.GenerationStatus +import org.utbot.greyboxfuzzer.quickcheck.generator.Generator +import org.utbot.greyboxfuzzer.quickcheck.internal.Zilch +import org.utbot.greyboxfuzzer.quickcheck.random.SourceOfRandomness + +class ZilchGenerator : Generator(Zilch::class.java) { + override fun generate( + random: SourceOfRandomness, + status: GenerationStatus + ): UtModel { + return UtNullModel(objectClassId)// generatorContext.utModelConstructor.construct(Zilch, classIdForType(Zilch::class.java)) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/random/SourceOfRandomness.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/random/SourceOfRandomness.kt new file mode 100644 index 0000000000..9794f47dd8 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/quickcheck/random/SourceOfRandomness.kt @@ -0,0 +1,362 @@ +package org.utbot.greyboxfuzzer.quickcheck.random + +import org.utbot.greyboxfuzzer.quickcheck.internal.Items +import org.utbot.greyboxfuzzer.quickcheck.internal.Ranges +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.math.BigInteger +import java.time.Duration +import java.time.Instant +import java.util.Random +import java.util.concurrent.TimeUnit + +/** + * A source of randomness, fed to + * [generators][org.utbot.quickcheck.generator.Generator] + * so they can produce random values for property parameters. + */ +class SourceOfRandomness(delegate: Random) { + private val delegate: Random + private var seed: Long + + /** + * Makes a new source of randomness. + * + * @param delegate a JDK source of randomness, to which the new instance + * will delegate + */ + init { + seed = delegate.nextLong() + this.delegate = delegate + delegate.setSeed(seed) + } + + /** + * + * Gives a JDK source of randomness, with the same internal state as + * this source of randomness. + * + * @return a JDK "clone" of self + */ + fun toJDKRandom(): Random { + val bytesOut = ByteArrayOutputStream() + try { + ObjectOutputStream(bytesOut).use { objectOut -> objectOut.writeObject(delegate) } + } catch (ex: IOException) { + throw IllegalStateException(ex) + } + val bytesIn = ByteArrayInputStream(bytesOut.toByteArray()) + try { + ObjectInputStream(bytesIn).use { objectIn -> return objectIn.readObject() as Random } + } catch (ex: IOException) { + throw IllegalStateException(ex) + } catch (shouldNeverHappen: ClassNotFoundException) { + throw AssertionError(shouldNeverHappen) + } + } + + /** + * @return a uniformly distributed boolean value + * @see Random.nextBoolean + */ + fun nextBoolean(): Boolean { + return delegate.nextBoolean() + } + + /** + * @param bytes a byte array to fill with random values + * @see Random.nextBytes + */ + fun nextBytes(bytes: ByteArray?) { + delegate.nextBytes(bytes) + } + + /** + * Gives an array of a given length containing random bytes. + * + * @param count the desired length of the random byte array + * @return random bytes + * @see Random.nextBytes + */ + fun nextBytes(count: Int): ByteArray { + val buffer = ByteArray(count) + delegate.nextBytes(buffer) + return buffer + } + + /** + * @return a uniformly distributed random `double` value in the + * interval `[0.0, 1.0)` + * @see Random.nextDouble + */ + fun nextDouble(): Double { + return delegate.nextDouble() + } + + /** + * @return a uniformly distributed random `float` value in the + * interval `[0.0, 1.0)` + * @see Random.nextFloat + */ + fun nextFloat(): Float { + return delegate.nextFloat() + } + + /** + * @return a Gaussian-distributed random double value + * @see Random.nextGaussian + */ + fun nextGaussian(): Double { + return delegate.nextGaussian() + } + + /** + * @return a uniformly distributed random `int` value + * @see Random.nextInt + */ + fun nextInt(): Int { + return delegate.nextInt() + } + + /** + * @param n upper bound + * @return a uniformly distributed random `int` value in the interval + * `[0, n)` + * @see Random.nextInt + */ + fun nextInt(n: Int): Int { + return delegate.nextInt(n) + } + + /** + * @return a uniformly distributed random `long` value + * @see Random.nextLong + */ + fun nextLong(): Long { + return delegate.nextLong() + } + + /** + * @param seed value with which to seed this source of randomness + * @see Random.setSeed + */ + fun setSeed(seed: Long) { + this.seed = seed + delegate.setSeed(seed) + } + + /** + * @return the value used to initially seed this source of randomness + */ + fun seed(): Long { + return seed + } + + /** + * Gives a random `byte` value, uniformly distributed across the + * interval `[min, max]`. + * + * @param min lower bound of the desired interval + * @param max upper bound of the desired interval + * @return a random value + */ + fun nextByte(min: Byte, max: Byte): Byte { + return nextLong(min.toLong(), max.toLong()).toByte() + } + + /** + * Gives a random `char` value, uniformly distributed across the + * interval `[min, max]`. + * + * @param min lower bound of the desired interval + * @param max upper bound of the desired interval + * @return a random value + */ + fun nextChar(min: Char, max: Char): Char { + Ranges.checkRange(Ranges.Type.CHARACTER, min, max) + return Char(nextLong(min.code.toLong(), max.code.toLong()).toUShort()) + } + + /** + * + * Gives a random `double` value in the interval + * `[min, max)`. + * + * + * This naive implementation takes a random `double` value from + * [Random.nextDouble] and scales/shifts the value into the desired + * interval. This may give surprising results for large ranges. + * + * @param min lower bound of the desired interval + * @param max upper bound of the desired interval + * @return a random value + */ + fun nextDouble(min: Double, max: Double): Double { + val comparison = Ranges.checkRange(Ranges.Type.FLOAT, min, max) + return if (comparison == 0) min else min + (max - min) * nextDouble() + } + + /** + * + * Gives a random `float` value in the interval + * `[min, max)`. + * + * + * This naive implementation takes a random `float` value from + * [Random.nextFloat] and scales/shifts the value into the desired + * interval. This may give surprising results for large ranges. + * + * @param min lower bound of the desired interval + * @param max upper bound of the desired interval + * @return a random value + */ + fun nextFloat(min: Float, max: Float): Float { + val comparison = Ranges.checkRange(Ranges.Type.FLOAT, min, max) + return if (comparison == 0) min else min + (max - min) * nextFloat() + } + + /** + * Gives a random `int` value, uniformly distributed across the + * interval `[min, max]`. + * + * @param min lower bound of the desired interval + * @param max upper bound of the desired interval + * @return a random value + */ + fun nextInt(min: Int, max: Int): Int { + return nextLong(min.toLong(), max.toLong()).toInt() + } + + /** + * Gives a random `long` value, uniformly distributed across the + * interval `[min, max]`. + * + * @param min lower bound of the desired interval + * @param max upper bound of the desired interval + * @return a random value + */ + fun nextLong(min: Long, max: Long): Long { + val comparison = Ranges.checkRange(Ranges.Type.INTEGRAL, min, max) + return if (comparison == 0) min else Ranges.choose(this, min, max) + } + + /** + * Gives a random `short` value, uniformly distributed across the + * interval `[min, max]`. + * + * @param min lower bound of the desired interval + * @param max upper bound of the desired interval + * @return a random value + */ + fun nextShort(min: Short, max: Short): Short { + return nextLong(min.toLong(), max.toLong()).toShort() + } + + /** + * Gives a random `BigInteger` representable by the given number + * of bits. + * + * @param numberOfBits the desired number of bits + * @return a random `BigInteger` + * @see BigInteger.BigInteger + */ + fun nextBigInteger(numberOfBits: Int): BigInteger { + return BigInteger(numberOfBits, delegate) + } + + /** + * Gives a random `Instant` value, uniformly distributed across the + * interval `[min, max]`. + * + * @param min lower bound of the desired interval + * @param max upper bound of the desired interval + * @return a random value + */ + fun nextInstant(min: Instant, max: Instant): Instant { + val comparison = Ranges.checkRange(Ranges.Type.STRING, min, max) + if (comparison == 0) return min + val next = nextSecondsAndNanos( + min.epochSecond, + min.nano.toLong(), + max.epochSecond, + max.nano.toLong() + ) + return Instant.ofEpochSecond(next[0], next[1]) + } + + /** + * Gives a random `Duration` value, uniformly distributed across the + * interval `[min, max]`. + * + * @param min lower bound of the desired interval + * @param max upper bound of the desired interval + * @return a random value + */ + fun nextDuration(min: Duration, max: Duration): Duration { + val comparison = Ranges.checkRange(Ranges.Type.STRING, min, max) + if (comparison == 0) return min + val next = nextSecondsAndNanos( + min.seconds, + min.nano.toLong(), + max.seconds, + max.nano.toLong() + ) + return Duration.ofSeconds(next[0], next[1]) + } + + /** + * Gives a random element of the given collection. + * + * @param type of items in the collection + * @param items a collection + * @return a randomly chosen element from the collection + */ + fun choose(items: Collection): T { + return Items.choose(items, this) + } + + /** + * Gives a random element of the given array. + * + * @param type of items in the array + * @param items an array + * @return a randomly chosen element from the array + */ + fun choose(items: Array): T { + return items[nextInt(items.size)] + } + + /** + * Gives a reference to the JDK-random delegate of this instance. + * Intended for subclasses. + * + * @return the JDK-random delegate + */ + protected fun delegate(): Random { + return delegate + } + + private fun nextSecondsAndNanos( + minSeconds: Long, + minNanos: Long, + maxSeconds: Long, + maxNanos: Long + ): LongArray { + val nanoMin = BigInteger.valueOf(minSeconds) + .multiply(NANOS_PER_SECOND) + .add(BigInteger.valueOf(minNanos)) + val nanoMax = BigInteger.valueOf(maxSeconds) + .multiply(NANOS_PER_SECOND) + .add(BigInteger.valueOf(maxNanos)) + val generated = Ranges.choose(this, nanoMin, nanoMax) + .divideAndRemainder(NANOS_PER_SECOND) + return longArrayOf(generated[0].toLong(), generated[1].toLong()) + } + + companion object { + private val NANOS_PER_SECOND = BigInteger.valueOf(TimeUnit.SECONDS.toNanos(1)) + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/CoverageCollector.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/CoverageCollector.kt new file mode 100644 index 0000000000..a44decd4be --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/CoverageCollector.kt @@ -0,0 +1,24 @@ +package org.utbot.greyboxfuzzer.util + +import org.utbot.framework.plugin.api.Instruction +import java.util.concurrent.ConcurrentHashMap + +object CoverageCollector { + + private object Covered + private val coveredInstructions = ConcurrentHashMap() + val coverage: Set + get() = coveredInstructions.keys + + fun addCoverage(coverage: Set) { + this.coveredInstructions.putAll(coverage.map { it to Covered }) + } + + fun addCoverage(instruction: Instruction) { + this.coveredInstructions[instruction] = Covered + } + + fun clear() { + coveredInstructions.clear() + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/CustomClassLoader.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/CustomClassLoader.kt new file mode 100644 index 0000000000..2ef7ab914b --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/CustomClassLoader.kt @@ -0,0 +1,9 @@ +//package org.utbot.greyboxfuzzer.util +// +//import java.net.URLClassLoader +// +//object CustomClassLoader { +// lateinit var classLoader: ClassLoader +// +// fun isClassLoaderInitialized() = this::classLoader.isInitialized +//} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/FuzzerIllegalStateException.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/FuzzerIllegalStateException.kt new file mode 100644 index 0000000000..7f2b1a9d4f --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/FuzzerIllegalStateException.kt @@ -0,0 +1,3 @@ +package org.utbot.greyboxfuzzer.util + +class FuzzerIllegalStateException(msg: String): Exception(msg) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/FuzzerUtModelConstructor.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/FuzzerUtModelConstructor.kt new file mode 100644 index 0000000000..b757ba35f1 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/FuzzerUtModelConstructor.kt @@ -0,0 +1,9 @@ +package org.utbot.greyboxfuzzer.util + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel + +data class FuzzerUtModelConstructor( + val construct: (Any?, ClassId) -> UtModel, + val computeUnusedIdAndUpdate: () -> Int +) \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/GenericsReplacer.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/GenericsReplacer.kt new file mode 100644 index 0000000000..d84dfd6250 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/GenericsReplacer.kt @@ -0,0 +1,55 @@ +//package org.utbot.greyboxfuzzer.util +// +//import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl +//import java.lang.reflect.Parameter +//import java.lang.reflect.Type +//import java.lang.reflect.TypeVariable +//import java.lang.reflect.WildcardType +// +//class GenericsReplacer { +// +// private val replacedGenerics = mutableListOf>() +// +// fun replaceUnresolvedGenericsToRandomTypes(parameter: Parameter) { +// if (replacedGenerics.isNotEmpty()) { +// makeReplacement(replacedGenerics) +// return +// } +// val allUnresolvedTypesInType = (parameter.parameterizedType as? ParameterizedTypeImpl) +// ?.actualTypeArgumentsRecursive +// ?.filter { it is WildcardType || it is TypeVariable<*> } +// ?: return +// val allUnresolvedTypesInAnnotatedType = (parameter.annotatedType.type as? ParameterizedTypeImpl) +// ?.actualTypeArgumentsRecursive +// ?.filter { it is WildcardType || it is TypeVariable<*> } +// ?: return +// val allUnresolvedTypes = allUnresolvedTypesInType.zip(allUnresolvedTypesInAnnotatedType) +// replacedGenerics.addAll(allUnresolvedTypes) +// makeReplacement(allUnresolvedTypes) +// } +// +// private fun makeReplacement(allUnresolvedTypes: List>) { +// for ((unresolvedType, unresolvedTypeCopy) in allUnresolvedTypes) { +// val upperBound = +// if (unresolvedType is WildcardType) { +// unresolvedType.upperBounds.firstOrNull() ?: continue +// } else if (unresolvedType is TypeVariable<*>) { +// unresolvedType.bounds?.firstOrNull() ?: continue +// } else continue +// val upperBoundAsSootClass = upperBound.toClass()?.toSootClass() ?: continue +// val randomChild = +// upperBoundAsSootClass.children.filterNot { it.name.contains("$") }.randomOrNull()?.toJavaClass() ?: continue +// val upperBoundsFields = +// if (unresolvedType is WildcardType) { +// unresolvedType.javaClass.getAllDeclaredFields().find { it.name.contains("upperBounds") }!! to +// unresolvedTypeCopy.javaClass.getAllDeclaredFields().find { it.name.contains("upperBounds") }!! +// } else if (unresolvedType is TypeVariable<*>) { +// unresolvedType.javaClass.getAllDeclaredFields().find { it.name.contains("bounds") }!! to +// unresolvedTypeCopy.javaClass.getAllDeclaredFields().find { it.name.contains("bounds") }!! +// } else continue +// upperBoundsFields.first.setFieldValue(unresolvedType, arrayOf(randomChild)) +// upperBoundsFields.second.setFieldValue(unresolvedType, arrayOf(randomChild)) +// } +// } +// +//} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/GreyBoxFuzzingStatisticPrinter.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/GreyBoxFuzzingStatisticPrinter.kt new file mode 100644 index 0000000000..a092bf932e --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/GreyBoxFuzzingStatisticPrinter.kt @@ -0,0 +1,53 @@ +package org.utbot.greyboxfuzzer.util + +import org.objectweb.asm.Type +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.util.jClass + +object GreyBoxFuzzingStatisticPrinter { + + fun printFuzzingStats(methods2executions: Map>) { + val methodsToInstructionsNumbers = methods2executions.entries.map { (method, executions) -> + val methodInstructions = + (executions.firstOrNull() as? UtGreyBoxFuzzedExecution)?.fuzzingResult?.methodInstructions ?: setOf() + method to methodInstructions + } + logger.debug { "OVERALL RESULTS:" } + logger.debug { "------------------------------------------" } + for ((method, instructions) in methodsToInstructionsNumbers) { + val coveredMethodInstructions = CoverageCollector.coverage + .asSequence() + .filter { it.className == Type.getInternalName(method.classId.jClass) } + .filter { it.methodSignature == method.signature } + .toSet() + val coveredMethodLines = coveredMethodInstructions.map { it.lineNumber }.toSet() + val methodLines = instructions.map { it.lineNumber }.toSet() + + logger.debug { "METHOD: ${method.name}" } + logger.debug { "COVERED INSTRUCTIONS: ${coveredMethodInstructions.size} from ${instructions.size} ${coveredMethodInstructions.size.toDouble() / instructions.size * 100}%" } + logger.debug { "COVERED INSTRUCTIONS: ${coveredMethodInstructions.map { it.id }.sorted()}" } + logger.debug { "NOT COVERED INSTRUCTIONS: ${instructions.filter { it !in coveredMethodInstructions }.map { it.id }.sorted()}" } + logger.debug { "------" } + logger.debug { "COVERED LINES: ${coveredMethodLines.size} from ${methodLines.size} ${coveredMethodLines.size.toDouble() / methodLines.size * 100}%" } + logger.debug { "COVERED LINES: ${coveredMethodLines.sorted()}" } + logger.debug { "NOT COVERED LINES: ${methodLines.filter { it !in coveredMethodLines }.sorted()}" } + logger.debug { "----------------------" } + } + logger.debug { "-----------------------------------------------------" } + val allInstructionsToCover = methodsToInstructionsNumbers.flatMap { it.second }.toSet() + val allClassNames = methodsToInstructionsNumbers.map { it.first.classId }.toSet().map { it.name } + val allInstructionsToCoverSize = allInstructionsToCover.size + val allMethodLineNumbersSize = methodsToInstructionsNumbers.flatMap { it.second.map { it.lineNumber }.toSet() }.size + val coveredInstructions = + CoverageCollector.coverage + .filter { it.className.replace('/', '.') in allClassNames } + .toSet() + .filter { it in allInstructionsToCover } + val numberOfCoveredLines = coveredInstructions.map { it.lineNumber }.toSet().size + val numberOfCoveredInstructions = coveredInstructions.size + logger.debug { "IN INSTRUCTIONS FINALLY COVERED $numberOfCoveredInstructions from $allInstructionsToCoverSize ${numberOfCoveredInstructions.toDouble() / allInstructionsToCoverSize * 100}%" } + logger.debug { "IN LINES FINALLY COVERED $numberOfCoveredLines from $allMethodLineNumbersSize ${numberOfCoveredLines.toDouble() / allMethodLineNumbersSize * 100}%" } + logger.debug { "------------------------------------------" } + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/KotlinUtils.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/KotlinUtils.kt new file mode 100644 index 0000000000..37ad0587a3 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/KotlinUtils.kt @@ -0,0 +1,42 @@ +package org.utbot.greyboxfuzzer.util + +import org.utbot.greyboxfuzzer.util.kcheck.nextInRange +import java.util.* + +fun kotlin.random.Random.getTrue(prob: Int) = Random().nextInt(101) < prob + +fun List.sublistBeforeLast(element: T): List = + this.indexOfLast { it == element }.let { lastIndex -> + if (lastIndex == -1) this + else this.subList(0, lastIndex) + } +fun MutableList.removeIfAndReturnRemovedElements(cond: (T) -> Boolean): List { + val res = mutableListOf() + val iterator = this.iterator() + while (iterator.hasNext()) { + val element = iterator.next() + if (cond.invoke(element)) { + res.add(element) + iterator.remove() + } + } + return res +} + +fun String.removeBetweenAll(startChar: Char, endChar: Char): String { + val resultingString = StringBuilder() + var flToRemove = false + for (ch in this) { + if (ch == startChar) { + flToRemove = true + continue + } else if (ch == endChar) { + flToRemove = false + continue + } + if (!flToRemove) { + resultingString.append(ch) + } + } + return resultingString.toString() +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/Logger.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/Logger.kt new file mode 100644 index 0000000000..35ead6f59d --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/Logger.kt @@ -0,0 +1,6 @@ +package org.utbot.greyboxfuzzer.util + +import mu.KotlinLogging + +class Logger +val logger = KotlinLogging.logger {} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/ReflectionUtils.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/ReflectionUtils.kt new file mode 100644 index 0000000000..199e0a6580 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/ReflectionUtils.kt @@ -0,0 +1,398 @@ +@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") + +package org.utbot.greyboxfuzzer.util + +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.javaruntype.type.Types +import org.utbot.common.withAccessibility +import ru.vyarus.java.generics.resolver.context.GenericsContext +import ru.vyarus.java.generics.resolver.context.GenericsInfo +import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl +import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl +import java.lang.reflect.* +import kotlin.random.Random + +fun Class<*>.getAllDeclaredFields(): List { + val res = mutableListOf() + var current: Class<*>? = this + while (current != null) { + try { + res.addAll(current.declaredFields) + } catch (_: Error) { + + } + current = current.superclass + } + return res +} + +val java.lang.reflect.Type.rawType: java.lang.reflect.Type + get() = if (this is ParameterizedType) rawType else this +fun Class<*>.getAllDeclaredMethodsAndConstructors(): List { + val res = mutableListOf() + res.addAll(declaredConstructors) + var current: Class<*>? = this + while (current != null) { + res.addAll(current.declaredMethods) + current = current.superclass + } + return res +} + +fun Field.getFieldValue(instance: Any?): Any? { + try { + val fixedInstance = + if (this.isStatic()) { + null + } else instance + return withAccessibility { + when (this.type) { + Boolean::class.javaPrimitiveType -> this.getBoolean(fixedInstance) + Byte::class.javaPrimitiveType -> this.getByte(fixedInstance) + Char::class.javaPrimitiveType -> this.getChar(fixedInstance) + Short::class.javaPrimitiveType -> this.getShort(fixedInstance) + Int::class.javaPrimitiveType -> this.getInt(fixedInstance) + Long::class.javaPrimitiveType -> this.getLong(fixedInstance) + Float::class.javaPrimitiveType -> this.getFloat(fixedInstance) + Double::class.javaPrimitiveType -> this.getDouble(fixedInstance) + else -> this.get(fixedInstance) + } + } + } catch (_: Throwable) { + return null + } +} + +fun Field.setFieldValue(instance: Any?, fieldValue: Any?) { + withAccessibility { + val fixedInstance = + if (this.isStatic()) { + null + } else instance + when (this.type) { + Boolean::class.javaPrimitiveType -> this.setBoolean(fixedInstance, fieldValue as Boolean) + Byte::class.javaPrimitiveType -> this.setByte(fixedInstance, fieldValue as Byte) + Char::class.javaPrimitiveType -> this.setChar(fixedInstance, fieldValue as Char) + Short::class.javaPrimitiveType -> this.setShort(fixedInstance, fieldValue as Short) + Int::class.javaPrimitiveType -> this.setInt(fixedInstance, fieldValue as Int) + Long::class.javaPrimitiveType -> this.setLong(fixedInstance, fieldValue as Long) + Float::class.javaPrimitiveType -> this.setFloat(fixedInstance, fieldValue as Float) + Double::class.javaPrimitiveType -> this.setDouble(fixedInstance, fieldValue as Double) + else -> this.set(fixedInstance, fieldValue) + } + } +} + +fun Type.toClass(): Class<*>? = + try { + when (this) { + is ParameterizedTypeImpl -> this.rawType + is ru.vyarus.java.generics.resolver.context.container.ParameterizedTypeImpl -> this.rawType.toClass() + is GenericArrayTypeImpl -> java.lang.reflect.Array.newInstance( + this.genericComponentType.toClass(), + 0 + ).javaClass + is ru.vyarus.java.generics.resolver.context.container.GenericArrayTypeImpl -> java.lang.reflect.Array.newInstance( + this.genericComponentType.toClass(), + 0 + ).javaClass + is ru.vyarus.java.generics.resolver.context.container.ExplicitTypeVariable -> this.rawType.toClass() + else -> this as? Class<*> + } + } catch (e: Exception) { + null + } + +fun Field.generateInstance(instance: Any, generatedValue: Any?) { + if (this.isStatic() && this.isFinal) return + this.isAccessible = true + this.isFinal = false + if (this.isEnumConstant || this.isSynthetic) return + if (this.type.isPrimitive) { + val definedValue = generatedValue + when (definedValue?.javaClass) { + null -> this.set(instance, null) + Boolean::class.javaObjectType -> this.setBoolean(instance, definedValue as Boolean) + Byte::class.javaObjectType -> this.setByte(instance, definedValue as Byte) + Char::class.javaObjectType -> this.setChar(instance, definedValue as Char) + Short::class.javaObjectType -> this.setShort(instance, definedValue as Short) + Int::class.javaObjectType -> this.setInt(instance, definedValue as Int) + Long::class.javaObjectType -> this.setLong(instance, definedValue as Long) + Float::class.javaObjectType -> this.setFloat(instance, definedValue as Float) + Double::class.javaObjectType -> this.setDouble(instance, definedValue as Double) + else -> return + } + } else { + this.set(instance, generatedValue) + } +} + +private fun Class<*>.processArray(value: Any): List { +// return (0 until JArray.getLength(value)).map { JArray.get(value, it) } +// val subFields = mutableListOf() +// for (i in 0 until JArray.getLength(value)) { +// val field = JArray.get(value, i) +// } + return emptyList() +} + +//private fun Field + +var Field.isFinal: Boolean + get() = (this.modifiers and Modifier.FINAL) == Modifier.FINAL + set(value) { + if (value == this.isFinal) return + val modifiersField = this.javaClass.getDeclaredField("modifiers") + modifiersField.isAccessible = true + modifiersField.setInt(this, this.modifiers and if (value) Modifier.FINAL else Modifier.FINAL.inv()) + } + +fun Method.isStatic() = modifiers.and(Modifier.STATIC) > 0 +fun Field.isStatic() = modifiers.and(Modifier.STATIC) > 0 +fun Method.hasModifiers(vararg modifiers: Int) = modifiers.all { it.and(this.modifiers) > 0 } +fun Field.hasModifiers(vararg modifiers: Int) = modifiers.all { it.and(this.modifiers) > 0 } +fun Class<*>.hasModifiers(vararg modifiers: Int) = modifiers.all { it.and(this.modifiers) > 0 } +fun Constructor<*>.hasModifiers(vararg modifiers: Int) = modifiers.all { it.and(this.modifiers) > 0 } +fun Constructor<*>.hasAtLeastOneOfModifiers(vararg modifiers: Int) = modifiers.any { it.and(this.modifiers) > 0 } +fun Class<*>.hasAtLeastOneOfModifiers(vararg modifiers: Int) = modifiers.any { it.and(this.modifiers) > 0 } +fun Class<*>.canBeInstantiated() = !hasAtLeastOneOfModifiers(Modifier.ABSTRACT, Modifier.INTERFACE) +fun Field.hasAtLeastOneOfModifiers(vararg modifiers: Int) = modifiers.any { it.and(this.modifiers) > 0 } + +fun ru.vyarus.java.generics.resolver.context.container.ParameterizedTypeImpl.getActualArguments(): Array { + val args = this.javaClass.getAllDeclaredFields().find { it.name == "actualArguments" } ?: return arrayOf() + return args.let { + it.isAccessible = true + it.get(this) as Array + }.also { args.isAccessible = false } +} + +fun List>.chooseRandomConstructor() = + if (Random.getTrue(60)) { + this.shuffled().minByOrNull { it.parameterCount } + } else this.randomOrNull() + +fun generateParameterizedTypeImpl( + clazz: Class<*>, + actualTypeParameters: Array +): ParameterizedTypeImpl { + val constructor = ParameterizedTypeImpl::class.java.declaredConstructors.first() + constructor.isAccessible = true + return constructor.newInstance(clazz, actualTypeParameters, null) as ParameterizedTypeImpl +} + +object ReflectionUtils { + fun getRandomClassWithBounds(bound: Class<*>): Class<*> { + return Any::class.java + } + + fun forJavaReflectTypeSafe(type: Type): org.javaruntype.type.Type<*> { + val strType = type.toString() + val safeType = + if (type is WildcardType) { + if ((strType.contains(" super ") && strType.contains("extends")) || strType.contains("extends")) { + type.upperBounds.firstOrNull() ?: Any::class.java.rawType + } else if (strType.contains(" super ")) { + type.lowerBounds.firstOrNull() ?: Any::class.java.rawType + } else { + Any::class.java.rawType + } + } else type + return try { + Types.forJavaLangReflectType(safeType) + } catch (e: Throwable) { + try { + Types.forJavaLangReflectType(safeType.toClass()) + } catch (e: Throwable) { + Types.forJavaLangReflectType(Any::class.java) + } + } + } + +} + +val ParameterizedTypeImpl.actualTypeArgumentsRecursive: List + get() { + val queue = ArrayDeque() + val res = mutableListOf() + if (this is TypeVariable<*>) { + queue.add(this) + } + this.actualTypeArguments.map { queue.add(it) } + while (queue.isNotEmpty()) { + val el = queue.removeFirst() + if (el is ParameterizedTypeImpl) { + el.actualTypeArguments.map { queue.add(it) } + } + res.add(el) + } + return res + } + +fun Class<*>.isFromSameJar(other: Class<*>) = + try { + val thisJar = + this.getResource('/' + this.name.replace('.', '/') + ".class")?.path?.substringBefore(".jar!") ?: "1" + val otherJar = + other.getResource('/' + other.name.replace('.', '/') + ".class")?.path?.substringBefore(".jar!") ?: "2" + thisJar == otherJar + } catch (e: Throwable) { + false + } + + +//fun Parameter.replaceUnresolvedGenericsToRandomTypes() { +// val allUnresolvedTypesInType = (this.parameterizedType as? ParameterizedTypeImpl) +// ?.actualTypeArgumentsRecursive +// ?.filter { it is WildcardType || it is TypeVariable<*> } +// ?: return +// val allUnresolvedTypesInAnnotatedType = (this.annotatedType.type as? ParameterizedTypeImpl) +// ?.actualTypeArgumentsRecursive +// ?.filter { it is WildcardType || it is TypeVariable<*> } +// ?: return +// val allUnresolvedTypes = allUnresolvedTypesInType.zip(allUnresolvedTypesInAnnotatedType) +// for ((unresolvedType, unresolvedTypeCopy) in allUnresolvedTypes) { +// val upperBound = +// if (unresolvedType is WildcardType) { +// unresolvedType.upperBounds.firstOrNull() ?: continue +// } else if (unresolvedType is TypeVariable<*>) { +// unresolvedType.bounds?.firstOrNull() ?: continue +// } else continue +// val upperBoundAsSootClass = upperBound.toClass()?.toSootClass() ?: continue +// val randomChild = +// upperBoundAsSootClass.children.filterNot { it.name.contains("$") }.randomOrNull()?.toJavaClass() ?: continue +// val upperBoundsFields = +// if (unresolvedType is WildcardType) { +// unresolvedType.javaClass.getAllDeclaredFields().find { it.name.contains("upperBounds") }!! to +// unresolvedTypeCopy.javaClass.getAllDeclaredFields().find { it.name.contains("upperBounds") }!! +// } else if (unresolvedType is TypeVariable<*>) { +// unresolvedType.javaClass.getAllDeclaredFields().find { it.name.contains("bounds") }!! to +// unresolvedTypeCopy.javaClass.getAllDeclaredFields().find { it.name.contains("bounds") }!! +// } else continue +// upperBoundsFields.first.setFieldValue(unresolvedType, arrayOf(randomChild)) +// upperBoundsFields.second.setFieldValue(unresolvedType, arrayOf(randomChild)) +// } +//} + +fun Method.resolveMethod( + parameterTypeContext: ParameterTypeContext, + typeArguments: List +): Pair, GenericsContext> { + val cl = this.declaringClass//parameterTypeContext.resolved.rawClass + val resolvedJavaType = + parameterTypeContext.generics.resolveType(parameterTypeContext.type()) as? ParameterizedType + val gm = LinkedHashMap() + if (resolvedJavaType != null) { + typeArguments.zip(resolvedJavaType.actualTypeArguments.toList()).forEach { + gm[it.first.typeName] = it.second + } + } + val m = mutableMapOf(cl to gm) + val generics = LinkedHashMap() + typeArguments.forEachIndexed { index, typeVariable -> + generics[typeVariable.typeName] = + (resolvedJavaType as ru.vyarus.java.generics.resolver.context.container.ParameterizedTypeImpl).actualTypeArguments[index] + } + val gctx = GenericsContext(GenericsInfo(cl, m), cl) + gctx.method(this).methodGenericsMap().forEach { (s, type) -> generics.getOrPut(s) { type } } + return generics to gctx +} + +fun org.javaruntype.type.Type<*>.convertToPrimitiveIfPossible(): org.javaruntype.type.Type<*> { + val possiblePrimitive = when (this.toString()) { + java.lang.Short::class.java.name -> Short::class.java + java.lang.Byte::class.java.name -> Byte::class.java + java.lang.Integer::class.java.name -> Int::class.java + java.lang.Long::class.java.name -> Long::class.java + java.lang.Float::class.java.name -> Float::class.java + java.lang.Double::class.java.name -> Double::class.java + java.lang.Character::class.java.name -> Char::class.java + java.lang.Boolean::class.java.name -> Boolean::class.java + else -> null + } + return possiblePrimitive?.let { ReflectionUtils.forJavaReflectTypeSafe(it) } ?: this +} + +fun Type.getActualTypeArguments(): Array = + when (this) { + is ParameterizedTypeImpl -> this.actualTypeArguments + is ru.vyarus.java.generics.resolver.context.container.ParameterizedTypeImpl -> this.actualTypeArguments + else -> arrayOf() + } + +class GenericsReplacer { + private val replacedGenerics = mutableListOf() + + private data class ReplacedTypeParameter( + val type: Type, + val typeBound: Type?, + val annotatedType: Type, + val annotatedTypeBound: Type? + ) + + fun replaceUnresolvedGenericsToRandomTypes(parameter: Parameter) { + if (replacedGenerics.isNotEmpty()) { + makeReplacement(replacedGenerics) + return + } + val allUnresolvedTypesInType = (parameter.parameterizedType as? ParameterizedTypeImpl) + ?.actualTypeArgumentsRecursive + ?.filter { it is WildcardType || it is TypeVariable<*> } + ?: return + val allUnresolvedTypesInAnnotatedType = (parameter.annotatedType.type as? ParameterizedTypeImpl) + ?.actualTypeArgumentsRecursive + ?.filter { it is WildcardType || it is TypeVariable<*> } + ?: return + val allUnresolvedTypes = allUnresolvedTypesInType.zip(allUnresolvedTypesInAnnotatedType) + replacedGenerics.addAll( + allUnresolvedTypes.map { + ReplacedTypeParameter(it.first, getUpperBound(it.first), it.second, getUpperBound(it.second)) + } + ) + makeReplacement(replacedGenerics) + } + + fun revert() { + if (replacedGenerics.isEmpty()) return + for ((type, upperBound, annotatedType, _) in replacedGenerics) { + setUpperBoundTo(type, annotatedType, upperBound?.toClass() ?: Any::class.java) + } + } + + private fun makeReplacement(allUnresolvedTypes: List) { + for ((type, upperBound, annotatedType, _) in allUnresolvedTypes) { + val upperBoundAsSootClass = upperBound?.toClass()?.toSootClass() ?: continue + val newRandomBound = + upperBoundAsSootClass.children + .filterNot { it.name.contains("$") } + .filterNot { it.javaPackageName.startsWith("sun") } + .randomOrNull()?.toJavaClass() ?: continue + setUpperBoundTo(type, annotatedType, newRandomBound) + } + } + + private fun setUpperBoundTo(type: Type, annotatedType: Type, clazz: Class<*>) { + val upperBoundsFields = + when (type) { + is WildcardType -> { + type.javaClass.getAllDeclaredFields().find { it.name.contains("upperBounds") }!! to + annotatedType.javaClass.getAllDeclaredFields() + .find { it.name.contains("upperBounds") }!! + } + is TypeVariable<*> -> { + type.javaClass.getAllDeclaredFields().find { it.name.contains("bounds") }!! to + annotatedType.javaClass.getAllDeclaredFields().find { it.name.contains("bounds") }!! + } + else -> return + } + upperBoundsFields.first.setFieldValue(type, arrayOf(clazz)) + upperBoundsFields.second.setFieldValue(type, arrayOf(clazz)) + } + + private fun getUpperBound(unresolvedType: Type): Type? = + when (unresolvedType) { + is WildcardType -> unresolvedType.upperBounds.firstOrNull() + is TypeVariable<*> -> unresolvedType.bounds?.firstOrNull() + else -> null + } + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/SootUtils.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/SootUtils.kt new file mode 100644 index 0000000000..0af0ce2cfa --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/SootUtils.kt @@ -0,0 +1,236 @@ +package org.utbot.greyboxfuzzer.util + +import org.utbot.common.nameOfPackage +import org.utbot.greyboxfuzzer.quickcheck.generator.java.lang.* +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.* +import soot.ArrayType +import soot.Hierarchy +import soot.RefType +import soot.Scene +import soot.SootClass +import soot.SootField +import soot.SootMethod +import soot.jimple.Constant +import soot.jimple.internal.* +import soot.toolkits.graph.ExceptionalUnitGraph +import java.lang.reflect.Executable +import java.lang.reflect.Field +import java.lang.reflect.Method +import kotlin.reflect.KFunction +import kotlin.reflect.jvm.javaMethod + +fun SootClass.getImplementersOfWithChain(onlyConcreteClasses: Boolean = true, allowNotOnlyStdLib: Boolean = false): List> { + this.checkLevel(SootClass.HIERARCHY) +// if (!this.isInterface && !this.isAbstract) { +// throw RuntimeException("interfaced needed; got $this") +// } + val thisAsJavaClass = this.toJavaClass() ?: return emptyList() + val res = mutableListOf(mutableListOf(this)) + val queue = ArrayDeque() + queue.add(this) + while (queue.isNotEmpty()) { + val curSootClass = queue.removeFirst() + val implementers = + if (curSootClass.isInterface) { + Scene.v().classes.filter { it.interfaces.contains(curSootClass) } + .filter { it.interfaces.contains(curSootClass) } //+ hierarchy.getDirectSubinterfacesOf(curSootClass) + } else { + Scene.v().classes.filter { it.superclassOrNull == curSootClass } + //hierarchy.getDirectSubclassesOf(curSootClass) + } + if (implementers.isEmpty()) continue + val oldLists = res.removeIfAndReturnRemovedElements { it.last() == curSootClass } + if (curSootClass.isConcrete || !onlyConcreteClasses) { + oldLists.forEach { res.add(it.toMutableList()) } + } + for (implementer in implementers) { + queue.add(implementer) + oldLists.forEach { res.add((it + listOf(implementer)).toMutableList()) } + } + } + return res.filter { + val isJavaStdLibClass = it.last().javaPackageName.startsWith("java") || allowNotOnlyStdLib + val isFromSameProject = it.last().toJavaClass()?.isFromSameJar(thisAsJavaClass) ?: false + //it.last().javaPackageName.contains(this.javaPackageName) || this.javaPackageName.contains(it.last().javaPackageName) + val isFromSamePackage = it.last().javaPackageName == this.javaPackageName + val isSupportedPackage = isJavaStdLibClass || isFromSameProject + val isAccessible = it.last().isPublic || (!it.last().isPublic && isFromSamePackage) + val isConcrete = !onlyConcreteClasses || it.last().isConcrete + it.all { !it.toString().contains("$") } && isAccessible && isConcrete && isSupportedPackage + } +} + +fun SootMethod.getAllTypesFromCastAndInstanceOfInstructions(): Set> = + this.activeBody.units.asSequence().filterIsInstance() + .map { it.rightOp } + .filter { it is JCastExpr || it is JInstanceOfExpr } + .mapNotNull { + when (it) { + is JCastExpr -> { + val type = it.type + when (type) { + is RefType -> type.sootClass?.toJavaClass() + is ArrayType -> { + if (type.elementType is RefType) { + try { + Class.forName("[L${type.elementType};") + } catch (e: Throwable) { + null + } + } else { + null + } + } + + else -> null + } + //(it.type as? RefType)?.sootClass?.toJavaClass() + } + + else -> { + val type = (it as JInstanceOfExpr).checkType + when (type) { + is RefType -> type.sootClass?.toJavaClass() + is ArrayType -> { + if (type.elementType is RefType) { + try { + Class.forName("[L${type.elementType};") + } catch (e: Throwable) { + null + } + } else { + null + } + } + + else -> null + } + } + } + }.toSet() + +val SootMethod.pureJavaSignature + get() = bytecodeSignature.substringAfter(' ').dropLast(1) + +val ExecutableId.sootMethod: SootMethod + get() { + val clazz = Scene.v().getSootClass(classId.name) + return clazz.methods.single { it.pureJavaSignature == signature } + } + +fun SootMethod.getClassFieldsUsedByFunc(clazz: Class<*>) = + activeBody.units + .asSequence() + .mapNotNull { it as? JAssignStmt } + .map { it.rightOp } + .mapNotNull { it as? JInstanceFieldRef } + .mapNotNull { fieldRef -> clazz.getAllDeclaredFields().find { it.name == fieldRef.field.name } } + .toSet() + +fun SootClass.toJavaClass(): Class<*>? = + try { + Class.forName(this.name) + } catch (e: Throwable) { + try { + utContext.classLoader.loadClass(this.name) + } catch (e: Throwable) { + null + } + } + +fun KFunction<*>.toSootMethod(): SootMethod? = this.javaMethod?.toSootMethod() + +fun Class<*>.toSootClass() = + Scene.v().classes.find { it.name == this.name } + +fun Method.toSootMethod(): SootMethod? { + val cl = declaringClass.toSootClass() ?: return null + return cl.methods.find { + val sig = it.bytecodeSignature.drop(1).dropLast(1).substringAfter("${cl.name}: ") + this.signature == sig + } +} + +fun SootMethod.toJavaMethod(): Executable? = + declaringClass.toJavaClass()?.getAllDeclaredMethodsAndConstructors()?.find { + it.signature == this.bytecodeSignature.drop(1).dropLast(1).substringAfter("${declaringClass.name}: ") + } + +fun SootField.toJavaField(): Field? = + declaringClass.toJavaClass()?.getAllDeclaredFields()?.find { it.name == name } + +fun Field.toSootField(): SootField? = + declaringClass.toSootClass()?.fields?.find { it.name == name } + +fun SootClass.getAllAncestors(): List { + val queue = ArrayDeque() + val res = mutableSetOf() + this.superclassOrNull?.let { queue.add(it) } + queue.addAll(this.interfaces) + while (queue.isNotEmpty()) { + val el = queue.removeFirst() + el.superclassOrNull?.let { + if (!res.contains(it) && !queue.contains(it)) queue.add(it) + } + el.interfaces.map { if (!res.contains(it) && !queue.contains(it)) queue.add(it) } + res.add(el) + } + return res.toList() +} + +val SootClass.children + get() = + Scene.v().classes.filter { it.getAllAncestors().contains(this) } + +val SootClass.superclassOrNull + get() = + try { + superclass + } catch (e: Exception) { + null + } + + +//TODO add stuff with generics +object SootStaticsCollector { + + private val classToStaticMethodsInstanceProviders = mutableMapOf, List>() + private val classToStaticFieldsInstanceProviders = mutableMapOf, List>() + fun getStaticMethodsInitializersOf(clazz: Class<*>): List { + if (classToStaticMethodsInstanceProviders.contains(clazz)) return classToStaticMethodsInstanceProviders[clazz]!! + val classes = Scene.v().classes.filter { !it.name.contains("$") } + val sootMethodsToProvideInstance = classes.flatMap { + it.methods + .asSequence() + .filter { it.isPublic && it.isStatic && it.returnType.toString() == clazz.name } + .filter { + it.declaringClass.javaPackageName.startsWith("java") || + it.declaringClass.javaPackageName.contains(clazz.nameOfPackage) || + clazz.nameOfPackage.contains(it.declaringClass.javaPackageName) + } + .filter { it.parameterTypes.all { !it.toString().contains(clazz.name) } } + .filter { !it.toString().contains('$') } + .toList() + } + val javaMethodsToProvideInstance = sootMethodsToProvideInstance.mapNotNull { it.toJavaMethod() as? Method } + classToStaticMethodsInstanceProviders[clazz] = javaMethodsToProvideInstance + return javaMethodsToProvideInstance + } + + fun getStaticFieldsInitializersOf(clazz: Class<*>): List { + if (classToStaticFieldsInstanceProviders.contains(clazz)) return classToStaticFieldsInstanceProviders[clazz]!! + val classes = Scene.v().classes.filter { !it.name.contains("$") } + val sootFieldsToProvideInstance = classes.flatMap { + it.fields + .asSequence() + .filter { it.isStatic && it.type.toString() == clazz.name } + .toList() + } + val javaFieldsToProvideInstance = sootFieldsToProvideInstance.mapNotNull { it.toJavaField() } + classToStaticFieldsInstanceProviders[clazz] = javaFieldsToProvideInstance + return javaFieldsToProvideInstance + } +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/UtBotUtils.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/UtBotUtils.kt new file mode 100644 index 0000000000..3983d088a1 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/UtBotUtils.kt @@ -0,0 +1,129 @@ +package org.utbot.greyboxfuzzer.util + +import org.utbot.greyboxfuzzer.quickcheck.internal.ParameterTypeContext +import org.utbot.greyboxfuzzer.generator.* +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.id +import org.utbot.greyboxfuzzer.quickcheck.generator.GeneratorContext +import java.lang.reflect.Method +import java.lang.reflect.Parameter + + +fun UtAssembleModel.addModification(modifications: List) = + UtAssembleModel( + this.id, + this.classId, + "${this.classId.name}#" + this.id?.toString(16), + this.instantiationCall, + this.origin + ) { modificationsChain + modifications } +// UtAssembleModel( +// this.id, +// this.classId, +// "${this.classId.name}#" + this.id?.toString(16), +// this.instantiationCall, +// this.modificationsChain + modifications, +// this.origin +// ) + +fun UtAssembleModel.addOrReplaceModification(newModification: UtStatementModel): UtAssembleModel { + val newModificationChain = + when (newModification) { + is UtDirectSetFieldModel -> { + val oldChain = this.modificationsChain.filterIsInstance().toMutableList() + oldChain.indexOfFirst { newModification.fieldId == it.fieldId }.let { + if (it != -1) oldChain.removeAt(it) + } + oldChain.add(newModification) + oldChain.toList() + } + is UtExecutableCallModel -> { + val oldChain = this.modificationsChain.filterIsInstance().toMutableList() + oldChain.indexOfFirst { newModification.executable == it.executable }.let { + if (it != -1) oldChain.removeAt(it) + } + oldChain.add(newModification) + oldChain.toList() + } + } + + return UtAssembleModel( + this.id, + this.classId, + "${this.classId.name}#" + this.id?.toString(16), + this.instantiationCall, + this.origin + ) { newModificationChain } +} + +fun UtModel.copy(): UtModel = + when (this) { + is UtAssembleModel -> this.copy() + is UtCompositeModel -> this.copy() + is UtArrayModel -> this.copy() + is UtClassRefModel -> this.copy() + is UtPrimitiveModel -> this.copy() + else -> this + } + + + +fun FuzzerUtModelConstructor.constructAssembleModelUsingMethodInvocation( + clazz: Class<*>, + methodExecutableId: ExecutableId, + parameterValues: List, + generatorContext: GeneratorContext +): UtAssembleModel { + val genId = generatorContext.utModelConstructor.computeUnusedIdAndUpdate() + return UtAssembleModel( + genId, + classIdForType(clazz), + "${clazz.name}#" + genId.toString(16), + UtExecutableCallModel( + null, + methodExecutableId, + parameterValues + ) + ) +} + +fun classIdForType(clazz: Class<*>): ClassId { + return clazz.id +} +//fun UtModelConstructor.constructModelFromValue(value: Any?, classId: ClassId) = +// if (value == null) { +// UtNullModel(classId) +// } else { +// try { +// ZestUtils.setUnserializableFieldsToNull(value) +// construct(value, classId) +// } catch (e: Throwable) { +// UtNullModel(classId) +// } +// } +// +//fun UtModelConstructor.constructModelFromValues(list: List>) = +// list.map { (value, classId) -> +// if (value?.value == null) { +// UtNullModel(classId) +// } else { +// try { +// ZestUtils.setUnserializableFieldsToNull(value.value) +// construct(value.value, classId) +// } catch (e: Throwable) { +// UtNullModel(classId) +// } +// } +// } + +fun Parameter.resolveParameterTypeAndBuildParameterContext( + parameterIndex: Int, + method: Method +): ParameterTypeContext { + val parameterTypeContext = this.createParameterTypeContext(0) + val resolvedOriginalType = parameterTypeContext.generics.resolveType(parameterTypeContext.type()) + val genericContext = resolvedOriginalType.createGenericsContext(this.type.toClass()!!) + val resolvedParameterType = genericContext.method(method).resolveParameterType(parameterIndex) + val newGenericContext = resolvedParameterType.createGenericsContext(resolvedParameterType.toClass()!!) + return createParameterContextForParameter(this, parameterIndex, newGenericContext, resolvedParameterType) +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/UtGreyBoxFuzzedExecution.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/UtGreyBoxFuzzedExecution.kt new file mode 100644 index 0000000000..5837b97b6a --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/UtGreyBoxFuzzedExecution.kt @@ -0,0 +1,34 @@ +package org.utbot.greyboxfuzzer.util + +import org.utbot.framework.plugin.api.* +import org.utbot.instrumentation.instrumentation.execution.UtFuzzingConcreteExecutionResult + +//class UtFuzzExecutionResult() : UtConcreteExecutionResult + +class UtGreyBoxFuzzedExecution( + stateBefore: EnvironmentModels, + val fuzzingResult: UtFuzzingConcreteExecutionResult, + stateAfter: EnvironmentModels = stateBefore, + coverage: Coverage? = null, + summary: List? = null, + testMethodName: String? = null, + displayName: String? = null +): UtExecution(stateBefore, stateAfter, fuzzingResult.result, coverage, summary, testMethodName, displayName) { + + override fun toString(): String = buildString { + append("UtGreyBoxFuzzedExecution(") + appendLine() + + append(":") + appendLine() + append(stateBefore) + appendLine() + + append(":") + appendLine() + append(result) + append(")") + } + + +} \ No newline at end of file diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/kcheck/Common.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/kcheck/Common.kt new file mode 100644 index 0000000000..44b057c4f3 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/kcheck/Common.kt @@ -0,0 +1,77 @@ +package org.utbot.greyboxfuzzer.util.kcheck + +import java.util.* + +object FancyFunctions { + fun (() -> R1).mapResult(mapper: (R1) -> R2): () -> R2 = + { mapper(this()) } + fun ((A1) -> R1).mapResult(mapper: (R1) -> R2): (A1) -> R2 = + { a1 -> mapper(this(a1)) } + fun ((A1, A2) -> R1).mapResult(mapper: (R1) -> R2): (A1, A2) -> R2 = + { a1, a2 -> mapper(this(a1, a2)) } + fun ((A1, A2, A3) -> R1).mapResult(mapper: (R1) -> R2): (A1, A2, A3) -> R2 = + { a1, a2, a3 -> mapper(this(a1, a2, a3)) } + fun ((A1, A2, A3, A4) -> R1).mapResult(mapper: (R1) -> R2): (A1, A2, A3, A4) -> R2 = + { a1, a2, a3, a4 -> mapper(this(a1, a2, a3, a4)) } +} + +inline infix fun Int.times(body: () -> Unit) { + for (i in 0..this) body() +} + +private class CharProgressionAsCharSequence(val from: CharProgression): CharSequence { + override val length: Int + get() = (from.last - from.first + 1) / from.step + + override operator fun get(index: Int): Char = + when (index) { + !in 0..(length - 1) -> throw IndexOutOfBoundsException("get(index: Int)") + else -> from.first + index + } + + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = + when{ + startIndex > endIndex -> throw IllegalArgumentException("startIndex > endIndex") + startIndex !in 0..(length - 1) -> throw IndexOutOfBoundsException("subSequence(startIndex: Int, endIndex: Int)") + endIndex !in 0..(length - 1) -> throw IndexOutOfBoundsException("subSequence(startIndex: Int, endIndex: Int)") + else -> CharProgression.fromClosedRange( + from.first + startIndex * from.step, + from.first + endIndex * from.step, + from.step + ).asCharSequence() + } + + override fun toString(): String = from.joinToString(separator = "") + override fun hashCode() = Objects.hash(from, 42) + override fun equals(other: Any?) = + other is CharProgressionAsCharSequence + && other.from == from +} + +fun CharProgression.asCharSequence(): CharSequence = CharProgressionAsCharSequence(this) + +private class CharListAsCharSequence(val from: List): CharSequence { + override val length: Int + get() = from.size + + override operator fun get(index: Int): Char = from[index] + + override fun subSequence(startIndex: Int, endIndex: Int) = from.subList(startIndex, endIndex).asCharSequence() + + override fun toString(): String = from.joinToString(separator = "") + override fun hashCode() = Objects.hash(from, 42) + override fun equals(other: Any?) = + other is CharListAsCharSequence + && other.from == from +} + +fun List.asCharSequence(): CharSequence = CharListAsCharSequence(this) + +inline fun Iterable<*>.firstInstanceOf(): T? { + for(e in this) if(e is T) return e + return null +} + +operator fun List.component6(): T = get(5) +operator fun List.component7(): T = get(6) +operator fun List.component8(): T = get(7) diff --git a/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/kcheck/RandomEx.kt b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/kcheck/RandomEx.kt new file mode 100644 index 0000000000..c8a1a8e6e0 --- /dev/null +++ b/utbot-greyboxfuzzer/src/main/kotlin/org/utbot/greyboxfuzzer/util/kcheck/RandomEx.kt @@ -0,0 +1,83 @@ +package org.utbot.greyboxfuzzer.util.kcheck + +import java.util.* + +class HighQualityRandom(seed: Long = System.nanoTime()): Random(seed) { + private var u: Long = 0L + private var v = 4101842887655102017L + private var w = 1L + + init { + u = seed xor v; + nextLong() + v = u; + nextLong() + w = v; + nextLong() + } + + override fun nextLong(): Long { + u = u * 2862933555777941757L + 7046029254386353087L; + v = v xor (v ushr 17) + v = v xor (v shl 31) + v = v xor (v ushr 8) + w = 4294957665L * (w and 0xffffffff) + (w ushr 32); + var x = u xor (u shl 21); + x = x xor (x ushr 35) + x = x xor (x shl 4) + return (x + v) xor w + } + + override fun next(bits: Int): Int { + return (nextLong() ushr (64 - bits)).toInt() + } +} + +fun Random.nextLong(bound: Long): Long { + var bits: Long + var v: Long + do { + bits = (nextLong() shl 1).ushr(1) + v = bits % bound + } while (bits - v + (bound - 1) < 0L) + return v +} +fun Random.nextShort(): Short = nextInRange(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt() + 1).toShort() +fun Random.nextShort(bound: Short): Short = nextInt(bound.toInt()).toShort() +fun Random.nextByte(): Byte = nextInRange(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt() + 1).toByte() +fun Random.nextByte(bound: Byte): Byte = nextInt(bound.toInt()).toByte() +fun Random.nextChar(): Char = nextShort().toChar() +fun Random.nextChar(alphabet: CharSequence) = alphabet[nextInt(alphabet.length)] +fun Random.nextString(alphabet: CharSequence, minLength: Int = 0, maxLength: Int = 1001) = + charSequence(alphabet).take(nextInRange(minLength, maxLength)).joinToString(separator = "") + +fun Random.nextInRange(min: Int, max: Int): Int = when { + max == min -> min + else -> (nextLong(max.toLong() - min.toLong()) + min).toInt() +} + + +fun Random.nextInRange(min: Long, max: Long): Long { + // this is a bit tricky + val minHalf = min / 2 + val maxHalf = max / 2 + val minOtherHalf = min - minHalf + val maxOtherHalf = max - maxHalf + return nextLong(maxHalf - minHalf + 1) + nextLong(maxOtherHalf - minOtherHalf) + min +} + +fun Random.nextInRange(min: Short, max: Short): Short = (nextInt(max - min) + min).toShort() +fun Random.nextInRange(min: Byte, max: Byte): Byte = (nextInt(max - min) + min).toByte() +fun Random.nextInRange(min: Float, max: Float): Float = nextFloat() * (max - min) + min +fun Random.nextInRange(min: Double, max: Double): Double = nextDouble() * (max - min) + min + +fun Random.intSequence() = generateSequence { nextInt() } +fun Random.longSequence() = generateSequence { nextLong() } +fun Random.shortSequence() = generateSequence { nextShort() } +fun Random.byteSequence() = generateSequence { nextByte() } +fun Random.booleanSequence() = generateSequence { nextBoolean() } +fun Random.floatSequence() = generateSequence { nextFloat() } +fun Random.doubleSequence() = generateSequence { nextDouble() } +fun Random.charSequence(alphabet: CharSequence) = generateSequence { nextChar(alphabet) } +fun Random.stringSequence(alphabet: CharSequence, minLength: Int = 0, maxLength: Int = 1000) = + generateSequence { nextString(alphabet, minLength, maxLength) } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt index c86ddd0206..ed7d2fe420 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt @@ -103,8 +103,8 @@ class ConcreteExecutorPool(val maxCount: Int = Settings.defaultConcreteExecutorP * @param TIResult the return type of [Instrumentation.invoke] function for the given [instrumentation]. */ class ConcreteExecutor> private constructor( - internal val instrumentation: TInstrumentation, - internal val pathsToUserClasses: String + val instrumentation: TInstrumentation, + val pathsToUserClasses: String ) : Closeable, Executor { private val ldef: LifetimeDefinition = LifetimeDefinition() private val instrumentedProcessRunner: InstrumentedProcessRunner = InstrumentedProcessRunner() diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceHandler.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceHandler.kt index 6676be0da4..e377adf17a 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceHandler.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceHandler.kt @@ -11,6 +11,7 @@ import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.commons.LocalVariablesSorter +import org.utbot.framework.plugin.api.Instruction sealed class InstructionData { abstract val line: Int @@ -62,7 +63,7 @@ class ProcessingStorage { private val classMethodToId = mutableMapOf() private val idToClassMethod = mutableMapOf() - private val instructionsData = mutableMapOf() + val instructionsData = mutableMapOf() private val classToInstructionsCount = mutableMapOf() fun addClass(className: String): Int { @@ -103,6 +104,18 @@ class ProcessingStorage { return instructionsData.getValue(id) } + fun getMethodInstructions(className: String, methodSignature: String): List = + instructionsData + .filter { + val insClassName = computeClassNameAndLocalId(it.key).first + insClassName == className && it.value.methodSignature == methodSignature + }.map { Instruction(className, it.value.methodSignature, it.value.line, it.key % SHIFT) } + + fun convertToLocalInstruction(instruction: Instruction): Instruction = + with (instruction) { + Instruction(className, methodSignature, lineNumber, id % SHIFT) + } + companion object { private const val SHIFT = 1.toLong().shl(32) // 2 ^ 32 } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceListStrategy.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceListStrategy.kt index 157ea89605..6ffd449bb6 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceListStrategy.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceListStrategy.kt @@ -10,7 +10,7 @@ import org.objectweb.asm.commons.LocalVariablesSorter class TraceListStrategy( private val className: String, - private val storage: ProcessingStorage, + val storage: ProcessingStorage, private val inserter: TraceInstructionBytecodeInserter ) : IInstructionVisitor { var currentLine: Int = 0 diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/FuzzerConcreteExecutor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/FuzzerConcreteExecutor.kt new file mode 100644 index 0000000000..b1434a279c --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/FuzzerConcreteExecutor.kt @@ -0,0 +1,81 @@ +package org.utbot.framework.concrete + +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtInstrumentation +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData +import org.utbot.instrumentation.instrumentation.execution.UtFuzzingConcreteExecutionResult + +class FuzzerConcreteExecutor( + private val pathsToUserClasses: String, +) { + + private val concreteExecutor = + if (UtSettings.greyBoxFuzzingCompetitionMode) { + ConcreteExecutor( + UtFuzzingExecutionInstrumentation, + pathsToUserClasses, + ).apply { this.classLoader = utContext.classLoader } + } else { + ConcreteExecutor( + UtFuzzingExecutionInstrumentationWithStateAfterCollection, + pathsToUserClasses, + ).apply { this.classLoader = utContext.classLoader } + } + + suspend fun execute( + methodUnderTest: ExecutableId, + stateBefore: EnvironmentModels, + instrumentation: List + ): UtFuzzingConcreteExecutionResult { + return if (UtSettings.greyBoxFuzzingCompetitionMode) { + val executor = concreteExecutor as ConcreteExecutor + val executionResult = executor.executeConcretelyFuzz(methodUnderTest, stateBefore, instrumentation) + UtFuzzingConcreteExecutionResult( + null, + executionResult.result, + executionResult.coverage, + executionResult.methodInstructions + ) + } else { + val executor = concreteExecutor as ConcreteExecutor + val executionResult = executor.executeConcretelyFuzzWithStateAfterCollection( + methodUnderTest, + stateBefore, + instrumentation + ) + UtFuzzingConcreteExecutionResult( + executionResult.stateAfter, + executionResult.result, + executionResult.coverage, + executionResult.methodInstructions + ) + } + } + + private suspend fun ConcreteExecutor.executeConcretelyFuzz( + methodUnderTest: ExecutableId, + stateBefore: EnvironmentModels, + instrumentation: List + ): UtFuzzingConcreteExecutionResult = executeAsync( + methodUnderTest.classId.name, + methodUnderTest.signature, + arrayOf(), + parameters = UtConcreteExecutionData(stateBefore, instrumentation) + ) + + private suspend fun ConcreteExecutor.executeConcretelyFuzzWithStateAfterCollection( + methodUnderTest: ExecutableId, + stateBefore: EnvironmentModels, + instrumentation: List + ): UtFuzzingConcreteExecutionResult = executeAsync( + methodUnderTest.classId.name, + methodUnderTest.signature, + arrayOf(), + parameters = UtConcreteExecutionData(stateBefore, instrumentation) + ) + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt index 3ab2051f72..accb4f3321 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt @@ -12,6 +12,7 @@ import org.utbot.instrumentation.instrumentation.execution.phases.start import org.utbot.framework.plugin.api.Coverage import org.utbot.framework.plugin.api.EnvironmentModels import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.Instruction import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.framework.plugin.api.UtInstrumentation import org.utbot.framework.plugin.api.UtModel @@ -50,6 +51,12 @@ class UtConcreteExecutionResult( } } +class UtFuzzingConcreteExecutionResult( + val stateAfter: EnvironmentModels?, + val result: UtExecutionResult, + val coverage: Coverage, + val methodInstructions: List? = null +) object UtExecutionInstrumentation : Instrumentation { private val delegateInstrumentation = InvokeInstrumentation() diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentationWithStatsCollection.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentationWithStatsCollection.kt new file mode 100644 index 0000000000..2bb8b0c069 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentationWithStatsCollection.kt @@ -0,0 +1,93 @@ +package org.utbot.framework.concrete + +import org.utbot.framework.plugin.api.* +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.et.ExplicitThrowInstruction +import org.utbot.instrumentation.instrumentation.et.TraceHandler +import org.utbot.instrumentation.instrumentation.execution.UtFuzzingConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.constructors.ConstructOnlyUserClassesOrCachedObjectsStrategy +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor +import org.utbot.instrumentation.instrumentation.execution.mock.InstrumentationContext +import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter +import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor +import java.security.AccessControlException +import java.security.ProtectionDomain +import java.util.* +import kotlin.reflect.jvm.javaMethod + +interface UtExecutionInstrumentationWithStatsCollection : Instrumentation { + + val delegateInstrumentation: InvokeInstrumentation + + val instrumentationContext: InstrumentationContext + + val traceHandler: TraceHandler + val pathsToUserClasses: MutableSet + override fun init(pathsToUserClasses: Set) { + this.pathsToUserClasses.clear() + this.pathsToUserClasses += pathsToUserClasses + } + + override fun getStaticField(fieldId: FieldId): Result = + delegateInstrumentation.getStaticField(fieldId).map { value -> + val cache = IdentityHashMap() + val strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy( + pathsToUserClasses, cache + ) + UtModelConstructor(cache, strategy).run { + construct(value, fieldId.type) + } + } + + + fun sortOutException(exception: Throwable): UtExecutionFailure { + if (exception is TimeoutException) { + return UtTimeoutException(exception) + } + if (exception is AccessControlException) { + return UtSandboxFailure(exception) + } + val instrs = traceHandler.computeInstructionList() + val isNested = if (instrs.isEmpty()) { + false + } else { + instrs.first().callId != instrs.last().callId + } + return if (instrs.isNotEmpty() && instrs.last().instructionData is ExplicitThrowInstruction) { + UtExplicitlyThrownException(IllegalStateException(exception::javaClass.name), isNested) + } else { + UtImplicitlyThrownException(IllegalStateException(exception::javaClass.name), isNested) + } + + } + + override fun transform( + loader: ClassLoader?, + className: String, + classBeingRedefined: Class<*>?, + protectionDomain: ProtectionDomain, + classfileBuffer: ByteArray + ): ByteArray { + val instrumenter = Instrumenter(classfileBuffer, loader) + + traceHandler.registerClass(className) + instrumenter.visitInstructions(traceHandler.computeInstructionVisitor(className)) + + val mockClassVisitor = instrumenter.visitClass { writer -> + MockClassVisitor( + writer, + InstrumentationContext.MockGetter::getMock.javaMethod!!, + InstrumentationContext.MockGetter::checkCallSite.javaMethod!!, + InstrumentationContext.MockGetter::hasMock.javaMethod!! + ) + } + + mockClassVisitor.signatureToId.forEach { (method, id) -> + instrumentationContext.methodSignatureToId += method to id + } + + return instrumenter.classByteCode + } + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtFuzzingExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtFuzzingExecutionInstrumentation.kt new file mode 100644 index 0000000000..7020822a61 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtFuzzingExecutionInstrumentation.kt @@ -0,0 +1,211 @@ +package org.utbot.framework.concrete + +import org.objectweb.asm.Type +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.* +import org.utbot.instrumentation.instrumentation.ArgumentList +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.et.TraceHandler +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData +import org.utbot.instrumentation.instrumentation.execution.UtFuzzingConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.constructors.ConstructOnlyUserClassesOrCachedObjectsStrategy +import org.utbot.instrumentation.instrumentation.execution.mock.InstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.phases.PhaseError +import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController +import org.utbot.instrumentation.instrumentation.execution.phases.start +import java.security.AccessControlException + +object UtFuzzingExecutionInstrumentation : UtExecutionInstrumentationWithStatsCollection { + + override val delegateInstrumentation: InvokeInstrumentation = InvokeInstrumentation() + + override val instrumentationContext: InstrumentationContext = InstrumentationContext() + + override val traceHandler: TraceHandler = TraceHandler() + override val pathsToUserClasses: MutableSet = mutableSetOf() + + override fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any? + ): UtFuzzingConcreteExecutionResult { + if (parameters !is UtConcreteExecutionData) { + throw IllegalArgumentException("Argument parameters must be of type UtConcreteExecutionData, but was: ${parameters?.javaClass}") + } + val (stateBefore, instrumentations, timeout) = parameters // smart cast to UtConcreteExecutionData + return PhasesController( + instrumentationContext, + traceHandler, + delegateInstrumentation, + generateNullOnError = true + ).computeFuzzingConcreteExecutionResult { + // construction + val (params, statics, cache) = valueConstructionContext.start { + val params = constructParameters(stateBefore) + val statics = constructStatics(stateBefore) + + mock(instrumentations) + + Triple(params, statics, getCache()) + } + + // preparation + val savedStatics = preparationContext.start { + val savedStatics = setStaticFields(statics) + resetTrace() + savedStatics + } + + try { + // invocation + val concreteResult = invocationContext.start { + invoke(clazz, methodSignature, params.map { it.value }, timeout) + } + + // statistics collection + val coverage = statisticsCollectionContext.start { getCoverage(clazz) }.toLocalCoverage(traceHandler) + val classJVMName = Type.getInternalName(clazz) + val methodInstructions = + traceHandler.processingStorage.getMethodInstructions(classJVMName, methodSignature) + + val concreteUtModelResult = concreteResult.fold({ + UtExecutionSuccess(UtNullModel(it?.javaClass?.id ?: objectClassId)) + }) { + sortOutException(it) + } + + + UtFuzzingConcreteExecutionResult( + null, + concreteUtModelResult, + coverage, + methodInstructions + ) + + } finally { + // postprocessing + postprocessingContext.start { + resetStaticFields(savedStatics) + } + } + } + } +} + +object UtFuzzingExecutionInstrumentationWithStateAfterCollection : UtExecutionInstrumentationWithStatsCollection { + + override val delegateInstrumentation: InvokeInstrumentation = InvokeInstrumentation() + + override val instrumentationContext: InstrumentationContext = InstrumentationContext() + + override val traceHandler: TraceHandler = TraceHandler() + override val pathsToUserClasses: MutableSet = mutableSetOf() + + override fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any? + ): UtFuzzingConcreteExecutionResult { + if (parameters !is UtConcreteExecutionData) { + throw IllegalArgumentException("Argument parameters must be of type UtConcreteExecutionData, but was: ${parameters?.javaClass}") + } + val (stateBefore, instrumentations, timeout) = parameters // smart cast to UtConcreteExecutionData + return PhasesController( + instrumentationContext, + traceHandler, + delegateInstrumentation, + generateNullOnError = true + ).computeFuzzingConcreteExecutionResult { + // construction + val (params, statics, cache) = valueConstructionContext.start { + val params = constructParameters(stateBefore) + val statics = constructStatics(stateBefore) + + mock(instrumentations) + + Triple(params, statics, getCache()) + } + + // preparation + val savedStatics = preparationContext.start { + val savedStatics = setStaticFields(statics) + resetTrace() + savedStatics + } + + try { + // invocation + val concreteResult = invocationContext.start { + invoke(clazz, methodSignature, params.map { it.value }, timeout) + } + + // statistics collection + val coverage = statisticsCollectionContext.start { getCoverage(clazz) }.toLocalCoverage(traceHandler) + val classJVMName = Type.getInternalName(clazz) + val methodInstructions = + traceHandler.processingStorage.getMethodInstructions(classJVMName, methodSignature) + + val methodId = clazz.singleExecutableId(methodSignature) + val returnClassId = methodId.returnType + // model construction + val (executionResult, stateAfter) = modelConstructionContext.start { + configureConstructor { + this.cache = cache + strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy(pathsToUserClasses, cache) + } + + val executionResult = convertToExecutionResult(concreteResult, returnClassId) + + val stateAfterParametersWithThis = constructParameters(params) + val stateAfterStatics = constructStatics(stateBefore, statics) + val (stateAfterThis, stateAfterParameters) = if (stateBefore.thisInstance == null) { + null to stateAfterParametersWithThis + } else { + stateAfterParametersWithThis.first() to stateAfterParametersWithThis.drop(1) + } + val stateAfter = EnvironmentModels(stateAfterThis, stateAfterParameters, stateAfterStatics) + + executionResult to stateAfter + } + UtFuzzingConcreteExecutionResult( + stateAfter, + executionResult, + coverage, + methodInstructions + ) + } finally { + // postprocessing + postprocessingContext.start { + resetStaticFields(savedStatics) + } + } + } + } +} + +private fun Coverage.toLocalCoverage(traceHandler: TraceHandler) = + Coverage( + coveredInstructions.map { traceHandler.processingStorage.convertToLocalInstruction(it) }, + instructionsCount, + missedInstructions.map { traceHandler.processingStorage.convertToLocalInstruction(it) } + ) + +private inline fun PhasesController.computeFuzzingConcreteExecutionResult(block: PhasesController.() -> UtFuzzingConcreteExecutionResult): UtFuzzingConcreteExecutionResult { + return use { + try { + block() + } catch (e: PhaseError) { + if (e.cause.cause is AccessControlException) { + return@use UtFuzzingConcreteExecutionResult( + MissingState, + UtSandboxFailure(e.cause.cause!!), + Coverage(), + null + ) + } + throw e + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BigNumberConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BigNumberConstructor.kt new file mode 100644 index 0000000000..63bcec156e --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BigNumberConstructor.kt @@ -0,0 +1,31 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.stringClassId + +internal class BigNumberConstructor : UtAssembleModelConstructorBase() { + + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, + value: Any, + classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + + val stringValue = value.toString() + val stringValueModel = internalConstructor.construct(stringValue, stringClassId) + + return UtExecutableCallModel( + instance = null, + ConstructorId(classId, listOf(stringClassId)), + listOf(stringValueModel), + + ) + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List = emptyList() +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BitSetConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BitSetConstructor.kt new file mode 100644 index 0000000000..e588887eb3 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BitSetConstructor.kt @@ -0,0 +1,36 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.longArrayClassId +import org.utbot.framework.plugin.api.util.methodId + +internal class BitSetConstructor : UtAssembleModelConstructorBase() { + + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, + value: Any, + classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.util.BitSet + return with(internalConstructor) { + UtExecutableCallModel( + instance = null, + methodId(classId, "valueOf", classId, longArrayClassId), + listOf( + construct(value.toLongArray(), longArrayClassId), + ), + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List = emptyList() + +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/DateTimeConstructors.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/DateTimeConstructors.kt new file mode 100644 index 0000000000..d40f9cff2b --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/DateTimeConstructors.kt @@ -0,0 +1,433 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.* + +internal class InstantConstructor : UtAssembleModelConstructorBase() { + + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + + checkClassCast(classId.jClass, value::class.java) + value as java.time.Instant + val seconds = value.epochSecond + val nanos = value.nano.toLong() + + val secondsModel = internalConstructor.construct(seconds, longClassId) + val nanosModel = internalConstructor.construct(nanos, longClassId) + + + return UtExecutableCallModel( + instance = null, + methodId(classId, "ofEpochSecond", classId, longClassId, longClassId), + listOf(secondsModel, nanosModel), + ) + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() +} + +internal class DurationConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.Duration + val seconds = value.seconds + val nanos = value.nano.toLong() + + val secondsModel = internalConstructor.construct(seconds, longClassId) + val nanosModel = internalConstructor.construct(nanos, longClassId) + + + return UtExecutableCallModel( + instance = null, + methodId(classId, "ofSeconds", classId, longClassId, longClassId), + listOf(secondsModel, nanosModel), + + ) + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class LocalDateConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.LocalDate + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "of", classId, intClassId, intClassId, intClassId), + listOf( + construct(value.year, intClassId), + construct(value.monthValue, intClassId), + construct(value.dayOfMonth, intClassId) + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class LocalTimeConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.LocalTime + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "of", classId, intClassId, intClassId, intClassId, intClassId), + listOf( + construct(value.hour, intClassId), + construct(value.minute, intClassId), + construct(value.second, intClassId), + construct(value.nano, intClassId) + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class LocalDateTimeConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.LocalDateTime + val timeClassId = java.time.LocalTime::class.java.id + val dateClassId = java.time.LocalTime::class.java.id + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "of", classId, dateClassId, timeClassId), + listOf( + construct(value.toLocalDate(), dateClassId), + construct(value.toLocalTime(), timeClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class ZoneIdConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.ZoneId + val id = value.id + + val idModel = internalConstructor.construct(id, stringClassId) + + + return UtExecutableCallModel( + instance = null, methodId(classId, "of", classId, stringClassId), listOf(idModel) + ) + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class MonthDayConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.MonthDay + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "of", classId, intClassId, intClassId), + listOf( + construct(value.monthValue, intClassId), + construct(value.dayOfMonth, intClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class YearConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.Year + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "of", classId, intClassId), + listOf( + construct(value.value, intClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class YearMonthConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.YearMonth + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "of", classId, intClassId, intClassId), + listOf( + construct(value.year, intClassId), + construct(value.monthValue, intClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class PeriodConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.Period + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "of", classId, intClassId, intClassId, intClassId), + listOf( + construct(value.years, intClassId), + construct(value.months, intClassId), + construct(value.days, intClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class ZoneOffsetConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.ZoneOffset + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "ofTotalSeconds", classId, intClassId), + listOf( + construct(value.totalSeconds, intClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class OffsetTimeConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.OffsetTime + val timeClassId = java.time.LocalTime::class.java.id + val offsetClassId = java.time.ZoneOffset::class.java.id + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "of", classId, timeClassId, offsetClassId), + listOf( + construct(value.toLocalTime(), timeClassId), + construct(value.offset, offsetClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class OffsetDateTimeConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.OffsetDateTime + val dateTimeClassId = java.time.LocalDateTime::class.java.id + val offsetClassId = java.time.ZoneOffset::class.java.id + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "of", classId, dateTimeClassId, offsetClassId), + listOf( + construct(value.toLocalDateTime(), dateTimeClassId), + construct(value.offset, offsetClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class ZonedDateTimeConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.time.ZonedDateTime + val dateTimeClassId = java.time.LocalDateTime::class.java.id + val offsetClassId = java.time.ZoneOffset::class.java.id + val zoneClassId = java.time.ZoneId::class.java.id + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "ofLenient", classId, dateTimeClassId, offsetClassId, zoneClassId), + listOf( + construct(value.toLocalDateTime(), dateTimeClassId), + construct(value.offset, offsetClassId), + construct(value.zone, zoneClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class DateConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.util.Date + val instantClassId = java.time.Instant::class.java.id + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "from", classId, instantClassId), + listOf( + construct(value.toInstant(), instantClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} + +internal class TimeZoneConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, value: Any, classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as java.util.TimeZone + val zoneClassId = java.time.ZoneId::class.java.id + + with(internalConstructor) { + return UtExecutableCallModel( + instance = null, + methodId(classId, "getTimeZone", classId, zoneClassId), + listOf( + construct(value.toZoneId(), zoneClassId), + ), + + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, value: Any + ): List = emptyList() + +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/LocaleConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/LocaleConstructor.kt new file mode 100644 index 0000000000..6626151846 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/LocaleConstructor.kt @@ -0,0 +1,36 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.methodId +import org.utbot.framework.plugin.api.util.stringClassId +import java.util.* + +internal class LocaleConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, + value: Any, + classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as Locale + + return with(internalConstructor) { + UtExecutableCallModel( + instance = null, + methodId(classId, "forLanguageTag", classId, stringClassId), + listOf( + construct(value.toLanguageTag(), stringClassId), + ), + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List = emptyList() +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt index ba6f49c717..3d821248b2 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt @@ -67,7 +67,8 @@ import org.utbot.instrumentation.process.runSandbox */ // TODO: JIRA:1379 -- Refactor ValueConstructor and MockValueConstructor class MockValueConstructor( - private val instrumentationContext: InstrumentationContext + private val instrumentationContext: InstrumentationContext, + private val generateNullOnError: Boolean = false ) : Closeable { private val classLoader: ClassLoader get() = utContext.classLoader @@ -133,7 +134,7 @@ class MockValueConstructor( is UtClassRefModel -> UtConcreteValue(model.value) is UtCompositeModel -> UtConcreteValue(constructObject(model), model.classId.jClass) is UtArrayModel -> UtConcreteValue(constructArray(model)) - is UtAssembleModel -> UtConcreteValue(constructFromAssembleModel(model), model.classId.jClass) + is UtAssembleModel -> constructConcreteValueFromAssembleModel(model) is UtLambdaModel -> UtConcreteValue(constructFromLambdaModel(model)) is UtVoidModel -> UtConcreteValue(Unit) // PythonModel, JsUtModel may be here @@ -141,6 +142,22 @@ class MockValueConstructor( } } + /** + * Constructs Concrete value from Assemble model. + * + * In fuzzing mode we are ignoring exceptions because concrete values as nulls are possible. + */ + private fun constructConcreteValueFromAssembleModel(model: UtAssembleModel): UtConcreteValue<*> = + try { + UtConcreteValue(constructFromAssembleModel(model), model.classId.jClass) + } catch (e: Throwable) { + if (generateNullOnError) { + UtConcreteValue(null, model.classId.jClass) + } else { + throw e + } + } + /** * Constructs an Enum<*> instance by model, uses reference-equality cache. */ diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UUIDConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UUIDConstructor.kt new file mode 100644 index 0000000000..a61121c5e0 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UUIDConstructor.kt @@ -0,0 +1,35 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.* +import java.util.* + +internal class UUIDConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, + value: Any, + classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + value as UUID + + return with(internalConstructor) { + UtExecutableCallModel( + instance = null, + constructorId(classId, longClassId, longClassId), + listOf( + construct(value.mostSignificantBits, longClassId), + construct(value.leastSignificantBits, longClassId), + ), + ) + } + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List = emptyList() +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtAssembleModelConstructors.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtAssembleModelConstructors.kt index 39576b949b..2850cf5632 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtAssembleModelConstructors.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtAssembleModelConstructors.kt @@ -76,6 +76,31 @@ private val predefinedConstructors = mutableMapOf, () -> UtAssembleMode /** * TODO: JIRA:1405 -- Add assemble constructors for another standard classes as well. */ + java.math.BigInteger::class.java.let { it to { BigNumberConstructor() } }, + java.math.BigDecimal::class.java.let { it to { BigNumberConstructor() } }, + + java.util.BitSet::class.java.let { it to { BitSetConstructor() } }, + java.util.UUID::class.java.let { it to { UUIDConstructor() } }, + java.util.Locale::class.java.let { it to { LocaleConstructor() } }, + java.util.Date::class.java.let { it to { DateConstructor() } }, + java.util.TimeZone::class.java.let { it to { TimeZoneConstructor() } }, + + java.time.Instant::class.java.let { it to { InstantConstructor() } }, + java.time.Duration::class.java.let { it to { DurationConstructor() } }, + java.time.ZoneId::class.java.let { it to { ZoneIdConstructor() } }, + java.time.LocalDate::class.java.let { it to { LocalDateConstructor() } }, + java.time.LocalTime::class.java.let { it to { LocalTimeConstructor() } }, + java.time.LocalDateTime::class.java.let { it to { LocalDateTimeConstructor() } }, + java.time.MonthDay::class.java.let { it to { MonthDayConstructor() } }, + java.time.Year::class.java.let { it to { YearConstructor() } }, + java.time.YearMonth::class.java.let { it to { YearMonthConstructor() } }, + java.time.Period::class.java.let { it to { PeriodConstructor() } }, + java.time.ZoneOffset::class.java.let { it to { ZoneOffsetConstructor() } }, + java.time.OffsetTime::class.java.let { it to { OffsetTimeConstructor() } }, + java.time.OffsetDateTime::class.java.let { it to { OffsetDateTimeConstructor() } }, + java.time.ZonedDateTime::class.java.let { it to { ZonedDateTimeConstructor() } }, + + ).apply { /** * Primitive wrappers diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt index f020382c85..d9942f4132 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt @@ -68,7 +68,7 @@ class UtModelConstructor( .mapNotNull { it.id } .toMutableSet() - private fun computeUnusedIdAndUpdate(): Int { + fun computeUnusedIdAndUpdate(): Int { while (unusedId in usedIds) { unusedId++ } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt index 58617a3a61..cfa1163c11 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt @@ -14,9 +14,10 @@ class PhasesController( instrumentationContext: InstrumentationContext, traceHandler: TraceHandler, delegateInstrumentation: Instrumentation>, + generateNullOnError: Boolean = false ) : Closeable { - val valueConstructionContext = ValueConstructionContext(instrumentationContext) + val valueConstructionContext = ValueConstructionContext(instrumentationContext, generateNullOnError) val preparationContext = PreparationContext(traceHandler) diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionContext.kt index aaa5732f25..cc46bcc9fe 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionContext.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionContext.kt @@ -22,13 +22,14 @@ class ValueConstructionPhaseError(cause: Throwable) : PhaseError( * This phase of values instantiation from given models. */ class ValueConstructionContext( - instrumentationContext: InstrumentationContext + instrumentationContext: InstrumentationContext, + generateNullOnError: Boolean = false ) : PhaseContext, Closeable { override fun wrapError(error: Throwable): ValueConstructionPhaseError = ValueConstructionPhaseError(error) - private val constructor = MockValueConstructor(instrumentationContext) + private val constructor = MockValueConstructor(instrumentationContext, generateNullOnError) fun getCache(): IdentityHashMap { return constructor.objectToModelCache diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessRunner.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessRunner.kt index 327ef53437..6a5a302105 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessRunner.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessRunner.kt @@ -19,11 +19,13 @@ class InstrumentedProcessRunner { private val cmds: List by lazy { val debugCmd = listOfNotNull(DEBUG_RUN_CMD.takeIf { UtSettings.runInstrumentedProcessWithDebug }) val javaVersionSpecificArguments = OpenModulesContainer.javaVersionSpecificArguments + val memoryLimit = listOf("-Xmx1g") val pathToJava = JdkInfoService.provide().path listOf(pathToJava.resolve("bin${File.separatorChar}${osSpecificJavaExecutable()}").toString()) + debugCmd + javaVersionSpecificArguments + + memoryLimit + listOf("-javaagent:$jarFile", "-ea", "-jar", "$jarFile") } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt index 1d05fe6c5b..f37bbbd1f4 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt @@ -271,6 +271,7 @@ object UtTestsDialogProcessor { true, UtSettings.useFuzzing, project.service().fuzzingValue, + UtSettings.useGreyBoxFuzzing, searchDirectory.pathString ) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt index eef1a9608b..c72545a691 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt @@ -258,6 +258,7 @@ class EngineProcess private constructor(val project: Project, private val classN isSymbolicEngineEnabled: Boolean, isFuzzingEnabled: Boolean, fuzzingValue: Double, + isGreyBoxFuzzingEnabled: Boolean, searchDirectory: String ): RdTestGenerationResult { assertReadAccessNotAllowed() @@ -273,6 +274,7 @@ class EngineProcess private constructor(val project: Project, private val classN isSymbolicEngineEnabled, isFuzzingEnabled, fuzzingValue, + isGreyBoxFuzzingEnabled, searchDirectory ) val result = engineModel.generate.startBlocking(params) diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt index c41a617309..0505a259d8 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt @@ -147,15 +147,26 @@ fun main(args: Array) { val timeBudgetSec = cmd[2].toLong() val cut = ClassUnderTest(classLoader.loadClass(classUnderTestName).id, outputDir, classfileDir.toFile()) - runGeneration( - project = "Contest", - cut, - timeBudgetSec, - fuzzingRatio = 0.1, - classpathString, - runFromEstimator = false, - methodNameFilter = null - ) + if (UtSettings.useGreyBoxFuzzing) { + GreyBoxFuzzerContest().runGeneration( + project = "GreyBoxFuzzingContest", + cut, + timeBudgetSec, + classpathString, + runFromEstimator = false, + methodNameFilter = null + ) + } else { + runGeneration( + project = "Contest", + cut, + timeBudgetSec, + fuzzingRatio = 0.1, + classpathString, + runFromEstimator = false, + methodNameFilter = null + ) + } println("${ContestMessage.READY}") } } @@ -415,7 +426,7 @@ fun runGeneration( statsForClass } -private fun prepareClass(javaClazz: Class<*>, methodNameFilter: String?): List { +internal fun prepareClass(javaClazz: Class<*>, methodNameFilter: String?): List { //1. all methods from cut val methods = javaClazz.declaredMethods .filterNot { it.isAbstract } @@ -493,7 +504,7 @@ internal val Method.isVisibleFromGeneratedTest: Boolean get() = (this.modifiers and Modifier.ABSTRACT) == 0 && (this.modifiers and Modifier.NATIVE) == 0 -private fun StatsForClass.updateCoverage(newCoverage: Coverage, isNewClass: Boolean, fromFuzzing: Boolean) { +internal fun StatsForClass.updateCoverage(newCoverage: Coverage, isNewClass: Boolean, fromFuzzing: Boolean) { coverage.update(newCoverage, isNewClass) // other coverage type updates by empty coverage to respect new class val emptyCoverage = newCoverage.copy( diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt index a9fe1a25d7..3c0d359c12 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt @@ -373,7 +373,7 @@ fun runEstimator( if (UtSettings.pathSelectorType == PathSelectorType.ML_SELECTOR || UtSettings.pathSelectorType == PathSelectorType.TORCH_SELECTOR) { Predictors.stateRewardPredictor = EngineAnalyticsContext.mlPredictorFactory() } - + logger.info { "PathSelectorType: ${UtSettings.pathSelectorType}" } if (UtSettings.pathSelectorType == PathSelectorType.ML_SELECTOR || UtSettings.pathSelectorType == PathSelectorType.TORCH_SELECTOR) { logger.info { "RewardModelPath: ${UtSettings.modelPath}" } diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/GreyBoxFuzzerContest.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/GreyBoxFuzzerContest.kt new file mode 100644 index 0000000000..0d961bd878 --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/GreyBoxFuzzerContest.kt @@ -0,0 +1,155 @@ +package org.utbot.contest + +import mu.KotlinLogging +import org.objectweb.asm.Type +import org.utbot.common.bracket +import org.utbot.common.info +import org.utbot.framework.codegen.domain.junitByVersion +import org.utbot.framework.codegen.CodeGenerator +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.services.JdkInfoService +import org.utbot.fuzzer.UtFuzzedExecution +import org.utbot.instrumentation.ConcreteExecutorPool +import org.utbot.instrumentation.warmup.Warmup +import java.net.URLClassLoader +import kotlinx.coroutines.ObsoleteCoroutinesApi +import kotlinx.coroutines.runBlocking +import org.utbot.framework.plugin.api.* +import java.io.File + + +class GreyBoxFuzzerContest { + + private val logger = KotlinLogging.logger {} + + @ObsoleteCoroutinesApi + @SuppressWarnings + fun runGeneration( + project: String, + cut: ClassUnderTest, + timeLimitSec: Long, + classpathString: String, + runFromEstimator: Boolean, + methodNameFilter: String? = null // For debug purposes you can specify method name){} + ): StatsForClass = runBlocking { + val timeBudgetMs = timeLimitSec * 1000 + val generationTimeout: Long = + timeBudgetMs - timeBudgetMs * 15 / 100 // 4000 ms for terminate all activities and finalize code in file + + logger.debug { "-----------------------------------------------------------------------------" } + logger.info( + "Contest.runGeneration: Time budget: $timeBudgetMs ms, Generation timeout=$generationTimeout ms, " + + "classpath=$classpathString, methodNameFilter=$methodNameFilter" + ) + + if (runFromEstimator) { + setOptions() + //will not be executed in real contest + logger.info() + .bracket("warmup: 1st optional soot initialization and executor warmup (not to be counted in time budget)") { + TestCaseGenerator( + listOf(cut.classfileDir.toPath()), + classpathString, + dependencyPath, + JdkInfoService.provide() + ) + } + logger.info().bracket("warmup (first): kotlin reflection :: init") { + prepareClass(ConcreteExecutorPool::class.java, "") + prepareClass(Warmup::class.java, "") + } + } + + logger.info("$cut") + + if (cut.classLoader.javaClass != URLClassLoader::class.java) { + logger.error("Seems like classloader for cut not valid (maybe it was backported to system): ${cut.classLoader}") + } + + val statsForClass = StatsForClass(project, cut.fqn) + + val codeGenerator = CodeGenerator( + cut.classId, + testFramework = junitByVersion(junitVersion), + staticsMocking = staticsMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = false + ) + + logger.info().bracket("class ${cut.fqn}", { statsForClass }) { + + val filteredMethods = logger.info().bracket("preparation class ${cut.clazz}: kotlin reflection :: run") { + prepareClass(cut.clazz, methodNameFilter) + } + + statsForClass.methodsCount = filteredMethods.size + + // nothing to process further + if (filteredMethods.isEmpty()) return@runBlocking statsForClass + + val testCaseGenerator = + logger.info().bracket("2nd optional soot initialization") { + TestCaseGenerator( + listOf(cut.classfileDir.toPath()), + classpathString, + dependencyPath, + JdkInfoService.provide() + ) + } + + val testSet = testCaseGenerator.generate( + filteredMethods, + MockStrategyApi.NO_MOCKS, + methodsGenerationTimeout = generationTimeout + ) + + var testsCounter = 0 + for (test in testSet) { + val method = test.method + val statsForMethod = StatsForMethod("${method.classId.simpleName}#${method.name}") + statsForClass.statsForMethods.add(statsForMethod) + for (result in test.executions) { + try { + val testMethodName = testMethodName(method.toString(), ++testsCounter) + val className = Type.getInternalName(method.classId.jClass) + logger.debug { "--new testCase collected, to generate: $testMethodName" } + statsForMethod.testsGeneratedCount++ + result.coverage?.let { + statsForClass.updateCoverage( + newCoverage = it, + isNewClass = !statsForClass.testedClassNames.contains(className), + fromFuzzing = result is UtFuzzedExecution + ) + } + statsForClass.testedClassNames.add(className) + } catch (e: Throwable) { + //Here we need isolation + logger.error(e) { "Code generation failed" } + } + } + } + + cut.generatedTestFile.parentFile.mkdirs() + for ((i, test) in testSet.withIndex()) { + val newClassName = "${cut.generatedTestFile.nameWithoutExtension}$i" + val newClassnamePath = cut.generatedTestFile.absolutePath.let { + val pathWithoutExtension = it.substringBeforeLast(".") + val extension = cut.generatedTestFile.extension + "$pathWithoutExtension$i.$extension" + } + val testAsString = codeGenerator.generateAsString(listOf(test), newClassName) + File(newClassnamePath).writeText(testAsString) + cut.generatedTestFile.writeText(testAsString, charset) + } + if (cut.generatedTestFile.exists()) { + cut.generatedTestFile.delete() + } + +// logger.info().bracket("Flushing tests for [${cut.simpleName}] on disk") { +// writeTestClass(cut, codeGenerator.generateAsString(testSet)) +// } + } + statsForClass + } + +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt index 7dc3fed93a..edfdc85e44 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt @@ -47,6 +47,7 @@ import org.utbot.framework.codegen.domain.models.CgStatement import org.utbot.framework.codegen.domain.models.CgSwitchCase import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgMockMethod import org.utbot.framework.codegen.domain.models.CgThisInstance import org.utbot.framework.codegen.domain.models.CgTripleSlashMultilineComment import org.utbot.framework.codegen.domain.models.CgTryCatch @@ -310,6 +311,8 @@ internal class CgPythonRenderer( print(")") } + override fun renderMethodSignature(element: CgMockMethod): Unit = renderMethodSignature(element) + override fun renderMethodSignature(element: CgErrorTestMethod) { print("def ") print(element.name) diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt index 1c89ef0312..fabaeb9215 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt @@ -55,6 +55,7 @@ object EngineProcessModel : Ext(EngineProcessRoot) { field("isSymbolicEngineEnabled", PredefinedType.bool) field("isFuzzingEnabled", PredefinedType.bool) field("fuzzingValue", PredefinedType.double) + field("isGreyBoxFuzzingEnabled", PredefinedType.bool) // method filters field("searchDirectory", PredefinedType.string) }