From e735107738514e736a223b94b387e5e5a49ae701 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 17 Sep 2023 19:09:25 +0200 Subject: [PATCH 001/128] Descriptors generated for all terms. Working on MockDescriptor --- .../research/kex/descriptor/descriptor.kt | 151 +++++++++++++++--- .../research/kex/parameters/Parameters.kt | 18 ++- .../research/kex/serialization/descriptor.kt | 9 +- .../research/kex/reanimator/Reanimator.kt | 13 +- .../state/transformer/AbstractGenerator.kt | 17 ++ .../state/transformer/DescriptorGenerator.kt | 20 ++- .../kex/trace/runner/ObjectTracingRunner.kt | 9 +- 7 files changed, 195 insertions(+), 42 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index f133b1bd3..20bd53316 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -65,7 +65,11 @@ sealed class Descriptor(term: Term, type: KexType) { infix fun neq(other: Descriptor) = !(this eq other) abstract fun print(map: MutableMap): String - abstract fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean + abstract fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean + abstract fun collectQuery(set: MutableSet): PredicateState abstract fun countDepth(visited: Set, cache: MutableMap): Int @@ -96,7 +100,8 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty override fun generateTypeInfo(visited: MutableSet) = emptyState() override fun countDepth(visited: Set, cache: MutableMap) = 1 - override fun contains(other: Descriptor, visited: MutableSet): Boolean = this.term == other.term + override fun contains(other: Descriptor, visited: MutableSet): Boolean = + this.term == other.term object Null : ConstantDescriptor(term { generate(KexNull()) }, KexNull()) { override fun print(map: MutableMap) = "$term = null" @@ -105,7 +110,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty require { term equality null } } - override fun structuralEquality(other: Descriptor, map: MutableSet>) = + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ) = other is Null } @@ -116,7 +124,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty require { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Bool) return false return this.value == other.value } @@ -129,7 +140,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty require { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Byte) return false return this.value == other.value } @@ -142,20 +156,27 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty require { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Char) return false return this.value == other.value } } - class Short(val value: kotlin.Short) : ConstantDescriptor(term { generate(KexShort) }, KexShort) { + class Short(val value: kotlin.Short) : + ConstantDescriptor(term { generate(KexShort) }, KexShort) { override fun print(map: MutableMap) = "$term = $value" override fun collectQuery(set: MutableSet): PredicateState = basic { require { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Short) return false return this.value == other.value } @@ -168,7 +189,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty require { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Int) return false return this.value == other.value } @@ -181,39 +205,98 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty require { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Long) return false return this.value == other.value } } - class Float(val value: kotlin.Float) : ConstantDescriptor(term { generate(KexFloat) }, KexFloat) { + class Float(val value: kotlin.Float) : + ConstantDescriptor(term { generate(KexFloat) }, KexFloat) { override fun print(map: MutableMap) = "$term = $value" override fun collectQuery(set: MutableSet): PredicateState = basic { require { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Float) return false return this.value == other.value } } - class Double(val value: kotlin.Double) : ConstantDescriptor(term { generate(KexDouble) }, KexDouble) { + class Double(val value: kotlin.Double) : + ConstantDescriptor(term { generate(KexDouble) }, KexDouble) { override fun print(map: MutableMap) = "$term = $value" override fun collectQuery(set: MutableSet): PredicateState = basic { require { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Double) return false return this.value == other.value } } } +/*class MockDescriptor(term: Term, type: KexType) : Descriptor(term, type) { + override fun contains(other: Descriptor, visited: MutableSet): Boolean { + TODO("Not yet implemented") + } + + override fun print(map: MutableMap): String { + TODO("Not yet implemented") + } + + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { + TODO("Not yet implemented") + } + + override fun collectQuery(set: MutableSet): PredicateState { + TODO("Not yet implemented") + } + + override fun countDepth(visited: Set, cache: MutableMap): Int { + TODO("Not yet implemented") + } + + override fun concretize( + cm: ClassManager, + accessLevel: AccessModifier, + random: Random, + visited: MutableSet + ): Descriptor { + TODO("Not yet implemented") + } + + override fun deepCopy(copied: MutableMap): Descriptor { + return this; + } + + override fun reduce(visited: MutableSet): Descriptor { + visited.add(this); + return this; + } + + override fun generateTypeInfo(visited: MutableSet): PredicateState { + TODO("Not yet implemented") + } + +}*/ + @Suppress("UNCHECKED_CAST") sealed class FieldContainingDescriptor>( term: Term, @@ -337,7 +420,10 @@ sealed class FieldContainingDescriptor>( val typeInfo = field.generateTypeInfo(visited) if (typeInfo.isNotEmpty) { state { - field.term equality this@FieldContainingDescriptor.term.field(key.second, key.first).load() + field.term equality this@FieldContainingDescriptor.term.field( + key.second, + key.first + ).load() } append(typeInfo) } @@ -345,7 +431,10 @@ sealed class FieldContainingDescriptor>( } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (this == other) return true if (other !is FieldContainingDescriptor<*>) return false if (this to other in map) return true @@ -407,7 +496,8 @@ class ClassDescriptor(type: KexClass) : val typeInfo = field.generateTypeInfo(visited) if (typeInfo.isNotEmpty) { state { - field.term equality this@ClassDescriptor.term.field(key.second, key.first).load() + field.term equality this@ClassDescriptor.term.field(key.second, key.first) + .load() } append(typeInfo) } @@ -441,6 +531,23 @@ class ClassDescriptor(type: KexClass) : } } +class MockDescriptor(klass: KexClass) : + FieldContainingDescriptor(term { generate(klass) }, klass) { + override fun concretize( + cm: ClassManager, + accessLevel: AccessModifier, + random: Random, + visited: MutableSet + ): ClassDescriptor { + // TODO: generate implementation class + return super.concretize(cm, accessLevel, random, visited) + } + + override fun deepCopy(copied: MutableMap): Descriptor { + TODO("Not yet implemented") + } +} + class ArrayDescriptor(val elementType: KexType, val length: Int) : Descriptor(term { generate(KexArray(elementType)) }, KexArray(elementType)) { val elements = mutableMapOf() @@ -547,7 +654,10 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (this == other) return true if (other !is ArrayDescriptor) return false if (this to other in map) return true @@ -610,7 +720,8 @@ open class DescriptorBuilder : StringInfoContext() { fun const(klass: KexClass) = ClassDescriptor(klass) fun `object`(type: KexClass): ObjectDescriptor = ObjectDescriptor(type) - fun array(length: Int, elementType: KexType): ArrayDescriptor = ArrayDescriptor(elementType, length) + fun array(length: Int, elementType: KexType): ArrayDescriptor = + ArrayDescriptor(elementType, length) fun default(type: KexType, nullable: Boolean): Descriptor = descriptor { when (type) { @@ -696,6 +807,8 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde } arrayDesc } + + is MockDescriptor -> TODO("Not implemented") } } } diff --git a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index f109fe0f8..0e79c1c5a 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -16,9 +16,10 @@ import kotlin.random.Random data class Parameters( val instance: T?, val arguments: List, - val statics: Set = setOf() + val statics: Set = setOf(), + val others: Set = setOf() ) { - val asList get() = listOfNotNull(instance) + arguments + statics + val asList get() = listOfNotNull(instance) + arguments + statics + others override fun toString(): String = buildString { appendLine("instance: $instance") @@ -26,6 +27,9 @@ data class Parameters( appendLine("args: ${arguments.joinToString("\n")}") if (statics.isNotEmpty()) appendLine("statics: ${statics.joinToString("\n")}") + if (others.isNotEmpty()) { + appendLine("others: ${others.joinToString("\n")}") + } } } @@ -35,7 +39,8 @@ val Parameters.asDescriptors: Parameters return Parameters( context.convert(instance), arguments.map { context.convert(it) }, - statics.mapTo(mutableSetOf()) { context.convert(it) } + statics.mapTo(mutableSetOf()) { context.convert(it) }, + others.mapTo(mutableSetOf()) { context.convert(it) } ) } @@ -46,7 +51,8 @@ fun Parameters.concreteParameters( ) = Parameters( instance?.concretize(cm, accessLevel, random), arguments.map { it.concretize(cm, accessLevel, random) }, - statics.mapTo(mutableSetOf()) { it.concretize(cm, accessLevel, random) } + statics.mapTo(mutableSetOf()) { it.concretize(cm, accessLevel, random) }, + others.mapTo(mutableSetOf()) { it.concretize(cm, accessLevel, random) } ) fun Parameters.filterStaticFinals(cm: ClassManager): Parameters { @@ -64,7 +70,7 @@ fun Parameters.filterStaticFinals(cm: ClassManager): Parameters null } } - return Parameters(instance, arguments, filteredStatics) + return Parameters(instance, arguments, filteredStatics, others) } private val ignoredStatics: Set by lazy { @@ -84,7 +90,7 @@ fun Parameters.filterIgnoredStatic(): Parameters { !ignored.isParent(typeName) } } - return Parameters(instance, arguments, filteredStatics) + return Parameters(instance, arguments, filteredStatics, others) } diff --git a/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt b/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt index 1a167bf48..a11a88e5d 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt @@ -10,13 +10,7 @@ import kotlinx.serialization.descriptors.element import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import org.vorpal.research.kex.descriptor.ArrayDescriptor -import org.vorpal.research.kex.descriptor.ClassDescriptor -import org.vorpal.research.kex.descriptor.ConstantDescriptor -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.FieldContainingDescriptor -import org.vorpal.research.kex.descriptor.ObjectDescriptor -import org.vorpal.research.kex.descriptor.descriptor +import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexArray import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexType @@ -169,6 +163,7 @@ private fun Descriptor.toWrapper(visited: MutableMap) { field.toWrapper(visited) } } + is MockDescriptor -> TODO ("not implemented") } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/Reanimator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/Reanimator.kt index af6a5173a..7e8be513d 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/Reanimator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/Reanimator.kt @@ -35,7 +35,8 @@ val Parameters.rtMapped: Parameters val instance = instance?.let { mapper.map(it) } val args = arguments.map { mapper.map(it) } val statics = statics.mapTo(mutableSetOf()) { mapper.map(it) } - return Parameters(instance, args, statics) + val others = others.mapTo(mutableSetOf()) { mapper.map(it) } + return Parameters(instance, args, statics, others) } val Parameters.rtUnmapped: Parameters @@ -44,7 +45,8 @@ val Parameters.rtUnmapped: Parameters val instance = instance?.let { mapper.map(it) } val args = arguments.map { mapper.map(it) } val statics = statics.mapTo(mutableSetOf()) { mapper.map(it) } - return Parameters(instance, args, statics) + val others = others.mapTo(mutableSetOf()) { mapper.map(it) } + return Parameters(instance, args, statics, others) } class Reanimator( @@ -65,7 +67,12 @@ class Reanimator( method.klassName ) - override fun generate(testName: String, method: Method, state: PredicateState, model: SMTModel) = try { + override fun generate( + testName: String, + method: Method, + state: PredicateState, + model: SMTModel + ) = try { val descriptors = generateFinalDescriptors(method, ctx, model, state) .filterStaticFinals(cm) .filterIgnoredStatic() diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt index f0defe9c6..640d6a94e 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt @@ -44,10 +44,13 @@ interface AbstractGenerator : Transformer> { var thisTerm: Term? val argTerms: MutableMap val staticFieldOwners: MutableSet + val allTerms: MutableSet + get() = memory.keys val instance get() = thisTerm?.let { memory[it] } val args get() = argTerms.map { memory[it.value] } val staticFields get() = staticFieldOwners.mapTo(mutableSetOf()) { memory[it]!! } + val allValues get() = allTerms.mapTo(mutableSetOf()) { memory[it]!! } // TODO check is !! correct fun generateThis() = thisTerm?.let { memory[it] = modelReanimator.reanimate(it) @@ -57,6 +60,18 @@ interface AbstractGenerator : Transformer> { reanimateTerm(term) } + + fun generateOtherTerms() { + hashSetOf().apply { + addAll(model.assignments.keys) + addAll(model.assignments.values) + model.arrays.values.forEach { addAll(it.keys) } + addAll(model.typeMap.keys) + } + .forEach { term -> reanimateTerm(term) } + } + + fun reanimateTerm(term: Term): T? = memory.getOrPut(term) { modelReanimator.reanimate(term) } @@ -81,6 +96,8 @@ interface AbstractGenerator : Transformer> { } generateThis() generateArgs() +// TODO generateOther +// generateOtherTerms() return instance to args } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index 5cafe00b7..88e14306e 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -50,12 +50,21 @@ class DescriptorGenerator( is ConstantDescriptor.Float -> this.value is ConstantDescriptor.Double -> this.value } + else -> this } override fun checkPath(path: Predicate): Boolean = when (path) { - is EqualityPredicate -> checkTerms(path.lhv, path.rhv) { a, b -> a.numericValue == b.numericValue } - is InequalityPredicate -> checkTerms(path.lhv, path.rhv) { a, b -> a.numericValue != b.numericValue } + is EqualityPredicate -> checkTerms( + path.lhv, + path.rhv + ) { a, b -> a.numericValue == b.numericValue } + + is InequalityPredicate -> checkTerms( + path.lhv, + path.rhv + ) { a, b -> a.numericValue != b.numericValue } + is DefaultSwitchPredicate -> { val lhv = path.cond val conditions = path.cases @@ -63,6 +72,7 @@ class DescriptorGenerator( val condValues = conditions.map { (it as ConstIntTerm).value } lhvValue !in condValues } + else -> unreachable { log.error("Unexpected predicate in path: $path") } } } @@ -133,7 +143,8 @@ fun generateInitialDescriptors( generator.args.mapIndexed { index, arg -> arg ?: descriptor { default(method.argTypes[index].kexType) } }, - generator.staticFields + generator.staticFields, + generator.allValues ) } @@ -150,6 +161,7 @@ fun generateInitialDescriptorsAndAA( generator.args.mapIndexed { index, arg -> arg ?: descriptor { default(method.argTypes[index].kexType) } }, - generator.staticFields + generator.staticFields, + generator.allValues ) to SMTModelALiasAnalysis(generator) } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/ObjectTracingRunner.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/ObjectTracingRunner.kt index 0f5d21d2f..895403051 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/ObjectTracingRunner.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/ObjectTracingRunner.kt @@ -78,7 +78,10 @@ class ReanimatingRandomObjectTracingRunner( private val Parameters.descriptors get() = with(Object2DescriptorConverter()) { - Parameters(convert(instance), arguments.map { convert(it) }, statics.mapTo(mutableSetOf()) { convert(it) }) + Parameters(convert(instance), + arguments.map { convert(it) }, + statics.mapTo(mutableSetOf()) { convert(it) }, + others.mapTo(mutableSetOf()) { convert(it) }) } override fun generateArguments(): Parameters? { @@ -87,14 +90,14 @@ class ReanimatingRandomObjectTracingRunner( log.error("Cannot generate parameters to invoke method $method") return null } - val parameters = Parameters(randomInstance, randomArgs.toList(), setOf()) + val parameters = Parameters(randomInstance, randomArgs.toList(), setOf(), setOf()) val (instance, args) = with(reanimator) { val descriptors = parameters.descriptors val callStacks = descriptors.actionSequences printer.print("test_$testCounter", method, callStacks) callStacks.executed } - return Parameters(instance, args, setOf()) + return Parameters(instance, args, setOf(), setOf()) } fun emit() { From c599d7920aae310ebad6c8de0a64398cfb4fc69a Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 28 Sep 2023 10:51:51 +0200 Subject: [PATCH 002/128] Descriptors generated for all terms. Methods calls collected from initial parameters --- .../kex/asm/analysis/util/extensions.kt | 51 ++++++++++++---- .../generator/GeneratorContext.kt | 58 ++----------------- .../state/transformer/AbstractGenerator.kt | 12 +--- .../state/transformer/DescriptorGenerator.kt | 8 +-- 4 files changed, 52 insertions(+), 77 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 47cfcf6d8..296532cc4 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -1,5 +1,6 @@ package org.vorpal.research.kex.asm.analysis.util +import com.jetbrains.rd.util.printlnError import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.TimeoutCancellationException import org.vorpal.research.kex.ExecutionContext @@ -19,12 +20,10 @@ import org.vorpal.research.kex.smt.AsyncIncrementalChecker import org.vorpal.research.kex.smt.Result import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery +import org.vorpal.research.kex.state.predicate.CallPredicate +import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term -import org.vorpal.research.kex.state.transformer.SymbolicStateForwardSlicer -import org.vorpal.research.kex.state.transformer.collectArguments -import org.vorpal.research.kex.state.transformer.generateInitialDescriptors -import org.vorpal.research.kex.state.transformer.generateInitialDescriptorsAndAA -import org.vorpal.research.kex.state.transformer.toTypeMap +import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.logging.debug @@ -49,6 +48,28 @@ suspend fun Method.analyzeOrTimeout( } } +fun funcCalls( + state: SymbolicState, + termToDescriptor: Map +): List> { + return state.clauses + .asSequence() + .map { clause -> clause.predicate } + .filter { predicate -> predicate.type.name == "S" } + .filterIsInstance() + .map { predicate -> + val term = predicate.lhv + predicate to termToDescriptor[term] + .also { descriptor -> + if (descriptor == null) { + log.debug { "Error. No descriptor for $term, type: ${term.type}" } + printlnError("Error. No descriptor for $term, type: ${term.type}") + } + } + } + .toList() +} + suspend fun Method.checkAsync( ctx: ExecutionContext, state: SymbolicState, @@ -68,7 +89,16 @@ suspend fun Method.checkAsync( } return try { - generateInitialDescriptors(this, ctx, result.model, checker.state) + val (initialDescriptors, termToDescriptor) = generateInitialDescriptors( + this, + ctx, + result.model, + checker.state + ) + val funcCalls = funcCalls(state, termToDescriptor) + print(funcCalls) + + initialDescriptors .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } } @@ -108,9 +138,10 @@ suspend fun Method.checkAsyncAndSlice( .filterIgnoredStatic() val (thisTerm, argTerms) = collectArguments(checker.state) - val termParams = Parameters(thisTerm, this@checkAsyncAndSlice.argTypes.mapIndexed { index, type -> - argTerms[index] ?: term { arg(type.kexType, index) } - }) + val termParams = + Parameters(thisTerm, this@checkAsyncAndSlice.argTypes.mapIndexed { index, type -> + argTerms[index] ?: term { arg(type.kexType, index) } + }) filteredParams to ConstraintExceptionPrecondition( termParams, @@ -152,7 +183,7 @@ suspend fun Method.checkAsyncIncremental( when (result) { is Result.SatResult -> try { val fullPS = checker.state + checker.queries[index].hardConstraints - generateInitialDescriptors(this, ctx, result.model, fullPS) + generateInitialDescriptors(this, ctx, result.model, fullPS).first .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/GeneratorContext.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/GeneratorContext.kt index 161ac8ada..27f720098 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/GeneratorContext.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/GeneratorContext.kt @@ -6,26 +6,8 @@ import org.vorpal.research.kex.asm.manager.instantiationManager import org.vorpal.research.kex.asm.state.PredicateStateAnalysis import org.vorpal.research.kex.asm.util.accessModifier import org.vorpal.research.kex.config.kexConfig -import org.vorpal.research.kex.descriptor.ArrayDescriptor -import org.vorpal.research.kex.descriptor.ClassDescriptor -import org.vorpal.research.kex.descriptor.ConstantDescriptor -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.FieldContainingDescriptor -import org.vorpal.research.kex.descriptor.ObjectDescriptor -import org.vorpal.research.kex.descriptor.descriptor -import org.vorpal.research.kex.ktype.KexArray -import org.vorpal.research.kex.ktype.KexBool -import org.vorpal.research.kex.ktype.KexByte -import org.vorpal.research.kex.ktype.KexChar -import org.vorpal.research.kex.ktype.KexClass -import org.vorpal.research.kex.ktype.KexDouble -import org.vorpal.research.kex.ktype.KexFloat -import org.vorpal.research.kex.ktype.KexInt -import org.vorpal.research.kex.ktype.KexLong -import org.vorpal.research.kex.ktype.KexReference -import org.vorpal.research.kex.ktype.KexShort -import org.vorpal.research.kex.ktype.KexType -import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.descriptor.* +import org.vorpal.research.kex.ktype.* import org.vorpal.research.kex.parameters.Parameters import org.vorpal.research.kex.reanimator.actionsequence.ActionList import org.vorpal.research.kex.reanimator.actionsequence.ActionSequence @@ -39,39 +21,7 @@ import org.vorpal.research.kex.state.predicate.axiom import org.vorpal.research.kex.state.predicate.state import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term -import org.vorpal.research.kex.state.transformer.AnnotationAdapter -import org.vorpal.research.kex.state.transformer.ArrayBoundsAdapter -import org.vorpal.research.kex.state.transformer.BasicInvariantsTransformer -import org.vorpal.research.kex.state.transformer.BoolTypeAdapter -import org.vorpal.research.kex.state.transformer.CastTypeInfo -import org.vorpal.research.kex.state.transformer.ClassAdapter -import org.vorpal.research.kex.state.transformer.ClassMethodAdapter -import org.vorpal.research.kex.state.transformer.ConcreteImplInliner -import org.vorpal.research.kex.state.transformer.ConstEnumAdapter -import org.vorpal.research.kex.state.transformer.ConstStringAdapter -import org.vorpal.research.kex.state.transformer.ConstantPropagator -import org.vorpal.research.kex.state.transformer.EqualsTransformer -import org.vorpal.research.kex.state.transformer.FieldNormalizer -import org.vorpal.research.kex.state.transformer.IntrinsicAdapter -import org.vorpal.research.kex.state.transformer.KexIntrinsicsAdapter -import org.vorpal.research.kex.state.transformer.KexRtAdapter -import org.vorpal.research.kex.state.transformer.NullityInfoAdapter -import org.vorpal.research.kex.state.transformer.Optimizer -import org.vorpal.research.kex.state.transformer.RecursiveInliner -import org.vorpal.research.kex.state.transformer.ReflectionInfoAdapter -import org.vorpal.research.kex.state.transformer.StaticFieldInliner -import org.vorpal.research.kex.state.transformer.StringMethodAdapter -import org.vorpal.research.kex.state.transformer.TermRemapper -import org.vorpal.research.kex.state.transformer.TypeInfo -import org.vorpal.research.kex.state.transformer.TypeInfoMap -import org.vorpal.research.kex.state.transformer.TypeNameAdapter -import org.vorpal.research.kex.state.transformer.collectArguments -import org.vorpal.research.kex.state.transformer.collectFieldAccesses -import org.vorpal.research.kex.state.transformer.collectFieldTerms -import org.vorpal.research.kex.state.transformer.collectPlainTypeInfos -import org.vorpal.research.kex.state.transformer.collectStaticTypeInfo -import org.vorpal.research.kex.state.transformer.generateInitialDescriptors -import org.vorpal.research.kex.state.transformer.transform +import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.util.StringInfoContext import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.ir.Field @@ -337,7 +287,7 @@ class GeneratorContext( return when (val result = checker.check(checkedState)) { is Result.SatResult -> { log.debug("Model: {}", result.model) - generateInitialDescriptors(this, context, result.model, checker.state) + generateInitialDescriptors(this, context, result.model, checker.state).first } else -> null diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt index 640d6a94e..818d2c401 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt @@ -11,12 +11,7 @@ import org.vorpal.research.kex.state.ChoiceState import org.vorpal.research.kex.state.PredicateState import org.vorpal.research.kex.state.emptyState import org.vorpal.research.kex.state.predicate.Predicate -import org.vorpal.research.kex.state.term.ConstBoolTerm -import org.vorpal.research.kex.state.term.ConstIntTerm -import org.vorpal.research.kex.state.term.ConstLongTerm -import org.vorpal.research.kex.state.term.FieldTerm -import org.vorpal.research.kex.state.term.Term -import org.vorpal.research.kex.state.term.term +import org.vorpal.research.kex.state.term.* import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kfg.type.TypeFactory import org.vorpal.research.kthelper.assert.unreachable @@ -61,7 +56,7 @@ interface AbstractGenerator : Transformer> { } - fun generateOtherTerms() { + fun generateAll() { hashSetOf().apply { addAll(model.assignments.keys) addAll(model.assignments.values) @@ -96,8 +91,7 @@ interface AbstractGenerator : Transformer> { } generateThis() generateArgs() -// TODO generateOther -// generateOtherTerms() + generateAll() return instance to args } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index 88e14306e..4c49ac630 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -135,7 +135,7 @@ fun generateInitialDescriptors( ctx: ExecutionContext, model: SMTModel, state: PredicateState -): Parameters { +): Pair, Map> { val generator = DescriptorGenerator(method, ctx, model, InitialDescriptorReanimator(model, ctx)) generator.apply(state) return Parameters( @@ -144,10 +144,10 @@ fun generateInitialDescriptors( arg ?: descriptor { default(method.argTypes[index].kexType) } }, generator.staticFields, - generator.allValues - ) + ) to generator.memory } + fun generateInitialDescriptorsAndAA( method: Method, ctx: ExecutionContext, @@ -162,6 +162,6 @@ fun generateInitialDescriptorsAndAA( arg ?: descriptor { default(method.argTypes[index].kexType) } }, generator.staticFields, - generator.allValues +// generator.allValues ) to SMTModelALiasAnalysis(generator) } From d69f10610aea59a706aa489020df976b1c66a7f5 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 8 Oct 2023 23:03:50 +0200 Subject: [PATCH 003/128] Parameters: methods related to mocks. MockDescriptor: work in progress. --- .../research/kex/descriptor/descriptor.kt | 297 ++++++++++++------ .../research/kex/parameters/Parameters.kt | 57 +++- .../kex/asm/analysis/util/extensions.kt | 85 +++-- 3 files changed, 300 insertions(+), 139 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 20bd53316..ec67fbbe4 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -4,24 +4,9 @@ package org.vorpal.research.kex.descriptor import org.vorpal.research.kex.asm.manager.instantiationManager import org.vorpal.research.kex.asm.util.AccessModifier -import org.vorpal.research.kex.ktype.KexArray -import org.vorpal.research.kex.ktype.KexBool -import org.vorpal.research.kex.ktype.KexByte -import org.vorpal.research.kex.ktype.KexChar -import org.vorpal.research.kex.ktype.KexClass -import org.vorpal.research.kex.ktype.KexDouble -import org.vorpal.research.kex.ktype.KexFloat -import org.vorpal.research.kex.ktype.KexInt -import org.vorpal.research.kex.ktype.KexJavaClass -import org.vorpal.research.kex.ktype.KexLong -import org.vorpal.research.kex.ktype.KexNull -import org.vorpal.research.kex.ktype.KexReference -import org.vorpal.research.kex.ktype.KexRtManager +import org.vorpal.research.kex.ktype.* import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.KexRtManager.rtUnmapped -import org.vorpal.research.kex.ktype.KexShort -import org.vorpal.research.kex.ktype.KexString -import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.state.PredicateState import org.vorpal.research.kex.state.basic import org.vorpal.research.kex.state.emptyState @@ -29,7 +14,9 @@ import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.util.StringInfoContext import org.vorpal.research.kfg.ClassManager +import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.assert.unreachable +import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import kotlin.random.Random @@ -249,77 +236,26 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty } } -/*class MockDescriptor(term: Term, type: KexType) : Descriptor(term, type) { - override fun contains(other: Descriptor, visited: MutableSet): Boolean { - TODO("Not yet implemented") - } - - override fun print(map: MutableMap): String { - TODO("Not yet implemented") - } - - override fun structuralEquality( - other: Descriptor, - map: MutableSet> - ): Boolean { - TODO("Not yet implemented") - } - - override fun collectQuery(set: MutableSet): PredicateState { - TODO("Not yet implemented") - } - - override fun countDepth(visited: Set, cache: MutableMap): Int { - TODO("Not yet implemented") - } - - override fun concretize( - cm: ClassManager, - accessLevel: AccessModifier, - random: Random, - visited: MutableSet - ): Descriptor { - TODO("Not yet implemented") - } - - override fun deepCopy(copied: MutableMap): Descriptor { - return this; - } - - override fun reduce(visited: MutableSet): Descriptor { - visited.add(this); - return this; - } - - override fun generateTypeInfo(visited: MutableSet): PredicateState { - TODO("Not yet implemented") - } - -}*/ @Suppress("UNCHECKED_CAST") -sealed class FieldContainingDescriptor>( +sealed class FieldContainingDescriptorBase>( term: Term, klass: KexClass ) : Descriptor(term, klass) { var klass = klass protected set - val fields = mutableMapOf, Descriptor>() operator fun get(key: Pair) = fields[key] operator fun get(field: String, type: KexType) = get(field to type) - operator fun set(key: Pair, value: Descriptor) { fields[key] = value } operator fun set(field: String, type: KexType, value: Descriptor) = set(field to type, value) - fun remove(field: String, type: KexType): Descriptor? = fields.remove(field to type) fun remove(field: Pair): Descriptor? = fields.remove(field) - fun merge(other: T): T { val newFields = other.fields + this.fields this.fields.clear() @@ -356,16 +292,19 @@ sealed class FieldContainingDescriptor>( override fun collectQuery(set: MutableSet): PredicateState { if (this in set) return emptyState() set += this - return basic { - axiom { term inequality null } - for ((field, value) in fields) { - val fieldTerm = term.field(field.second, field.first) - append(value.collectQuery(set)) - require { fieldTerm.load() equality value.term } - } + return fieldsQuery(set) + } + + protected fun fieldsQuery(set: MutableSet) = basic { + axiom { term inequality null } + for ((field, value) in fields) { + val fieldTerm = term.field(field.second, field.first) + append(value.collectQuery(set)) + require { fieldTerm.load() equality value.term } } } + override fun concretize( cm: ClassManager, accessLevel: AccessModifier, @@ -380,11 +319,20 @@ sealed class FieldContainingDescriptor>( this.klassDescriptor["name" to KexJavaClass()] = descriptor { string("$type") } this.term = term { generate(type) } + concretizeFields(cm, accessLevel, random, visited) + + return this as T + } + + protected fun concretizeFields( + cm: ClassManager, + accessLevel: AccessModifier, + random: Random, + visited: MutableSet + ) { for ((field, value) in fields.toMap()) { fields[field] = value.concretize(cm, accessLevel, random, visited) } - - return this as T } override fun contains(other: Descriptor, visited: MutableSet): Boolean { @@ -397,15 +345,17 @@ sealed class FieldContainingDescriptor>( override fun reduce(visited: MutableSet): T { if (this in visited) return this as T visited += this + reduceFields(visited) + return this as T + } + protected fun reduceFields(visited: MutableSet) { for ((field, value) in fields.toMap()) { when { value eq descriptor { default(field.second) } -> fields.remove(field) else -> fields[field] = value.reduce(visited) } } - - return this as T } override fun generateTypeInfo(visited: MutableSet): PredicateState { @@ -414,13 +364,13 @@ sealed class FieldContainingDescriptor>( val instanceOfTerm = term { generate(KexBool) } return basic { - axiom { instanceOfTerm equality (term `is` this@FieldContainingDescriptor.type) } + axiom { instanceOfTerm equality (term `is` this@FieldContainingDescriptorBase.type) } axiom { instanceOfTerm equality true } - for ((key, field) in this@FieldContainingDescriptor.fields) { + for ((key, field) in this@FieldContainingDescriptorBase.fields) { val typeInfo = field.generateTypeInfo(visited) if (typeInfo.isNotEmpty) { state { - field.term equality this@FieldContainingDescriptor.term.field( + field.term equality this@FieldContainingDescriptorBase.term.field( key.second, key.first ).load() @@ -441,7 +391,7 @@ sealed class FieldContainingDescriptor>( if (this.klass != other.klass) return false map += this to other - for ((field, type) in this.fields.keys.intersect(other.fields.keys)) { + for ((field, type) in this.fields.keys.intersect(other.fields.keys)) { // TODO: maybe `union` ??? val thisValue = this[field, type] ?: return false val otherValue = other[field, type] ?: return false if (!thisValue.structuralEquality(otherValue, map)) return false @@ -462,6 +412,141 @@ sealed class FieldContainingDescriptor>( } } +class MockDescriptor(term: Term, type: KexClass, methods: Collection) : + FieldContainingDescriptorBase(term, type) { + + constructor(term: Term, type: KexClass, methods: Collection, original: ObjectDescriptor) : this( + term, + type, + methods + ) { + for ((field, value) in original.fields) { + fields[field] = value + } + } + + val methodReturns: Map> = + mutableMapOf>().apply { + methods.forEach { method -> put(method, mutableListOf()) } + } + + val allReturns: Iterable + get() = methodReturns.values.asSequence().flatMap { it.asSequence() }.asIterable() + + val methods: Set + get() = methodReturns.keys + + fun addReturnValue(method: Method, value: Descriptor) { + methodReturns[method]?.add(value) ?: log.debug { "Error. No method $method in mockDescriptor $this" } + } + + override fun concretize( + cm: ClassManager, + accessLevel: AccessModifier, + random: Random, + visited: MutableSet + ): MockDescriptor { + if (this in visited) return this + visited += this + concretizeFields(cm, accessLevel, random, visited) + // allReturns.forEach { descriptor -> descriptor.concretize(cm, accessLevel, random, visited) } + for (list in methodReturns.values) { + list.map { descriptor -> descriptor.concretize(cm, accessLevel, random, visited) } + } + + return this + } + + override fun reduce(visited: MutableSet): MockDescriptor { + if (this in visited) return this + visited += this + reduceFields(visited) + for ((method, list) in methodReturns) { + val type = method.returnType.kexType + while (list.isNotEmpty() && list.last() eq descriptor { default(type) }) { + list.removeLast() + } + } + allReturns.forEach { descriptor -> descriptor.reduce() } + return this + } + + override fun contains(other: Descriptor, visited: MutableSet): Boolean { + if (this in visited) return false + if (this == other) return true + visited += this + return allReturns.any { it.contains(other, visited) } || fields.values.any { it.contains(other, visited) } + } + + override fun countDepth(visited: Set, cache: MutableMap): Int { + if (this in cache) return cache[this]!! + if (this in visited) return 0 + val newVisited = visited + this + var maxDepth = 0 + for (value in fields.values) { + maxDepth = maxOf(maxDepth, value.countDepth(newVisited, cache)) + } + for (value in allReturns) { + maxDepth = maxOf(maxDepth, value.countDepth(newVisited, cache)) + } + cache[this] = maxDepth + 1 + return maxDepth + 1 + } + + override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + if (this == other) return true + if (other !is MockDescriptor) return false + if (this to other in map) return true + if (this.klass != other.klass) return false + + map += this to other + + for (key in this.fields.keys.union(other.fields.keys)) { // union or intersect??? + val thisValue = this[key] ?: return false + val otherValue = other[key] ?: return false + if (!thisValue.structuralEquality(otherValue, map)) return false + } + for (method in this.methods.union(other.methods)) { + val thisValues = this.methodReturns[method] ?: return false + val otherValues = other.methodReturns[method] ?: return false + if (thisValues.size != otherValues.size) return false + for (i in 0..thisValues.lastIndex) { + if (!thisValues[i].structuralEquality(otherValues[i], map)) return false + } + } + return true + } + + override fun collectQuery(set: MutableSet): PredicateState { + TODO("Mock. Unimplemented") + } + + + + override fun deepCopy(copied: MutableMap): Descriptor { + if (this in copied) return copied[this]!! + val copy = MockDescriptor(term, type as KexClass, methodReturns.keys) + copied[this] = copy + + for ((method, list) in methodReturns) { + list.forEach { value -> copy.addReturnValue(method, value.deepCopy(copied)) } + } + for ((field, value) in fields) { + copy[field] = value.deepCopy(copied) + } + + return copy + } +} + +//@Suppress("UNCHECKED_CAST") +sealed class FieldContainingDescriptor>( + term: Term, + klass: KexClass +) : + FieldContainingDescriptorBase(term, klass) { +} + class ObjectDescriptor(klass: KexClass) : FieldContainingDescriptor(term { generate(klass) }, klass) { override fun deepCopy(copied: MutableMap): Descriptor { @@ -514,13 +599,12 @@ class ClassDescriptor(type: KexClass) : if (this in visited) return this visited += this - for ((field, value) in fields.toMap()) { - fields[field] = value.concretize(cm, accessLevel, random, visited) - } + concretizeFields(cm, accessLevel, random, visited) return this } + fun filterFinalFields(cm: ClassManager): ClassDescriptor { val kfgClass = klass.kfgClass(cm.type) for ((name, type) in fields.keys.toSet()) { @@ -531,23 +615,6 @@ class ClassDescriptor(type: KexClass) : } } -class MockDescriptor(klass: KexClass) : - FieldContainingDescriptor(term { generate(klass) }, klass) { - override fun concretize( - cm: ClassManager, - accessLevel: AccessModifier, - random: Random, - visited: MutableSet - ): ClassDescriptor { - // TODO: generate implementation class - return super.concretize(cm, accessLevel, random, visited) - } - - override fun deepCopy(copied: MutableMap): Descriptor { - TODO("Not yet implemented") - } -} - class ArrayDescriptor(val elementType: KexType, val length: Int) : Descriptor(term { generate(KexArray(elementType)) }, KexArray(elementType)) { val elements = mutableMapOf() @@ -723,6 +790,8 @@ open class DescriptorBuilder : StringInfoContext() { fun array(length: Int, elementType: KexType): ArrayDescriptor = ArrayDescriptor(elementType, length) + fun mock(type: KexClass, methods: Set): MockDescriptor = TODO("Mock. How to get term?") + fun default(type: KexType, nullable: Boolean): Descriptor = descriptor { when (type) { is KexBool -> const(false) @@ -778,6 +847,12 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde KexRtManager.Mode.UNMAP -> rtUnmapped } + private val Method.mapped + get() = when (mode) { + KexRtManager.Mode.MAP -> rtMapped + KexRtManager.Mode.UNMAP -> rtUnmapped + } + fun map(descriptor: Descriptor): Descriptor = cache.getOrElse(descriptor) { when (descriptor) { is ConstantDescriptor -> descriptor @@ -808,7 +883,21 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde arrayDesc } - is MockDescriptor -> TODO("Not implemented") + is MockDescriptor -> { + val mockMapped = + MockDescriptor( + descriptor.term, + descriptor.type.mapped as KexClass, + descriptor.methods.map { method -> method.mapped }) + cache[descriptor] = mockMapped + for ((field, value) in descriptor.fields) { + mockMapped[field.first, field.second.mapped] = map(value) + } + for ((method, values) in descriptor.methodReturns) { + values.forEach { value -> mockMapped.addReturnValue(method.mapped, map(value)) } + } + mockMapped + } } } } diff --git a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 0e79c1c5a..003426dad 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -1,15 +1,20 @@ package org.vorpal.research.kex.parameters import kotlinx.serialization.Serializable +import org.vorpal.research.kex.asm.manager.instantiationManager import org.vorpal.research.kex.asm.util.AccessModifier import org.vorpal.research.kex.config.kexConfig -import org.vorpal.research.kex.descriptor.ClassDescriptor -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.Object2DescriptorConverter +import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexClass +import org.vorpal.research.kex.ktype.KexRtManager.isKexRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped +import org.vorpal.research.kex.state.predicate.CallPredicate +import org.vorpal.research.kex.state.term.CallTerm +import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.Package +import org.vorpal.research.kfg.type.ClassType +import org.vorpal.research.kfg.type.TypeFactory import kotlin.random.Random @Serializable @@ -94,3 +99,49 @@ fun Parameters.filterIgnoredStatic(): Parameters { } +private fun Collection.replaceUninstantiableWithMocks( + types: TypeFactory, + termToDescriptor: MutableMap +): Sequence { + return asSequence().map { descriptor -> + val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass + if (klass == null || !instantiationManager.isInstantiable(klass) || descriptor.type.isKexRt) { + descriptor + } else { + val mock = if (descriptor is ObjectDescriptor) { + MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods, descriptor) + } else { + MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods) + } + termToDescriptor[descriptor.term] = mock; + mock + } + } +} + +private fun Parameters.generateInitialMocks( + types: TypeFactory, termToDescriptor: MutableMap +): Parameters { + val mockedArguments = arguments.replaceUninstantiableWithMocks(types, termToDescriptor).toList() + val mockedStatics = statics.replaceUninstantiableWithMocks(types, termToDescriptor).toSet() + val mockedOthers = others.replaceUninstantiableWithMocks(types, termToDescriptor).toSet() + + return Parameters(instance, mockedArguments, mockedStatics, mockedOthers) +} + +fun Parameters.generateMocks( + methodCalls: List>, + termToDescriptor: MutableMap, + cm: ClassManager, + accessLevel: AccessModifier +): Parameters { + val withMocks = this.generateInitialMocks(cm.type, termToDescriptor) + for ((callPredicate, value) in methodCalls) { + val call = callPredicate.call as CallTerm + val mock = termToDescriptor[call.owner] + if (mock is MockDescriptor) { + mock.addReturnValue(call.method, value) + } + } + return withMocks +} \ No newline at end of file diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 296532cc4..70a94c837 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -1,6 +1,5 @@ package org.vorpal.research.kex.asm.analysis.util -import com.jetbrains.rd.util.printlnError import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.TimeoutCancellationException import org.vorpal.research.kex.ExecutionContext @@ -11,10 +10,7 @@ import org.vorpal.research.kex.descriptor.Descriptor import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType -import org.vorpal.research.kex.parameters.Parameters -import org.vorpal.research.kex.parameters.concreteParameters -import org.vorpal.research.kex.parameters.filterIgnoredStatic -import org.vorpal.research.kex.parameters.filterStaticFinals +import org.vorpal.research.kex.parameters.* import org.vorpal.research.kex.smt.AsyncChecker import org.vorpal.research.kex.smt.AsyncIncrementalChecker import org.vorpal.research.kex.smt.Result @@ -48,10 +44,10 @@ suspend fun Method.analyzeOrTimeout( } } -fun funcCalls( +fun methodCalls( state: SymbolicState, termToDescriptor: Map -): List> { +): List> { return state.clauses .asSequence() .map { clause -> clause.predicate } @@ -59,14 +55,12 @@ fun funcCalls( .filterIsInstance() .map { predicate -> val term = predicate.lhv - predicate to termToDescriptor[term] - .also { descriptor -> - if (descriptor == null) { - log.debug { "Error. No descriptor for $term, type: ${term.type}" } - printlnError("Error. No descriptor for $term, type: ${term.type}") - } - } + val descriptor = termToDescriptor[term] + if (descriptor == null) log.debug { "Error. No descriptor for $term, type: ${term.type}\n" } + predicate to descriptor } + .filter { (_, value) -> value != null } + .map { (call, value) -> call to value!! } .toList() } @@ -88,6 +82,7 @@ suspend fun Method.checkAsync( return null } + return try { val (initialDescriptors, termToDescriptor) = generateInitialDescriptors( this, @@ -95,10 +90,18 @@ suspend fun Method.checkAsync( result.model, checker.state ) - val funcCalls = funcCalls(state, termToDescriptor) - print(funcCalls) + val methodCalls = methodCalls(state, termToDescriptor) +// println(methodCalls) - initialDescriptors + val withMocks = initialDescriptors.generateMocks( + methodCalls, + termToDescriptor.toMutableMap(), + ctx.cm, + ctx.accessLevel + ) + assert(initialDescriptors == withMocks) + withMocks +// initialDescriptors .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } } @@ -124,18 +127,25 @@ suspend fun Method.checkAsyncAndSlice( .filterValues { it.isJavaRt } .mapValues { it.value.rtMapped } .toTypeMap() - val result = checker.prepareAndCheck(this, clauses + query, concreteTypeInfo, enableInlining) + val result = + checker.prepareAndCheck(this, clauses + query, concreteTypeInfo, enableInlining) if (result !is Result.SatResult) { return null } return try { - val (params, aa) = generateInitialDescriptorsAndAA(this, ctx, result.model, checker.state) - val filteredParams = params.concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { - log.debug { "Generated params:\n$it" } - } - .filterStaticFinals(ctx.cm) - .filterIgnoredStatic() + val (params, aa) = generateInitialDescriptorsAndAA( + this, + ctx, + result.model, + checker.state + ) + val filteredParams = + params.concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { + log.debug { "Generated params:\n$it" } + } + .filterStaticFinals(ctx.cm) + .filterIgnoredStatic() val (thisTerm, argTerms) = collectArguments(checker.state) val termParams = @@ -173,7 +183,8 @@ suspend fun Method.checkAsyncIncremental( this, IncrementalPredicateState( clauses + query, - queries.map { PredicateQuery(it.clauses.asState() + it.path.asState()) }.toPersistentList() + queries.map { PredicateQuery(it.clauses.asState() + it.path.asState()) } + .toPersistentList() ), concreteTypeInfo, enableInlining @@ -220,7 +231,8 @@ suspend fun Method.checkAsyncIncrementalAndSlice( this, IncrementalPredicateState( clauses + query, - queries.map { PredicateQuery(it.clauses.asState() + it.path.asState()) }.toPersistentList() + queries.map { PredicateQuery(it.clauses.asState() + it.path.asState()) } + .toPersistentList() ), concreteTypeInfo, enableInlining @@ -230,11 +242,17 @@ suspend fun Method.checkAsyncIncrementalAndSlice( when (result) { is Result.SatResult -> try { val fullPS = (checker.state + checker.queries[index].hardConstraints).simplify() - val (params, aa) = generateInitialDescriptorsAndAA(this, ctx, result.model, fullPS) - val filteredParams = params.concreteParameters(ctx.cm, ctx.accessLevel, ctx.random) - .also { log.debug { "Generated params:\n$it" } } - .filterStaticFinals(ctx.cm) - .filterIgnoredStatic() + val (params, aa) = generateInitialDescriptorsAndAA( + this, + ctx, + result.model, + fullPS + ) + val filteredParams = + params.concreteParameters(ctx.cm, ctx.accessLevel, ctx.random) + .also { log.debug { "Generated params:\n$it" } } + .filterStaticFinals(ctx.cm) + .filterIgnoredStatic() val (thisTerm, argTerms) = collectArguments(fullPS) val termParams = Parameters( @@ -246,7 +264,10 @@ suspend fun Method.checkAsyncIncrementalAndSlice( filteredParams to ConstraintExceptionPrecondition( termParams, - SymbolicStateForwardSlicer(termParams.asList.toSet(), aa).apply(state + queries[index]) + SymbolicStateForwardSlicer( + termParams.asList.toSet(), + aa + ).apply(state + queries[index]) ) } catch (e: Throwable) { log.error("Error during descriptor generation: ", e) From 5a21682d1cefea92a3d5044f854aaff6387448c2 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 14 Oct 2023 13:02:32 +0200 Subject: [PATCH 004/128] DescriptorWrapper.Mock WIP. --- .../asm/manager/ClassInstantiationManager.kt | 4 +- .../research/kex/descriptor/descriptor.kt | 280 +++++++++--------- .../research/kex/parameters/Parameters.kt | 8 +- .../research/kex/serialization/descriptor.kt | 46 ++- .../analysis/crash/precondition/builders.kt | 25 +- .../kex/reanimator/RandomObjectReanimator.kt | 14 +- .../actionsequence/ActionSequence.kt | 2 + 7 files changed, 207 insertions(+), 172 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt b/core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt index 8023c03b9..7cf85c69d 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt @@ -10,9 +10,7 @@ import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.ktype.kexType import org.vorpal.research.kex.util.isSubtypeOfCached import org.vorpal.research.kfg.ClassManager -import org.vorpal.research.kfg.ir.Class -import org.vorpal.research.kfg.ir.ConcreteClass -import org.vorpal.research.kfg.ir.Method +import org.vorpal.research.kfg.ir.* import org.vorpal.research.kfg.type.ArrayType import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.SystemTypeNames diff --git a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index ec67fbbe4..113ecf04a 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -15,6 +15,8 @@ import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.util.StringInfoContext import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.Method +import org.vorpal.research.kfg.type.ClassType +import org.vorpal.research.kfg.type.TypeFactory import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log @@ -236,9 +238,8 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty } } - @Suppress("UNCHECKED_CAST") -sealed class FieldContainingDescriptorBase>( +sealed class AbstractFieldContainingDescriptor>( term: Term, klass: KexClass ) : @@ -364,13 +365,13 @@ sealed class FieldContainingDescriptorBase> val instanceOfTerm = term { generate(KexBool) } return basic { - axiom { instanceOfTerm equality (term `is` this@FieldContainingDescriptorBase.type) } + axiom { instanceOfTerm equality (term `is` this@AbstractFieldContainingDescriptor.type) } axiom { instanceOfTerm equality true } - for ((key, field) in this@FieldContainingDescriptorBase.fields) { + for ((key, field) in this@AbstractFieldContainingDescriptor.fields) { val typeInfo = field.generateTypeInfo(visited) if (typeInfo.isNotEmpty) { state { - field.term equality this@FieldContainingDescriptorBase.term.field( + field.term equality this@AbstractFieldContainingDescriptor.term.field( key.second, key.first ).load() @@ -391,7 +392,7 @@ sealed class FieldContainingDescriptorBase> if (this.klass != other.klass) return false map += this to other - for ((field, type) in this.fields.keys.intersect(other.fields.keys)) { // TODO: maybe `union` ??? + for ((field, type) in this.fields.keys.union(other.fields.keys)) { // TODO: maybe `union` ??? val thisValue = this[field, type] ?: return false val otherValue = other[field, type] ?: return false if (!thisValue.structuralEquality(otherValue, map)) return false @@ -412,139 +413,11 @@ sealed class FieldContainingDescriptorBase> } } -class MockDescriptor(term: Term, type: KexClass, methods: Collection) : - FieldContainingDescriptorBase(term, type) { - - constructor(term: Term, type: KexClass, methods: Collection, original: ObjectDescriptor) : this( - term, - type, - methods - ) { - for ((field, value) in original.fields) { - fields[field] = value - } - } - - val methodReturns: Map> = - mutableMapOf>().apply { - methods.forEach { method -> put(method, mutableListOf()) } - } - - val allReturns: Iterable - get() = methodReturns.values.asSequence().flatMap { it.asSequence() }.asIterable() - - val methods: Set - get() = methodReturns.keys - - fun addReturnValue(method: Method, value: Descriptor) { - methodReturns[method]?.add(value) ?: log.debug { "Error. No method $method in mockDescriptor $this" } - } - - override fun concretize( - cm: ClassManager, - accessLevel: AccessModifier, - random: Random, - visited: MutableSet - ): MockDescriptor { - if (this in visited) return this - visited += this - concretizeFields(cm, accessLevel, random, visited) - // allReturns.forEach { descriptor -> descriptor.concretize(cm, accessLevel, random, visited) } - for (list in methodReturns.values) { - list.map { descriptor -> descriptor.concretize(cm, accessLevel, random, visited) } - } - - return this - } - - override fun reduce(visited: MutableSet): MockDescriptor { - if (this in visited) return this - visited += this - reduceFields(visited) - for ((method, list) in methodReturns) { - val type = method.returnType.kexType - while (list.isNotEmpty() && list.last() eq descriptor { default(type) }) { - list.removeLast() - } - } - allReturns.forEach { descriptor -> descriptor.reduce() } - return this - } - - override fun contains(other: Descriptor, visited: MutableSet): Boolean { - if (this in visited) return false - if (this == other) return true - visited += this - return allReturns.any { it.contains(other, visited) } || fields.values.any { it.contains(other, visited) } - } - - override fun countDepth(visited: Set, cache: MutableMap): Int { - if (this in cache) return cache[this]!! - if (this in visited) return 0 - val newVisited = visited + this - var maxDepth = 0 - for (value in fields.values) { - maxDepth = maxOf(maxDepth, value.countDepth(newVisited, cache)) - } - for (value in allReturns) { - maxDepth = maxOf(maxDepth, value.countDepth(newVisited, cache)) - } - cache[this] = maxDepth + 1 - return maxDepth + 1 - } - - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { - if (this == other) return true - if (other !is MockDescriptor) return false - if (this to other in map) return true - if (this.klass != other.klass) return false - - map += this to other - - for (key in this.fields.keys.union(other.fields.keys)) { // union or intersect??? - val thisValue = this[key] ?: return false - val otherValue = other[key] ?: return false - if (!thisValue.structuralEquality(otherValue, map)) return false - } - for (method in this.methods.union(other.methods)) { - val thisValues = this.methodReturns[method] ?: return false - val otherValues = other.methodReturns[method] ?: return false - if (thisValues.size != otherValues.size) return false - for (i in 0..thisValues.lastIndex) { - if (!thisValues[i].structuralEquality(otherValues[i], map)) return false - } - } - return true - } - - override fun collectQuery(set: MutableSet): PredicateState { - TODO("Mock. Unimplemented") - } - - - - override fun deepCopy(copied: MutableMap): Descriptor { - if (this in copied) return copied[this]!! - val copy = MockDescriptor(term, type as KexClass, methodReturns.keys) - copied[this] = copy - - for ((method, list) in methodReturns) { - list.forEach { value -> copy.addReturnValue(method, value.deepCopy(copied)) } - } - for ((field, value) in fields) { - copy[field] = value.deepCopy(copied) - } - - return copy - } -} - -//@Suppress("UNCHECKED_CAST") sealed class FieldContainingDescriptor>( term: Term, klass: KexClass ) : - FieldContainingDescriptorBase(term, klass) { + AbstractFieldContainingDescriptor(term, klass) { } class ObjectDescriptor(klass: KexClass) : @@ -732,7 +605,8 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : if (this.length != other.length) return false map += this to other - for (index in this.elements.keys.intersect(other.elements.keys)) { + // TODO: maybe `union` + for (index in this.elements.keys.union(other.elements.keys)) { val thisValue = this[index] ?: return false val otherValue = other[index] ?: return false val res = thisValue.structuralEquality(otherValue, map) @@ -754,6 +628,136 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : } } +class MockDescriptor(term: Term, type: KexClass, methods: Collection = emptyList()) : + AbstractFieldContainingDescriptor(term, type) { + + constructor(type: KexClass, methods: Collection) : this(term { generate(type) }, type, methods) + constructor(methods: Collection, original: ObjectDescriptor) : this( + original.term, + original.type as KexClass, + methods + ) { + for ((field, value) in original.fields) { + fields[field] = value + } + } + + val methodReturns: Map> = + mutableMapOf>().apply { + methods.forEach { method -> put(method, mutableListOf()) } + } + + val allReturns: Iterable + get() = methodReturns.values.asSequence().flatMap { it.asSequence() }.asIterable() + + val methods: Set + get() = methodReturns.keys + + operator fun get(method: Method) = methodReturns[method] + operator fun set(method: Method, values: List) { + methodReturns[method]?.clear() + methodReturns[method]?.addAll(values) + } + + fun addReturnValue(method: Method, value: Descriptor) { + methodReturns[method]?.add(value) ?: log.debug { "Error. No method $method in mockDescriptor $this" } + } + + override fun concretize( + cm: ClassManager, + accessLevel: AccessModifier, + random: Random, + visited: MutableSet + ): MockDescriptor { + if (this in visited) return this + visited += this + concretizeFields(cm, accessLevel, random, visited) + for (list in methodReturns.values) { + list.replaceAll { descriptor -> descriptor.concretize(cm, accessLevel, random, visited) } + } + return this + } + + override fun reduce(visited: MutableSet): MockDescriptor { + if (this in visited) return this + visited += this + reduceFields(visited) + for ((method, list) in methodReturns) { + val type = method.returnType.kexType + while (list.isNotEmpty() && list.last() eq descriptor { default(type) }) { + list.removeLast() + } + } + allReturns.forEach { descriptor -> descriptor.reduce() } + return this + } + + override fun contains(other: Descriptor, visited: MutableSet): Boolean { + if (this in visited) return false + if (this == other) return true + visited += this + return allReturns.any { it.contains(other, visited) } || fields.values.any { it.contains(other, visited) } + } + + override fun countDepth(visited: Set, cache: MutableMap): Int { + if (this in cache) return cache[this]!! + if (this in visited) return 0 + val newVisited = visited + this + var maxDepth = 0 + for (value in fields.values) { + maxDepth = maxOf(maxDepth, value.countDepth(newVisited, cache)) + } + for (value in allReturns) { + maxDepth = maxOf(maxDepth, value.countDepth(newVisited, cache)) + } + cache[this] = maxDepth + 1 + return maxDepth + 1 + } + + override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + if (this == other) return true + if (other !is MockDescriptor) return false + if (this to other in map) return true + if (this.klass != other.klass) return false + + map += this to other + + for (key in this.fields.keys.union(other.fields.keys)) { // union or intersect??? + val thisValue = this[key] ?: return false + val otherValue = other[key] ?: return false + if (!thisValue.structuralEquality(otherValue, map)) return false + } + for (method in this.methods.union(other.methods)) { + val thisValues = this.methodReturns[method] ?: return false + val otherValues = other.methodReturns[method] ?: return false + if (thisValues.size != otherValues.size) return false + for ((thisValue, otherValue) in thisValues.zip(otherValues)) { + if (!thisValue.structuralEquality(otherValue, map)) return false + } + } + return true + } + + override fun collectQuery(set: MutableSet): PredicateState { + TODO("Mock. Unimplemented") + } + + override fun deepCopy(copied: MutableMap): Descriptor { + if (this in copied) return copied[this]!! + val copy = MockDescriptor(term, type as KexClass, methodReturns.keys) + copied[this] = copy + + for ((method, list) in methodReturns) { + list.forEach { value -> copy.addReturnValue(method, value.deepCopy(copied)) } + } + for ((field, value) in fields) { + copy[field] = value.deepCopy(copied) + } + + return copy + } +} + open class DescriptorBuilder : StringInfoContext() { val `null` = ConstantDescriptor.Null fun const(@Suppress("UNUSED_PARAMETER") nothing: Nothing?) = `null` @@ -790,7 +794,9 @@ open class DescriptorBuilder : StringInfoContext() { fun array(length: Int, elementType: KexType): ArrayDescriptor = ArrayDescriptor(elementType, length) - fun mock(type: KexClass, methods: Set): MockDescriptor = TODO("Mock. How to get term?") + fun mock(type: KexClass, methods: Collection = emptyList()) = MockDescriptor(type, methods) + fun mock(type: KexClass, types: TypeFactory): MockDescriptor = + MockDescriptor(type, (type.getKfgType(types) as ClassType).klass.methods) fun default(type: KexType, nullable: Boolean): Descriptor = descriptor { when (type) { diff --git a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 003426dad..fdd309d14 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -102,18 +102,18 @@ fun Parameters.filterIgnoredStatic(): Parameters { private fun Collection.replaceUninstantiableWithMocks( types: TypeFactory, termToDescriptor: MutableMap -): Sequence { - return asSequence().map { descriptor -> +): Collection { + return map { descriptor -> val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass if (klass == null || !instantiationManager.isInstantiable(klass) || descriptor.type.isKexRt) { descriptor } else { val mock = if (descriptor is ObjectDescriptor) { - MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods, descriptor) + MockDescriptor(klass.methods, descriptor) } else { MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods) } - termToDescriptor[descriptor.term] = mock; + termToDescriptor[descriptor.term] = mock mock } } diff --git a/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt b/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt index a11a88e5d..6839d3a3b 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt @@ -1,5 +1,6 @@ package org.vorpal.research.kex.serialization +import kotlinx.serialization.Contextual import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException @@ -14,6 +15,7 @@ import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexArray import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexType +import org.vorpal.research.kfg.ir.Method @JvmInline @Serializable @@ -65,6 +67,31 @@ internal sealed class DescriptorWrapper { } } + @Serializable + class Mock( + override val id: Id, + override val type: KexType, + val fields: MutableList, Id>>, + val methodReturns: MutableList>> + ) : DescriptorWrapper() { + override fun convert(map: Map, output: MutableMap) { + if (id in output) return + val methods = methodReturns.map { (method, _) -> method } + val instance = descriptor { mock(type as KexClass, methods) }.also { + output[id] = it + } as MockDescriptor + + for ((field, fieldId) in fields) { + map.getValue(fieldId).toDescriptor(map, output) + instance[field] = output[fieldId]!! + } + + for ((method, values) in methodReturns) { + instance[method] = values.map { id -> map.getValue(id).toDescriptor(map, output) } + } + } + } + @Serializable class Klass( override val id: Id, @@ -141,6 +168,7 @@ private fun Descriptor.toWrapper(visited: MutableMap) { element.toWrapper(visited) } } + is ClassDescriptor -> { val klass = DescriptorWrapper.Klass(id, this.type, mutableListOf()).also { visited[id] = it @@ -152,6 +180,7 @@ private fun Descriptor.toWrapper(visited: MutableMap) { field.toWrapper(visited) } } + is ObjectDescriptor -> { val instance = DescriptorWrapper.Object(id, this.type, mutableListOf()).also { visited[id] = it @@ -163,7 +192,22 @@ private fun Descriptor.toWrapper(visited: MutableMap) { field.toWrapper(visited) } } - is MockDescriptor -> TODO ("not implemented") + + is MockDescriptor -> { + val instance = DescriptorWrapper.Mock(id, this.type, mutableListOf(), mutableListOf()).also { + visited[id] = it + } + for ((field, value) in this.fields) { + instance.fields += field to value.id + } + for (field in this.fields.values) { + field.toWrapper(visited) + } + for (value in this.allReturns){ + value.toWrapper(visited) + } + + } } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt index 6a88fea41..ded5c6a82 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt @@ -2,10 +2,7 @@ package org.vorpal.research.kex.asm.analysis.crash.precondition import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.analysis.symbolic.TraverserState -import org.vorpal.research.kex.descriptor.ArrayDescriptor -import org.vorpal.research.kex.descriptor.ConstantDescriptor -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.FieldContainingDescriptor +import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.kexType import org.vorpal.research.kex.parameters.Parameters import org.vorpal.research.kex.state.predicate.path @@ -13,27 +10,12 @@ import org.vorpal.research.kex.state.predicate.state import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.SymbolicStateTermRemapper -import org.vorpal.research.kex.trace.symbolic.PathClause -import org.vorpal.research.kex.trace.symbolic.PathClauseType -import org.vorpal.research.kex.trace.symbolic.PersistentSymbolicState -import org.vorpal.research.kex.trace.symbolic.StateClause -import org.vorpal.research.kex.trace.symbolic.persistentClauseStateOf -import org.vorpal.research.kex.trace.symbolic.persistentPathConditionOf -import org.vorpal.research.kex.trace.symbolic.persistentSymbolicState +import org.vorpal.research.kex.trace.symbolic.* import org.vorpal.research.kex.util.asSet import org.vorpal.research.kfg.arrayIndexOOBClass import org.vorpal.research.kfg.classCastClass import org.vorpal.research.kfg.ir.Class -import org.vorpal.research.kfg.ir.value.instruction.ArrayLoadInst -import org.vorpal.research.kfg.ir.value.instruction.ArrayStoreInst -import org.vorpal.research.kfg.ir.value.instruction.CallInst -import org.vorpal.research.kfg.ir.value.instruction.CastInst -import org.vorpal.research.kfg.ir.value.instruction.FieldLoadInst -import org.vorpal.research.kfg.ir.value.instruction.FieldStoreInst -import org.vorpal.research.kfg.ir.value.instruction.Instruction -import org.vorpal.research.kfg.ir.value.instruction.NewArrayInst -import org.vorpal.research.kfg.ir.value.instruction.NewInst -import org.vorpal.research.kfg.ir.value.instruction.ThrowInst +import org.vorpal.research.kfg.ir.value.instruction.* import org.vorpal.research.kfg.negativeArrayClass import org.vorpal.research.kfg.nullptrClass import org.vorpal.research.kthelper.assert.unreachable @@ -214,6 +196,7 @@ class DescriptorExceptionPreconditionBuilder( is ConstantDescriptor -> this.asSymbolicState(location, mapping, visited) is FieldContainingDescriptor<*> -> this.asSymbolicState(location, mapping, visited) is ArrayDescriptor -> this.asSymbolicState(location, mapping, visited) + is MockDescriptor -> TODO("Mock. Unimplemented") } private fun ConstantDescriptor.asSymbolicState( diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/RandomObjectReanimator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/RandomObjectReanimator.kt index 995b4846f..ba63df4fe 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/RandomObjectReanimator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/RandomObjectReanimator.kt @@ -5,12 +5,7 @@ import org.vorpal.research.kex.asm.manager.instantiationManager import org.vorpal.research.kex.asm.state.PredicateStateAnalysis import org.vorpal.research.kex.asm.util.accessModifier import org.vorpal.research.kex.config.RuntimeConfig -import org.vorpal.research.kex.descriptor.ArrayDescriptor -import org.vorpal.research.kex.descriptor.ClassDescriptor -import org.vorpal.research.kex.descriptor.ConstantDescriptor -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.ObjectDescriptor -import org.vorpal.research.kex.descriptor.convertToDescriptor +import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.random.Randomizer import org.vorpal.research.kex.reanimator.actionsequence.ActionSequence import org.vorpal.research.kex.reanimator.actionsequence.ActionSequenceExecutor @@ -64,14 +59,21 @@ class RandomObjectReanimator( this.fields.all { it.value.isValid(set) } } } + is ClassDescriptor -> { val set = visited + this this.fields.all { it.value.isValid(set) } } + is ArrayDescriptor -> { val set = visited + this this.elements.all { it.value.isValid(set) } } + + is MockDescriptor -> { + val set = visited + this + this.fields.all { it.value.isValid(set) } && this.allReturns.all { it.isValid(set) } + } } private fun randomObject(): Any { diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt index 486d9c148..8787c3966 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt @@ -185,6 +185,8 @@ class ReflectionList( } } +// TODO MockList? + sealed interface CodeAction { val parameters: List From c8e375755928dab103db2cfcf3d79558c9c5f186 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 14 Oct 2023 13:07:16 +0200 Subject: [PATCH 005/128] TODOs and comments fixes --- .../kotlin/org/vorpal/research/kex/descriptor/descriptor.kt | 5 ++--- .../research/kex/asm/analysis/crash/precondition/builders.kt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 113ecf04a..514765ad5 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -392,7 +392,7 @@ sealed class AbstractFieldContainingDescriptor = e map += this to other - for (key in this.fields.keys.union(other.fields.keys)) { // union or intersect??? + for (key in this.fields.keys.union(other.fields.keys)) { val thisValue = this[key] ?: return false val otherValue = other[key] ?: return false if (!thisValue.structuralEquality(otherValue, map)) return false diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt index ded5c6a82..a288ab687 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt @@ -196,7 +196,7 @@ class DescriptorExceptionPreconditionBuilder( is ConstantDescriptor -> this.asSymbolicState(location, mapping, visited) is FieldContainingDescriptor<*> -> this.asSymbolicState(location, mapping, visited) is ArrayDescriptor -> this.asSymbolicState(location, mapping, visited) - is MockDescriptor -> TODO("Mock. Unimplemented") + is MockDescriptor -> TODO("Mock. Implement later") } private fun ConstantDescriptor.asSymbolicState( From 9d9c2996abb165e7be52f480763e7cb5475baf09 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 14 Oct 2023 19:21:23 +0200 Subject: [PATCH 006/128] DescriptorWrapper.Mock field wrapping --- .../org/vorpal/research/kex/serialization/descriptor.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt b/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt index 6839d3a3b..03811565e 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt @@ -203,10 +203,13 @@ private fun Descriptor.toWrapper(visited: MutableMap) { for (field in this.fields.values) { field.toWrapper(visited) } - for (value in this.allReturns){ + + for ((method, returns) in this.methodReturns) { + instance.methodReturns += method to returns.mapTo(mutableListOf()) { value -> value.id } + } + for (value in this.allReturns) { value.toWrapper(visited) } - } } } From c7a9e58e5cf4f32facb5af738660a35aa47b594a Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 15 Oct 2023 13:49:01 +0200 Subject: [PATCH 007/128] mockDescriptorSerialization test. fix replaceUninstantiableWithMocks --- .../research/kex/parameters/Parameters.kt | 2 +- .../kex/serialization/KexSerializerTest.kt | 28 ++++++++++--------- .../kex/asm/analysis/util/extensions.kt | 2 -- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index fdd309d14..8f9b684e6 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -105,7 +105,7 @@ private fun Collection.replaceUninstantiableWithMocks( ): Collection { return map { descriptor -> val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass - if (klass == null || !instantiationManager.isInstantiable(klass) || descriptor.type.isKexRt) { + if (klass == null || instantiationManager.isInstantiable(klass) || descriptor.type.isKexRt) { descriptor } else { val mock = if (descriptor is ObjectDescriptor) { diff --git a/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt b/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt index 7cb562fd1..55cef6945 100644 --- a/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt +++ b/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt @@ -8,20 +8,9 @@ import org.vorpal.research.kex.KexTest import org.vorpal.research.kex.descriptor.Descriptor import org.vorpal.research.kex.descriptor.DescriptorBuilder import org.vorpal.research.kex.descriptor.convertToDescriptor -import org.vorpal.research.kex.ktype.KexArray -import org.vorpal.research.kex.ktype.KexBool -import org.vorpal.research.kex.ktype.KexClass -import org.vorpal.research.kex.ktype.KexDouble -import org.vorpal.research.kex.ktype.KexInt -import org.vorpal.research.kex.ktype.KexType -import org.vorpal.research.kex.ktype.KexVoid -import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.ktype.* import org.vorpal.research.kex.state.PredicateState -import org.vorpal.research.kex.state.predicate.Predicate -import org.vorpal.research.kex.state.predicate.PredicateType -import org.vorpal.research.kex.state.predicate.assume -import org.vorpal.research.kex.state.predicate.path -import org.vorpal.research.kex.state.predicate.state +import org.vorpal.research.kex.state.predicate.* import org.vorpal.research.kex.state.term.FieldLoadTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term @@ -186,4 +175,17 @@ class KexSerializerTest : KexTest("kex-serializer") { val recursiveFromJson = serializer.fromJson(recursiveJson) assertTrue { recursiveInstance eq recursiveFromJson } } + + + @Test + fun mockDescriptorSerializationTest() { + return with(DescriptorBuilder()) { + val klass = KexClass("java/util/stream/Stream") + val instance = mock(klass, cm.type) + instance["string" to cm.stringClass.kexType] = convertToDescriptor("test") + val instanceJson = serializer.toJson(instance) // TODO register MockDescriptor to remove + val instanceFromJson = serializer.fromJson(instanceJson) + assertTrue { instance eq instanceFromJson } + } + } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 70a94c837..f9eff037c 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -99,9 +99,7 @@ suspend fun Method.checkAsync( ctx.cm, ctx.accessLevel ) - assert(initialDescriptors == withMocks) withMocks -// initialDescriptors .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } } From 9102bdc33bc993296142d43760d62bdafa9aadd6 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 15 Oct 2023 13:52:49 +0200 Subject: [PATCH 008/128] UnimplementedInterfaceConcolicLongTest --- .../UnimplementedInterfaceConcolicLongTest.kt | 18 ++++++++++++++++ .../concolic/UnimplementedInterfaceTests.java | 21 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/UnimplementedInterfaceConcolicLongTest.kt create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/UnimplementedInterfaceConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/UnimplementedInterfaceConcolicLongTest.kt new file mode 100644 index 000000000..09046a50d --- /dev/null +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/UnimplementedInterfaceConcolicLongTest.kt @@ -0,0 +1,18 @@ +package org.vorpal.research.kex.concolic + +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.InternalSerializationApi +import org.junit.Test +import kotlin.time.ExperimentalTime + +@ExperimentalTime +@ExperimentalSerializationApi +@InternalSerializationApi +@DelicateCoroutinesApi +class UnimplementedInterfaceConcolicLongTest : ConcolicTest("unimplemented-interface-concolic") { + @Test + fun unimplementedInterfaceTest() { + assertCoverage(cm["org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests"], 1.0) + } +} diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java new file mode 100644 index 000000000..06d599bba --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java @@ -0,0 +1,21 @@ +package org.vorpal.research.kex.test.concolic; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + +interface Unimplemented { + + int foo(); +} + +@SuppressWarnings("ALL") +public class UnimplementedInterfaceTests { + + public void testUnimplemented(Unimplemented i) { + if (i.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + +} From 6edd5cce64d96d13b04cd133767f4ef5f45c677b Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 15 Oct 2023 14:12:04 +0200 Subject: [PATCH 009/128] Better mockDescriptorSerializationTest: added self-reference --- .../vorpal/research/kex/serialization/KexSerializerTest.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt b/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt index 55cef6945..156dd5570 100644 --- a/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt +++ b/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt @@ -182,8 +182,9 @@ class KexSerializerTest : KexTest("kex-serializer") { return with(DescriptorBuilder()) { val klass = KexClass("java/util/stream/Stream") val instance = mock(klass, cm.type) - instance["string" to cm.stringClass.kexType] = convertToDescriptor("test") - val instanceJson = serializer.toJson(instance) // TODO register MockDescriptor to remove + instance["selfReference" to klass] = instance + // TODO register MockDescriptor to remove + val instanceJson = serializer.toJson(instance) val instanceFromJson = serializer.fromJson(instanceJson) assertTrue { instance eq instanceFromJson } } From 1c7e52cfb6d0905a913a2cadf5dfde514a5d751a Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 15 Oct 2023 15:31:53 +0200 Subject: [PATCH 010/128] MockDescriptor serializer now registered --- .../vorpal/research/kex/serialization/context.kt | 15 +++------------ .../kex/serialization/KexSerializerTest.kt | 4 ++-- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/serialization/context.kt b/core/src/main/kotlin/org/vorpal/research/kex/serialization/context.kt index b373a5c3a..13f9ed64c 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/serialization/context.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/serialization/context.kt @@ -5,23 +5,13 @@ import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.serializer -import org.vorpal.research.kex.descriptor.ArrayDescriptor -import org.vorpal.research.kex.descriptor.ClassDescriptor -import org.vorpal.research.kex.descriptor.ConstantDescriptor -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.FieldContainingDescriptor -import org.vorpal.research.kex.descriptor.ObjectDescriptor +import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.state.PredicateState import org.vorpal.research.kex.state.predicate.Predicate import org.vorpal.research.kex.state.predicate.PredicateType import org.vorpal.research.kex.state.term.Term -import org.vorpal.research.kex.trace.symbolic.Clause -import org.vorpal.research.kex.trace.symbolic.PathClause -import org.vorpal.research.kex.trace.symbolic.StateClause -import org.vorpal.research.kex.trace.symbolic.SymbolicState -import org.vorpal.research.kex.trace.symbolic.SymbolicStateImpl -import org.vorpal.research.kex.trace.symbolic.WrappedValue +import org.vorpal.research.kex.trace.symbolic.* import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kfg.ir.value.NameMapperContext @@ -105,6 +95,7 @@ fun getDescriptorSerialModule(): SerializersModule = SerializersModule { contextual(ObjectDescriptor::class, descriptorSerializer.to()) contextual(ClassDescriptor::class, descriptorSerializer.to()) contextual(ArrayDescriptor::class, descriptorSerializer.to()) + contextual(MockDescriptor::class, descriptorSerializer.to()) } @InternalSerializationApi diff --git a/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt b/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt index 156dd5570..3ed75e35b 100644 --- a/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt +++ b/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt @@ -183,8 +183,8 @@ class KexSerializerTest : KexTest("kex-serializer") { val klass = KexClass("java/util/stream/Stream") val instance = mock(klass, cm.type) instance["selfReference" to klass] = instance - // TODO register MockDescriptor to remove - val instanceJson = serializer.toJson(instance) + instance["string" to cm.stringClass.kexType] = convertToDescriptor("test") + val instanceJson = serializer.toJson(instance) val instanceFromJson = serializer.fromJson(instanceJson) assertTrue { instance eq instanceFromJson } } From a00a36073ca7bb90afea38c12852f26ac0d88364 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 16 Oct 2023 13:09:54 +0200 Subject: [PATCH 011/128] mockDescriptorSerializationTest: testing returnValues serialization --- .../kotlin/org/vorpal/research/kex/descriptor/descriptor.kt | 2 +- .../kotlin/org/vorpal/research/kex/parameters/Parameters.kt | 2 +- .../org/vorpal/research/kex/serialization/KexSerializerTest.kt | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 07d2eb4bf..a873bd058 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -793,7 +793,7 @@ open class DescriptorBuilder : StringInfoContext() { fun array(length: Int, elementType: KexType): ArrayDescriptor = ArrayDescriptor(elementType, length) - fun mock(type: KexClass, methods: Collection = emptyList()) = MockDescriptor(type, methods) + fun mock(type: KexClass, methods: Collection) = MockDescriptor(type, methods) fun mock(type: KexClass, types: TypeFactory): MockDescriptor = MockDescriptor(type, (type.getKfgType(types) as ClassType).klass.methods) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 1b0770a86..992bc3e61 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -11,10 +11,10 @@ import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term +import org.vorpal.research.kex.util.KfgTargetFilter import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory -import org.vorpal.research.kex.util.KfgTargetFilter import kotlin.random.Random @Serializable diff --git a/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt b/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt index 3ed75e35b..55178a336 100644 --- a/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt +++ b/core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt @@ -15,6 +15,7 @@ import org.vorpal.research.kex.state.term.FieldLoadTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kfg.stringClass +import org.vorpal.research.kfg.type.ClassType import kotlin.test.assertTrue @ExperimentalSerializationApi @@ -181,7 +182,9 @@ class KexSerializerTest : KexTest("kex-serializer") { fun mockDescriptorSerializationTest() { return with(DescriptorBuilder()) { val klass = KexClass("java/util/stream/Stream") + val toArrayMethod = (klass.getKfgType(cm.type) as ClassType).klass.methods.first { it.name == "toArray" } val instance = mock(klass, cm.type) + instance.addReturnValue(toArrayMethod, convertToDescriptor(Array(2) { Any() })) instance["selfReference" to klass] = instance instance["string" to cm.stringClass.kexType] = convertToDescriptor("test") val instanceJson = serializer.toJson(instance) From 932c83cc3620797fda3768274323a33cb6626f0a Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 23 Oct 2023 15:33:35 +0200 Subject: [PATCH 012/128] Fixed MockSequence mapping. Fixed tests --- .../research/kex/util/KfgClassLoader.kt | 1 + .../actionsequence/ActionSequence.kt | 113 +++++++++++++++++- .../generator/ConcolicSequenceGenerator.kt | 1 + .../actionsequence/generator/MockGenerator.kt | 63 ++++++++++ .../generator/UnknownGenerator.kt | 29 +---- .../javagen/ActionSequence2JavaPrinter.kt | 67 ++--------- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 46 ++----- .../kotlingen/ActionSequence2KotlinPrinter.kt | 59 +-------- .../runner/SymbolicExternalTracingRunner.kt | 10 +- .../concolic/UnimplementedInterfaceTests.java | 7 +- 10 files changed, 209 insertions(+), 187 deletions(-) create mode 100644 kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt diff --git a/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 0ae918e21..c38f671d8 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -33,6 +33,7 @@ class KfgClassLoader( "class org.vorpal.research.kex.test.concolic.SetConcolicTests", "class org.vorpal.research.kex.test.concolic.StringConcolicTests", "class org.vorpal.research.kex.test.concolic.TestEnum", + "class org.vorpal.research.kex.test.concolic.UnimplementedInterfaceTests" ).mapTo(mutableSetOf()) { KfgTargetFilter.parse(it) } private val EXCLUDES = setOf( "package java.*", diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt index 14af50aec..96ce87f16 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt @@ -34,7 +34,7 @@ class PrimaryValue(val value: T) : ActionSequence(value.toString()) { override fun clone(): ActionSequence = this } -class StringValue(val value: String = "") : ActionSequence(value) { +class StringValue(val value: String = "") : ActionSequence(value) { override val isConstantValue: Boolean get() = true override fun print() = value @@ -97,6 +97,7 @@ class ActionList( is ActionList -> top.flatMap { it.parameters }.forEach { queue += it } + else -> {} } } @@ -169,6 +170,7 @@ class ReflectionList( return builder.toString() } + override fun print(builder: StringBuilder, visited: MutableSet) { if (this in visited) return visited += this @@ -186,7 +188,60 @@ class ReflectionList( } } -// TODO MockList? +class MockSequence( + name: String, + val mockitoCalls: MutableList, // creates instance and setup methods + val reflectionActions: MutableList // only setup fields +) : ActionSequence(name) { + constructor(name: String) : this(name, mutableListOf(), mutableListOf()) + + override fun print(): String { + val builder = StringBuilder() + this.print(builder, mutableSetOf()) + return builder.toString() + } + + override fun print(builder: StringBuilder, visited: MutableSet) { + if (this in visited) return + visited += this + for (mockCall in mockitoCalls) { + mockCall.print(this, builder, visited) + } + for (reflectionCall in reflectionActions) { + reflectionCall.print(this, builder, visited) + } + } + + override fun clone() = MockSequence(name, mockitoCalls.toMutableList(), reflectionActions.toMutableList()) +} + +sealed interface MockitoCall { + val parameters: List + + fun print(owner: ActionSequence, builder: StringBuilder, visited: MutableSet) +} + +data class MockitoNewInstance(val klass: Class) : MockitoCall { + override val parameters: List + get() = listOf() + + override fun toString() = "Mockito.mock(${klass.fullName}})" + + override fun print(owner: ActionSequence, builder: StringBuilder, visited: MutableSet) { + builder.appendLine { "${owner.name} = $this" } + } +} + +data class MockitoSetupMethod( + val method: Method, val returnValues: List +) : MockitoCall { + override val parameters: List + get() = returnValues + + override fun print(owner: ActionSequence, builder: StringBuilder, visited: MutableSet) { + builder.appendLine { "${owner.name}.$method returns $returnValues" } + } +} sealed interface CodeAction { val parameters: List @@ -260,8 +315,7 @@ data class InnerClassConstructorCall( val constructor: Method, val outerObject: ActionSequence, val args: List -) : - CodeAction { +) : CodeAction { override val parameters: List get() = listOf(outerObject) + args override fun toString() = "${outerObject}.$constructor(${args.joinToString(", ")})" @@ -362,7 +416,15 @@ data class NewArrayWithInitializer( override fun toString() = elements.joinToString(", ", prefix = "{", postfix = "}") { it.name } override fun print(owner: ActionSequence, builder: StringBuilder, visited: MutableSet) { elements.forEach { it.print(builder, visited) } - builder.appendLine("${asArray.component} ${owner.name} = ${elements.joinToString(", ", prefix = "{", postfix = "}") { it.name }}") + builder.appendLine( + "${asArray.component} ${owner.name} = ${ + elements.joinToString( + ", ", + prefix = "{", + postfix = "}" + ) { it.name } + }" + ) } } @@ -485,6 +547,7 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { val newTarget = mapper.map(ct.target) UnknownSequence(newTarget.term.toString(), ct.type.mapped, newTarget) } + is TestCall -> TestCall(ct.name, ct.test, ct.instance?.let { map(it) }, ct.args.map { map(it) }) is ReflectionList -> when (ct) { in cache -> cache[ct]!! @@ -497,6 +560,7 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { res } } + is ActionList -> when (ct) { in cache -> cache[ct]!! else -> { @@ -508,6 +572,23 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { res } } + + is MockSequence -> when (ct) { + in cache -> cache[ct]!! + else -> { + val res = MockSequence(ct.name.mapped) + cache[ct] = res + for (mockCall in ct.mockitoCalls) { + res.mockitoCalls += map(mockCall) + } + for (reflectionCall in ct.reflectionActions) { + res.reflectionActions += map(reflectionCall) + } + + // TODO: Mock. Not sure if it works + res + } + } } private val Class.mapped @@ -516,6 +597,12 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { KexRtManager.Mode.UNMAP -> rtUnmapped } + private val Method.mapped + get() = when (mode) { + KexRtManager.Mode.MAP -> rtMapped + KexRtManager.Mode.UNMAP -> rtUnmapped + } + private val Type.mapped get() = when (mode) { KexRtManager.Mode.MAP -> rtMapped @@ -537,6 +624,7 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { val unmappedField = unmappedKlass.getField(api.field.name, api.field.type.mapped) ReflectionSetField(unmappedField, map(api.value)) } + is ReflectionSetStaticField -> { val unmappedKlass = api.field.klass.mapped val unmappedField = unmappedKlass.getField(api.field.name, api.field.type.mapped) @@ -555,10 +643,12 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { ) ConstructorCall(unmappedMethod, api.args.map { map(it) }) } + is DefaultConstructorCall -> { val unmappedKlass = api.klass.mapped DefaultConstructorCall(unmappedKlass) } + is EnumValueCreation -> EnumValueCreation(api.klass.mapped, api.name) is ExternalConstructorCall -> { val unmappedKlass = api.constructor.klass.mapped @@ -569,6 +659,7 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { ) ExternalConstructorCall(unmappedMethod, api.args.map { map(it) }) } + is ExternalMethodCall -> { val unmappedKlass = api.method.klass.mapped val unmappedMethod = unmappedKlass.getMethod( @@ -578,11 +669,13 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { ) ExternalMethodCall(unmappedMethod, map(api.instance), api.args.map { map(it) }) } + is FieldSetter -> { val unmappedKlass = api.field.klass.mapped val unmappedField = unmappedKlass.getField(api.field.name, api.field.type.mapped) FieldSetter(unmappedField, map(api.value)) } + is InnerClassConstructorCall -> { val unmappedKlass = api.constructor.klass.mapped val unmappedMethod = unmappedKlass.getMethod( @@ -593,6 +686,7 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { InnerClassConstructorCall(unmappedMethod, map(api.outerObject), api.args.map { map(it) }) } + is MethodCall -> { val unmappedKlass = api.method.klass.mapped val unmappedMethod = unmappedKlass.getMethod( @@ -602,6 +696,7 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { ) MethodCall(unmappedMethod, api.args.map { map(it) }) } + is NewArray -> NewArray(api.klass.mapped, map(api.length)) is NewArrayWithInitializer -> NewArrayWithInitializer(api.klass.mapped, api.elements.map { map(it) }) is StaticFieldGetter -> { @@ -609,11 +704,13 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { val unmappedField = unmappedKlass.getField(api.field.name, api.field.type.mapped) StaticFieldGetter(unmappedField) } + is StaticFieldSetter -> { val unmappedKlass = api.field.klass.mapped val unmappedField = unmappedKlass.getField(api.field.name, api.field.type.mapped) StaticFieldSetter(unmappedField, map(api.value)) } + is StaticMethodCall -> { val unmappedKlass = api.method.klass.mapped val unmappedMethod = unmappedKlass.getMethod( @@ -623,7 +720,13 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { ) StaticMethodCall(unmappedMethod, api.args.map { map(it) }) } + is ClassConstantGetter -> ClassConstantGetter(api.type.mapped) is ArrayClassConstantGetter -> ArrayClassConstantGetter(map(api.elementType)) } + + fun map(api: MockitoCall): MockitoCall = when (api) { + is MockitoNewInstance -> MockitoNewInstance(api.klass.mapped) + is MockitoSetupMethod -> MockitoSetupMethod(api.method.mapped, api.returnValues.map { value -> map(value) }) + } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/ConcolicSequenceGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/ConcolicSequenceGenerator.kt index 28cc63c90..6e129b041 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/ConcolicSequenceGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/ConcolicSequenceGenerator.kt @@ -23,6 +23,7 @@ class ConcolicSequenceGenerator(override val context: GeneratorContext) : Genera typeGenerators += FieldGenerator(this) typeGenerators += ReflectionEnumGenerator(this) typeGenerators += KexRtGenerator(this) + typeGenerators += MockGenerator(this) typeGenerators += UnknownGenerator(this) } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt new file mode 100644 index 000000000..efadc14b1 --- /dev/null +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt @@ -0,0 +1,63 @@ +package org.vorpal.research.kex.reanimator.actionsequence.generator + +import org.vorpal.research.kex.descriptor.Descriptor +import org.vorpal.research.kex.descriptor.MockDescriptor +import org.vorpal.research.kex.ktype.KexRtManager.rtUnmapped +import org.vorpal.research.kex.ktype.KexType +import org.vorpal.research.kex.reanimator.actionsequence.* +import org.vorpal.research.kfg.UnknownInstanceException +import org.vorpal.research.kfg.ir.Class +import org.vorpal.research.kfg.type.ClassType +import org.vorpal.research.kfg.type.TypeFactory +import org.vorpal.research.kthelper.logging.log + +class MockGenerator(private val fallback: Generator) : Generator { + override val context get() = fallback.context + + override fun supports(descriptor: Descriptor): Boolean = descriptor is MockDescriptor + + override fun generate(descriptor: Descriptor, generationDepth: Int): ActionSequence = with(context) { + descriptor as? MockDescriptor ?: throw IllegalArgumentException("Expected MockDescriptor. Got: $descriptor") + descriptor as MockDescriptor // TODO remove. Helps autocompletion + + val name = "${descriptor.term}" + val actionSequence = MockSequence(name) + saveToCache(descriptor, actionSequence) + val kfgClass = (descriptor.type.getKfgType(types) as ClassType).klass + actionSequence.mockitoCalls.add(MockitoNewInstance(kfgClass)) + + for ((method, returnValuesDesc) in descriptor.methodReturns) { + if (method !in kfgClass.methods) { + log.warn("Method $method is not found in class $kfgClass") + continue + } + val returnValues = returnValuesDesc.map { value -> fallback.generate(value) } + actionSequence.mockitoCalls += MockitoSetupMethod(method, returnValues) + } + + actionSequence.reflectionActions.addSetupFieldsCalls(descriptor.fields, kfgClass, types, fallback) + + getFromCache(descriptor)!! + } + + +} + +fun MutableList.addSetupFieldsCalls( + fields: MutableMap, Descriptor>, + kfgClass: Class, + types: TypeFactory, + fallback: Generator +) { + for ((field, value) in fields) { + val fieldType = field.second.getKfgType(types).rtUnmapped + val kfgField = try { + kfgClass.getField(field.first, fieldType) + } catch (e: UnknownInstanceException) { + log.warn("Field ${field.first}: ${field.second} is not found in class $kfgClass") + continue + } + val valueAS = fallback.generate(value) + this += ReflectionSetField(kfgField, valueAS) + } +} diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/UnknownGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/UnknownGenerator.kt index 2413e6b60..341d112ac 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/UnknownGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/UnknownGenerator.kt @@ -1,22 +1,10 @@ package org.vorpal.research.kex.reanimator.actionsequence.generator -import org.vorpal.research.kex.descriptor.ArrayDescriptor -import org.vorpal.research.kex.descriptor.ClassDescriptor -import org.vorpal.research.kex.descriptor.ConstantDescriptor -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.ObjectDescriptor +import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexNull import org.vorpal.research.kex.ktype.KexRtManager.rtUnmapped import org.vorpal.research.kex.ktype.kexType -import org.vorpal.research.kex.reanimator.actionsequence.ActionSequence -import org.vorpal.research.kex.reanimator.actionsequence.PrimaryValue -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionArrayWrite -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionList -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionNewArray -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionNewInstance -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionSetField -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionSetStaticField -import org.vorpal.research.kex.reanimator.actionsequence.UnknownSequence +import org.vorpal.research.kex.reanimator.actionsequence.* import org.vorpal.research.kfg.UnknownInstanceException import org.vorpal.research.kfg.type.ArrayType import org.vorpal.research.kfg.type.ClassType @@ -41,17 +29,7 @@ class UnknownGenerator( val kfgClass = (descriptor.type.getKfgType(types) as ClassType).klass actionSequence += ReflectionNewInstance(kfgClass.asType) - for ((field, value) in descriptor.fields) { - val fieldType = field.second.getKfgType(types).rtUnmapped - val kfgField = try { - kfgClass.getField(field.first, fieldType) - } catch (e: UnknownInstanceException) { - log.warn("Field ${field.first}: ${field.second} is not found in class $kfgClass") - continue - } - val valueAS = fallback.generate(value) - actionSequence += ReflectionSetField(kfgField, valueAS) - } + actionSequence.list.addSetupFieldsCalls(descriptor.fields, kfgClass, types, fallback) } is ArrayDescriptor -> { val kfgArray = (descriptor.type.getKfgType(types) as ArrayType) @@ -68,6 +46,7 @@ class UnknownGenerator( } is ClassDescriptor -> { val kfgClass = (descriptor.type.getKfgType(types) as ClassType).klass + actionSequence.list.addSetupFieldsCalls(descriptor.fields, kfgClass, types, fallback) for ((field, value) in descriptor.fields) { val fieldType = field.second.getKfgType(types).rtUnmapped val kfgField = try { diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt index 6bb5ecc75..a0abc957d 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt @@ -6,69 +6,17 @@ import org.vorpal.research.kex.asm.util.accessModifier import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.parameters.Parameters -import org.vorpal.research.kex.reanimator.actionsequence.ActionList -import org.vorpal.research.kex.reanimator.actionsequence.ActionSequence -import org.vorpal.research.kex.reanimator.actionsequence.ArrayClassConstantGetter -import org.vorpal.research.kex.reanimator.actionsequence.ArrayWrite -import org.vorpal.research.kex.reanimator.actionsequence.ClassConstantGetter -import org.vorpal.research.kex.reanimator.actionsequence.CodeAction -import org.vorpal.research.kex.reanimator.actionsequence.ConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.DefaultConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.EnumValueCreation -import org.vorpal.research.kex.reanimator.actionsequence.ExternalConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.ExternalMethodCall -import org.vorpal.research.kex.reanimator.actionsequence.FieldSetter -import org.vorpal.research.kex.reanimator.actionsequence.InnerClassConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.MethodCall -import org.vorpal.research.kex.reanimator.actionsequence.NewArray -import org.vorpal.research.kex.reanimator.actionsequence.NewArrayWithInitializer -import org.vorpal.research.kex.reanimator.actionsequence.PrimaryValue -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionArrayWrite -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionCall -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionList -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionNewArray -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionNewInstance -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionSetField -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionSetStaticField -import org.vorpal.research.kex.reanimator.actionsequence.StaticFieldGetter -import org.vorpal.research.kex.reanimator.actionsequence.StaticFieldSetter -import org.vorpal.research.kex.reanimator.actionsequence.StaticMethodCall -import org.vorpal.research.kex.reanimator.actionsequence.StringValue -import org.vorpal.research.kex.reanimator.actionsequence.TestCall -import org.vorpal.research.kex.reanimator.actionsequence.UnknownSequence +import org.vorpal.research.kex.reanimator.actionsequence.* import org.vorpal.research.kex.reanimator.codegen.ActionSequencePrinter -import org.vorpal.research.kex.util.getConstructor -import org.vorpal.research.kex.util.getMethod -import org.vorpal.research.kex.util.isSubtypeOfCached -import org.vorpal.research.kex.util.javaString -import org.vorpal.research.kex.util.kex -import org.vorpal.research.kex.util.loadClass +import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.ir.Class -import org.vorpal.research.kfg.type.ArrayType -import org.vorpal.research.kfg.type.BoolType -import org.vorpal.research.kfg.type.ByteType -import org.vorpal.research.kfg.type.CharType -import org.vorpal.research.kfg.type.ClassType -import org.vorpal.research.kfg.type.DoubleType -import org.vorpal.research.kfg.type.FloatType -import org.vorpal.research.kfg.type.IntType -import org.vorpal.research.kfg.type.LongType -import org.vorpal.research.kfg.type.NullType -import org.vorpal.research.kfg.type.ShortType +import org.vorpal.research.kfg.type.* import org.vorpal.research.kfg.type.Type -import org.vorpal.research.kfg.type.VoidType -import org.vorpal.research.kfg.type.classType -import org.vorpal.research.kfg.type.objectType import org.vorpal.research.kthelper.assert.ktassert import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.tryOrNull -import java.lang.reflect.Constructor -import java.lang.reflect.GenericArrayType -import java.lang.reflect.Method -import java.lang.reflect.ParameterizedType -import java.lang.reflect.TypeVariable -import java.lang.reflect.WildcardType +import java.lang.reflect.* private val testTimeout by lazy { kexConfig.getIntValue("testGen", "testTimeout", 10) @@ -397,6 +345,8 @@ open class ActionSequence2JavaPrinter( is StringValue -> listOf().also { asConstant } + + is MockSequence -> TODO("Mock. Unimplemented") } with(current) { for (statement in statements) @@ -871,7 +821,10 @@ open class ActionSequence2JavaPrinter( ) } - protected open fun printArrayClassConstantGetter(owner: ActionSequence, call: ArrayClassConstantGetter): List { + protected open fun printArrayClassConstantGetter( + owner: ActionSequence, + call: ArrayClassConstantGetter + ): List { call.elementType.printAsJava() val actualType = ASClass(ctx.types.classType) actualTypes[owner] = actualType diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 76f76810d..0fb2babb7 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -2,43 +2,12 @@ package org.vorpal.research.kex.reanimator.codegen.javagen import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.config.kexConfig -import org.vorpal.research.kex.ktype.KexBool -import org.vorpal.research.kex.ktype.KexByte -import org.vorpal.research.kex.ktype.KexChar -import org.vorpal.research.kex.ktype.KexDouble -import org.vorpal.research.kex.ktype.KexFloat -import org.vorpal.research.kex.ktype.KexInt -import org.vorpal.research.kex.ktype.KexLong -import org.vorpal.research.kex.ktype.KexShort -import org.vorpal.research.kex.ktype.KexType -import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.ktype.* import org.vorpal.research.kex.parameters.Parameters -import org.vorpal.research.kex.reanimator.actionsequence.ActionList -import org.vorpal.research.kex.reanimator.actionsequence.ActionSequence -import org.vorpal.research.kex.reanimator.actionsequence.ConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.DefaultConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.EnumValueCreation -import org.vorpal.research.kex.reanimator.actionsequence.ExternalConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.ExternalMethodCall -import org.vorpal.research.kex.reanimator.actionsequence.InnerClassConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.MethodCall -import org.vorpal.research.kex.reanimator.actionsequence.NewArray -import org.vorpal.research.kex.reanimator.actionsequence.PrimaryValue -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionArrayWrite -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionList -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionNewArray -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionNewInstance -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionSetField -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionSetStaticField -import org.vorpal.research.kex.reanimator.actionsequence.StaticFieldGetter -import org.vorpal.research.kex.reanimator.actionsequence.StringValue -import org.vorpal.research.kex.reanimator.actionsequence.UnknownSequence -import org.vorpal.research.kfg.type.ArrayType -import org.vorpal.research.kfg.type.ClassType -import org.vorpal.research.kfg.type.PrimitiveType -import org.vorpal.research.kfg.type.Type -import org.vorpal.research.kfg.type.objectType +import org.vorpal.research.kex.reanimator.actionsequence.* +import org.vorpal.research.kfg.type.* import org.vorpal.research.kthelper.assert.unreachable +import org.vorpal.research.kthelper.logging.error import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.runIf @@ -127,6 +96,13 @@ class ExecutorAS2JavaPrinter( } } ?: unreachable { log.error("Unexpected call in arg") } + is MockSequence -> arg.mockitoCalls.firstNotNullOfOrNull { + when (it) { + is MockitoNewInstance -> it.klass.asType + else -> null + } + } ?: unreachable { log.error { "Unexpected call in arg" } } + is PrimaryValue<*> -> return@forEach is StringValue -> return@forEach else -> unreachable { log.error("Unexpected call in arg") } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/kotlingen/ActionSequence2KotlinPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/kotlingen/ActionSequence2KotlinPrinter.kt index 5ef17f33c..524510413 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/kotlingen/ActionSequence2KotlinPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/kotlingen/ActionSequence2KotlinPrinter.kt @@ -3,64 +3,15 @@ package org.vorpal.research.kex.reanimator.codegen.kotlingen import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.parameters.Parameters -import org.vorpal.research.kex.reanimator.actionsequence.ActionList -import org.vorpal.research.kex.reanimator.actionsequence.ActionSequence -import org.vorpal.research.kex.reanimator.actionsequence.ArrayClassConstantGetter -import org.vorpal.research.kex.reanimator.actionsequence.ArrayWrite -import org.vorpal.research.kex.reanimator.actionsequence.ClassConstantGetter -import org.vorpal.research.kex.reanimator.actionsequence.CodeAction -import org.vorpal.research.kex.reanimator.actionsequence.ConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.DefaultConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.EnumValueCreation -import org.vorpal.research.kex.reanimator.actionsequence.ExternalConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.ExternalMethodCall -import org.vorpal.research.kex.reanimator.actionsequence.FieldSetter -import org.vorpal.research.kex.reanimator.actionsequence.InnerClassConstructorCall -import org.vorpal.research.kex.reanimator.actionsequence.MethodCall -import org.vorpal.research.kex.reanimator.actionsequence.NewArray -import org.vorpal.research.kex.reanimator.actionsequence.NewArrayWithInitializer -import org.vorpal.research.kex.reanimator.actionsequence.PrimaryValue -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionArrayWrite -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionCall -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionList -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionNewArray -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionNewInstance -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionSetField -import org.vorpal.research.kex.reanimator.actionsequence.ReflectionSetStaticField -import org.vorpal.research.kex.reanimator.actionsequence.StaticFieldGetter -import org.vorpal.research.kex.reanimator.actionsequence.StaticFieldSetter -import org.vorpal.research.kex.reanimator.actionsequence.StaticMethodCall -import org.vorpal.research.kex.reanimator.actionsequence.StringValue -import org.vorpal.research.kex.reanimator.actionsequence.TestCall -import org.vorpal.research.kex.reanimator.actionsequence.UnknownSequence +import org.vorpal.research.kex.reanimator.actionsequence.* import org.vorpal.research.kex.reanimator.codegen.ActionSequencePrinter -import org.vorpal.research.kex.util.getConstructor -import org.vorpal.research.kex.util.getMethod -import org.vorpal.research.kex.util.isSubtypeOfCached -import org.vorpal.research.kex.util.kex -import org.vorpal.research.kex.util.loadClass +import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.ir.Class -import org.vorpal.research.kfg.type.ArrayType -import org.vorpal.research.kfg.type.BoolType -import org.vorpal.research.kfg.type.ByteType -import org.vorpal.research.kfg.type.CharType -import org.vorpal.research.kfg.type.ClassType -import org.vorpal.research.kfg.type.DoubleType -import org.vorpal.research.kfg.type.FloatType -import org.vorpal.research.kfg.type.IntType -import org.vorpal.research.kfg.type.LongType -import org.vorpal.research.kfg.type.NullType -import org.vorpal.research.kfg.type.ShortType +import org.vorpal.research.kfg.type.* import org.vorpal.research.kfg.type.Type -import org.vorpal.research.kfg.type.VoidType -import org.vorpal.research.kfg.type.classType import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.log -import java.lang.reflect.Constructor -import java.lang.reflect.Method -import java.lang.reflect.ParameterizedType -import java.lang.reflect.TypeVariable -import java.lang.reflect.WildcardType +import java.lang.reflect.* import kotlin.reflect.KClass import kotlin.reflect.KClassifier import kotlin.reflect.KType @@ -365,6 +316,8 @@ open class ActionSequence2KotlinPrinter( is StringValue -> listOf().also { asConstant } + + is MockSequence -> TODO("Mock") } with(current) { for (statement in statements) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt index 6913d2010..89970b4d2 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt @@ -5,13 +5,7 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.config.kexConfig -import org.vorpal.research.kex.trace.symbolic.protocol.Client2MasterConnection -import org.vorpal.research.kex.trace.symbolic.protocol.ControllerProtocolHandler -import org.vorpal.research.kex.trace.symbolic.protocol.ControllerProtocolSocketHandler -import org.vorpal.research.kex.trace.symbolic.protocol.ExecutionCompletedResult -import org.vorpal.research.kex.trace.symbolic.protocol.ExecutionResult -import org.vorpal.research.kex.trace.symbolic.protocol.ExecutionTimedOutResult -import org.vorpal.research.kex.trace.symbolic.protocol.TestExecutionRequest +import org.vorpal.research.kex.trace.symbolic.protocol.* import org.vorpal.research.kex.util.getJvmModuleParams import org.vorpal.research.kex.util.getPathSeparator import org.vorpal.research.kex.util.outputDirectory @@ -109,7 +103,7 @@ class SymbolicExternalTracingRunner(val ctx: ExecutionContext) { is ExecutionCompletedResult -> log.debug("Execution result: {}", result.trace) else -> log.debug("Execution result: {}", result) } - //log.debug("Test {} executed with result {}", klass, result) + log.debug("Test {} executed with result {}", klass, result) return result ?: ExecutionTimedOutResult("Connection timeout") } } diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java index 06d599bba..9c0e2e44b 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java @@ -2,13 +2,12 @@ import org.vorpal.research.kex.intrinsics.AssertIntrinsics; -interface Unimplemented { - - int foo(); -} @SuppressWarnings("ALL") public class UnimplementedInterfaceTests { + public interface Unimplemented { + int foo(); + } public void testUnimplemented(Unimplemented i) { if (i.foo() == 42) { From e8c1a83bc2f24956e4ee4844e4589ef1a273ce48 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 31 Oct 2023 14:14:48 +0100 Subject: [PATCH 013/128] Mockito.mock printing --- .../research/kex/util/KfgClassLoader.kt | 3 +- .../kex/asm/analysis/util/extensions.kt | 1 + .../actionsequence/ActionSequence.kt | 32 ++++++++-------- .../actionsequence/generator/MockGenerator.kt | 6 +-- .../javagen/ActionSequence2JavaPrinter.kt | 29 +++++++++++++- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 38 ++++++++++++++++++- 6 files changed, 86 insertions(+), 23 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index c38f671d8..c9ea3dd45 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -33,7 +33,8 @@ class KfgClassLoader( "class org.vorpal.research.kex.test.concolic.SetConcolicTests", "class org.vorpal.research.kex.test.concolic.StringConcolicTests", "class org.vorpal.research.kex.test.concolic.TestEnum", - "class org.vorpal.research.kex.test.concolic.UnimplementedInterfaceTests" + "class org.vorpal.research.kex.test.concolic.UnimplementedInterfaceTests", + "class org.vorpal.research.kex.test.concolic.Unimplemented" ).mapTo(mutableSetOf()) { KfgTargetFilter.parse(it) } private val EXCLUDES = setOf( "package java.*", diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index f9eff037c..5b7e58f26 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -99,6 +99,7 @@ suspend fun Method.checkAsync( ctx.cm, ctx.accessLevel ) + withMocks .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt index 96ce87f16..be38112fd 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt @@ -190,8 +190,8 @@ class ReflectionList( class MockSequence( name: String, - val mockitoCalls: MutableList, // creates instance and setup methods - val reflectionActions: MutableList // only setup fields + val mockCalls: MutableList, // create instance and setup methods + val reflectionCalls: MutableList // only setup fields ) : ActionSequence(name) { constructor(name: String) : this(name, mutableListOf(), mutableListOf()) @@ -204,24 +204,24 @@ class MockSequence( override fun print(builder: StringBuilder, visited: MutableSet) { if (this in visited) return visited += this - for (mockCall in mockitoCalls) { + for (mockCall in mockCalls) { mockCall.print(this, builder, visited) } - for (reflectionCall in reflectionActions) { + for (reflectionCall in reflectionCalls) { reflectionCall.print(this, builder, visited) } } - override fun clone() = MockSequence(name, mockitoCalls.toMutableList(), reflectionActions.toMutableList()) + override fun clone() = MockSequence(name, mockCalls.toMutableList(), reflectionCalls.toMutableList()) } -sealed interface MockitoCall { +sealed interface MockCall { val parameters: List fun print(owner: ActionSequence, builder: StringBuilder, visited: MutableSet) } -data class MockitoNewInstance(val klass: Class) : MockitoCall { +data class MockNewInstance(val klass: Class) : MockCall { override val parameters: List get() = listOf() @@ -232,9 +232,9 @@ data class MockitoNewInstance(val klass: Class) : MockitoCall { } } -data class MockitoSetupMethod( +data class MockSetupMethod( val method: Method, val returnValues: List -) : MockitoCall { +) : MockCall { override val parameters: List get() = returnValues @@ -578,11 +578,11 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { else -> { val res = MockSequence(ct.name.mapped) cache[ct] = res - for (mockCall in ct.mockitoCalls) { - res.mockitoCalls += map(mockCall) + for (mockCall in ct.mockCalls) { + res.mockCalls += map(mockCall) } - for (reflectionCall in ct.reflectionActions) { - res.reflectionActions += map(reflectionCall) + for (reflectionCall in ct.reflectionCalls) { + res.reflectionCalls += map(reflectionCall) } // TODO: Mock. Not sure if it works @@ -725,8 +725,8 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { is ArrayClassConstantGetter -> ArrayClassConstantGetter(map(api.elementType)) } - fun map(api: MockitoCall): MockitoCall = when (api) { - is MockitoNewInstance -> MockitoNewInstance(api.klass.mapped) - is MockitoSetupMethod -> MockitoSetupMethod(api.method.mapped, api.returnValues.map { value -> map(value) }) + fun map(api: MockCall): MockCall = when (api) { + is MockNewInstance -> MockNewInstance(api.klass.mapped) + is MockSetupMethod -> MockSetupMethod(api.method.mapped, api.returnValues.map { value -> map(value) }) } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt index efadc14b1..6475ac6f1 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt @@ -24,7 +24,7 @@ class MockGenerator(private val fallback: Generator) : Generator { val actionSequence = MockSequence(name) saveToCache(descriptor, actionSequence) val kfgClass = (descriptor.type.getKfgType(types) as ClassType).klass - actionSequence.mockitoCalls.add(MockitoNewInstance(kfgClass)) + actionSequence.mockCalls.add(MockNewInstance(kfgClass)) for ((method, returnValuesDesc) in descriptor.methodReturns) { if (method !in kfgClass.methods) { @@ -32,10 +32,10 @@ class MockGenerator(private val fallback: Generator) : Generator { continue } val returnValues = returnValuesDesc.map { value -> fallback.generate(value) } - actionSequence.mockitoCalls += MockitoSetupMethod(method, returnValues) + actionSequence.mockCalls += MockSetupMethod(method, returnValues) } - actionSequence.reflectionActions.addSetupFieldsCalls(descriptor.fields, kfgClass, types, fallback) + actionSequence.reflectionCalls.addSetupFieldsCalls(descriptor.fields, kfgClass, types, fallback) getFromCache(descriptor)!! } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt index a0abc957d..cfd26560d 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt @@ -252,6 +252,11 @@ open class ActionSequence2JavaPrinter( actionSequence.args.forEach { resolveTypes(it, visited) } } + is MockSequence -> { + actionSequence.mockCalls.reversed().map { resolveTypes(it, visited) } + actionSequence.reflectionCalls.reversed().map { resolveTypes(it, visited) } + } + else -> {} } } @@ -330,6 +335,11 @@ open class ActionSequence2JavaPrinter( is ReflectionNewInstance -> {} } + private fun resolveTypes(mockCall: MockCall, visited: MutableSet): Unit = when (mockCall) { + is MockNewInstance -> {} + is MockSetupMethod -> mockCall.returnValues.forEach { value -> resolveTypes(value, visited) } + } + protected open fun ActionSequence.printAsJava() { if (name in printedStacks) return printedStacks += name @@ -346,7 +356,7 @@ open class ActionSequence2JavaPrinter( asConstant } - is MockSequence -> TODO("Mock. Unimplemented") + is MockSequence -> printMockSequence(this) } with(current) { for (statement in statements) @@ -360,6 +370,12 @@ open class ActionSequence2JavaPrinter( protected open fun printReflectionList(reflectionList: ReflectionList): List = reflectionList.flatMap { printReflectionCall(reflectionList, it) } + protected open fun printMockSequence(mockSequence: MockSequence): List = mutableListOf().apply { + addAll(mockSequence.mockCalls.flatMap { printMockCall(mockSequence, it) }) + addAll(mockSequence.reflectionCalls.flatMap { printReflectionCall(mockSequence, it)}) + } + + protected val Class.javaString: String get() = this.asType.javaString @Suppress("RecursivePropertyAccessor") @@ -428,6 +444,11 @@ open class ActionSequence2JavaPrinter( is ArrayClassConstantGetter -> printArrayClassConstantGetter(owner, codeAction) } + private fun printMockCall(owner: MockSequence, mockCall: MockCall): List = when (mockCall) { + is MockNewInstance -> printMockNewInstance(owner, mockCall) + is MockSetupMethod -> printMockSetupMethod(owner, mockCall) + } + protected val PrimaryValue.asConstant: String get() = when (val value = value) { null -> "null" @@ -869,6 +890,12 @@ open class ActionSequence2JavaPrinter( protected open fun printReflectionArrayWrite(owner: ActionSequence, call: ReflectionArrayWrite): List = unreachable { log.error("Reflection calls are not supported in AS 2 Java printer") } + protected open fun printMockNewInstance(owner: ActionSequence, call: MockNewInstance): List = + unreachable { log.error("Mock calls are not supported in AS 2 Java printer") } + + protected open fun printMockSetupMethod(owner: ActionSequence, call: MockSetupMethod): List = + unreachable { log.error("Mock calls are not supported in AS 2 Java printer") } + protected open fun printUnknownSequence(sequence: UnknownSequence): List { val actualType = sequence.target.type.asType return listOf( diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 0fb2babb7..d5726c390 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -61,6 +61,7 @@ class ExecutorAS2JavaPrinter( import("java.lang.reflect.Constructor") import("java.lang.reflect.Field") import("java.lang.reflect.Array") + import("org.mockito.Mockito") // TODO make configurable importStatic("${reflectionUtils.klass.pkg}.${reflectionUtils.klass.name}.*") with(klass) { @@ -96,9 +97,9 @@ class ExecutorAS2JavaPrinter( } } ?: unreachable { log.error("Unexpected call in arg") } - is MockSequence -> arg.mockitoCalls.firstNotNullOfOrNull { + is MockSequence -> arg.mockCalls.firstNotNullOfOrNull { when (it) { - is MockitoNewInstance -> it.klass.asType + is MockNewInstance -> it.klass.asType else -> null } } ?: unreachable { log.error { "Unexpected call in arg" } } @@ -402,4 +403,37 @@ class ExecutorAS2JavaPrinter( ?: reflectionUtils.setElement return listOf("${setElementMethod.name}(${owner.name}, ${call.index.stackName}, ${call.value.stackName})") } + + override fun printMockNewInstance(owner: ActionSequence, call: MockNewInstance): List { + val actualType = ASClass(ctx.types.objectType) + val kfgClass = call.klass + return listOf( + if (resolvedTypes[owner] != null) { + val rest = resolvedTypes[owner]!! + val type = actualType.merge(rest) + actualTypes[owner] = type + "${ + printVarDeclaration( + owner.name, + type + ) + } = Mockito.mock(Class.forName(\"${kfgClass.canonicalDesc}\"))" + } else { + actualTypes[owner] = actualType + "${ + printVarDeclaration( + owner.name, + actualType + ) + } = Mockito.mock(Class.forName(\"${kfgClass.canonicalDesc}\"))" + } + ) + } + + override fun printMockSetupMethod(owner: ActionSequence, call: MockSetupMethod): List { + if (call.returnValues.isEmpty()){ + return emptyList() + } + TODO("Mock. Unimplemented.") + } } From c198f9f9292f32a2791a95ffd482cfb06670b6e0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 2 Nov 2023 12:56:29 +0100 Subject: [PATCH 014/128] Mockito.mock printing --- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index d5726c390..983218e00 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -430,10 +430,36 @@ class ExecutorAS2JavaPrinter( ) } + private fun mockitoAnyFromType(type: Type): String = "Mockito." + when (type) { + is PrimitiveType -> when (type) { + is IntType -> "anyInt()" + is BoolType -> "anyBoolean()" + is ByteType -> "anyByte()" + is CharType -> "anyChar()" + is LongType -> "anyLong()" + is ShortType -> "anyShort()" + is DoubleType -> "anyDouble()" + is FloatType -> "anyFloat()" + } + + else -> "any()" + } + + override fun printMockSetupMethod(owner: ActionSequence, call: MockSetupMethod): List { - if (call.returnValues.isEmpty()){ + if (call.returnValues.isEmpty()) { return emptyList() } - TODO("Mock. Unimplemented.") + call.returnValues.forEach { it.printAsJava() } + + val returnValues = call.returnValues.joinToString(", ") { + it.forceCastIfNull(resolvedTypes[it]) + } + val method = call.method + val anys = call.method.argTypes.joinToString(", ") { type -> mockitoAnyFromType(type) } + + val type = call.method.klass.asType.asType + // TODO: Mock. Call method using ReflectionUtils, so it can mock package-private methods + return listOf("Mockito.when((${owner.cast(type)}).${method.name}($anys)).thenReturn(${returnValues})") } } From 47dc6975ab65716cf90cfed473d567615dc148a0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 2 Nov 2023 13:03:03 +0100 Subject: [PATCH 015/128] Fixes --- .../vorpal/research/kex/launcher/WorkerLauncher.kt | 8 ++------ .../research/kex/asm/analysis/util/extensions.kt | 13 +++++++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt b/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt index 257689693..0a1a31b3a 100644 --- a/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt +++ b/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt @@ -11,12 +11,7 @@ import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.random.easyrandom.EasyRandomDriver import org.vorpal.research.kex.serialization.KexSerializer import org.vorpal.research.kex.trace.symbolic.protocol.Worker2MasterSocketConnection -import org.vorpal.research.kex.util.KfgClassLoader -import org.vorpal.research.kex.util.compiledCodeDirectory -import org.vorpal.research.kex.util.getIntrinsics -import org.vorpal.research.kex.util.getJunit -import org.vorpal.research.kex.util.getPathSeparator -import org.vorpal.research.kex.util.getRuntime +import org.vorpal.research.kex.util.* import org.vorpal.research.kex.worker.ExecutorWorker import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.KfgConfig @@ -79,6 +74,7 @@ class WorkerLauncher(args: Array) { *classPaths.toTypedArray(), kexConfig.compiledCodeDirectory, getJunit()?.path + // TODO: Mock. Add mockito ) ) { kfgClass -> val instrumenter = SymbolicTraceInstrumenter(classManager) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 5b7e58f26..9be98985b 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -1,5 +1,6 @@ package org.vorpal.research.kex.asm.analysis.util +import com.jetbrains.rd.util.first import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.TimeoutCancellationException import org.vorpal.research.kex.ExecutionContext @@ -7,6 +8,8 @@ import org.vorpal.research.kex.asm.analysis.crash.precondition.ConstraintExcepti import org.vorpal.research.kex.asm.manager.MethodManager import org.vorpal.research.kex.asm.util.AccessModifier import org.vorpal.research.kex.descriptor.Descriptor +import org.vorpal.research.kex.descriptor.MockDescriptor +import org.vorpal.research.kex.descriptor.descriptor import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType @@ -100,6 +103,16 @@ suspend fun Method.checkAsync( ctx.accessLevel ) + // TODO: remove testing + when (val arg = withMocks.arguments[0]) { + is MockDescriptor -> { + arg.methodReturns.first().value.add(descriptor { const(1) }) + arg.methodReturns.first().value.add(descriptor { const(10) }) + } + + else -> {} + } + withMocks .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } From f26cb5cae5a8c00b33bf5491ab56d5fa008c31a7 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 9 Nov 2023 12:50:10 +0100 Subject: [PATCH 016/128] Add Mockito to deps, logback prints line and method --- core/pom.xml | 10 ++++++++++ .../kotlin/org/vorpal/research/kex/util/rt.kt | 14 +++++++++++++ core/src/test/resources/logback-test.xml | 6 +++--- kex-annotation-processor/pom.xml | 10 ++++++++++ kex-boolector/pom.xml | 10 ++++++++++ kex-executor/pom.xml | 10 ++++++++++ kex-executor/src/main/resources/logback.xml | 4 ++-- kex-ksmt/pom.xml | 10 ++++++++++ kex-runner/pom.xml | 10 ++++++++++ kex-runner/src/main/resources/logback.xml | 4 ++-- kex-test/pom.xml | 10 ++++++++++ kex-z3/pom.xml | 10 ++++++++++ pom.xml | 20 +++++++++++++++++++ 13 files changed, 121 insertions(+), 7 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index d638b01d5..2a4e5927d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -98,6 +98,16 @@ easy-random-core ${easy-random.version} + + net.bytebuddy + byte-buddy-agent + 1.14.5 + + + org.mockito + mockito-core + 4.11.0 + diff --git a/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index b00d341b6..fc000d69b 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -67,6 +67,20 @@ fun getJunit(): Container? { return JarContainer(libPath.resolve("junit-$junitVersion.jar").toAbsolutePath(), Package.defaultPackage) } +fun getMockito(): Container? { + val libPath = kexConfig.libPath ?: return null + val mockitoVersion = "4.11.0" // TODO: Mock. Make configurable + return JarContainer(libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath(), Package.defaultPackage) +// return JarContainer(libPath.resolve("mockito-subclass-$mockitoVersion.jar").toAbsolutePath(), Package.defaultPackage) +} + +fun getMockitoInline(): Container? { + val libPath = kexConfig.libPath ?: return null + val mockitoVersion = "4.11.0" // TODO: Mock. Make configurable + return JarContainer(libPath.resolve("mockito-inline-$mockitoVersion.jar").toAbsolutePath(), Package.defaultPackage) +} + + fun getKexRuntime(): Container? { if (!kexConfig.getBooleanValue("kex", "useKexRuntime", true)) return null val libPath = kexConfig.libPath ?: return null diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index 1e17139b8..5bcfb75d6 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -3,7 +3,7 @@ - %-5p [%t] - %m%n + %-5p [%t][%method:%line] - %m%n @@ -15,7 +15,7 @@ DENY - %-5p [%t] - %m%n + %-5p [%t][%method:%line] - %m%n @@ -29,7 +29,7 @@ ${kex-run-id} true - %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c] - %m%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c][%method:%line] - %m%n diff --git a/kex-annotation-processor/pom.xml b/kex-annotation-processor/pom.xml index b558bf05c..0e32786c1 100644 --- a/kex-annotation-processor/pom.xml +++ b/kex-annotation-processor/pom.xml @@ -35,6 +35,16 @@ kotlinx-serialization-json-jvm ${serialization.version} + + org.mockito + mockito-core + 4.11.0 + + + net.bytebuddy + byte-buddy-agent + 1.14.5 + diff --git a/kex-boolector/pom.xml b/kex-boolector/pom.xml index 12ca68d6d..4c56dc533 100644 --- a/kex-boolector/pom.xml +++ b/kex-boolector/pom.xml @@ -46,6 +46,16 @@ boolector-java ${boolector.version} + + org.mockito + mockito-core + 4.11.0 + + + net.bytebuddy + byte-buddy-agent + 1.14.5 + diff --git a/kex-executor/pom.xml b/kex-executor/pom.xml index c737a65df..5d6371c9e 100644 --- a/kex-executor/pom.xml +++ b/kex-executor/pom.xml @@ -52,6 +52,16 @@ kotlinx-coroutines-core ${coroutines.version} + + org.mockito + mockito-core + 4.11.0 + + + net.bytebuddy + byte-buddy-agent + 1.14.5 + diff --git a/kex-executor/src/main/resources/logback.xml b/kex-executor/src/main/resources/logback.xml index 24e134037..31c9f2345 100644 --- a/kex-executor/src/main/resources/logback.xml +++ b/kex-executor/src/main/resources/logback.xml @@ -3,7 +3,7 @@ - [%-5p] - %m%n + [%-5p][%method:%line] - %m%n @@ -16,7 +16,7 @@ ${kex-run-id} true - %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c] - %m%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c][%method:%line] - %m%n diff --git a/kex-ksmt/pom.xml b/kex-ksmt/pom.xml index f5d8154b9..a7dafe393 100644 --- a/kex-ksmt/pom.xml +++ b/kex-ksmt/pom.xml @@ -82,6 +82,16 @@ ksmt-runner ${ksmt.version} + + org.mockito + mockito-core + 4.11.0 + + + net.bytebuddy + byte-buddy-agent + 1.14.5 + diff --git a/kex-runner/pom.xml b/kex-runner/pom.xml index a1b0007b7..3f120ba58 100644 --- a/kex-runner/pom.xml +++ b/kex-runner/pom.xml @@ -110,6 +110,16 @@ commons-text ${commons-text.version} + + org.mockito + mockito-core + 4.11.0 + + + net.bytebuddy + byte-buddy-agent + 1.14.5 + diff --git a/kex-runner/src/main/resources/logback.xml b/kex-runner/src/main/resources/logback.xml index d81d55719..9bcc14bc3 100644 --- a/kex-runner/src/main/resources/logback.xml +++ b/kex-runner/src/main/resources/logback.xml @@ -4,7 +4,7 @@ - [%-5p] - %m%n + [%-5p][%method:%line] - %m%n @@ -17,7 +17,7 @@ ${kex-run-id} false - %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c] - %m%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c][%method:%line] - %m%n diff --git a/kex-test/pom.xml b/kex-test/pom.xml index 0328b5547..1acb8f19f 100644 --- a/kex-test/pom.xml +++ b/kex-test/pom.xml @@ -37,6 +37,16 @@ kex-intrinsics ${kex-intrinsics.version} + + org.mockito + mockito-core + 4.11.0 + + + net.bytebuddy + byte-buddy-agent + 1.14.5 + diff --git a/kex-z3/pom.xml b/kex-z3/pom.xml index ecd448b0b..85361bb9c 100644 --- a/kex-z3/pom.xml +++ b/kex-z3/pom.xml @@ -46,6 +46,16 @@ z3 ${z3.version} + + org.mockito + mockito-core + 4.11.0 + + + net.bytebuddy + byte-buddy-agent + 1.14.5 + diff --git a/pom.xml b/pom.xml index 7630820ce..bd8ff6cc2 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,26 @@ kotlinx-collections-immutable-jvm ${collections.version} + + org.mockito + mockito-core + 4.11.0 + + + net.bytebuddy + byte-buddy-agent + 1.14.5 + + + net.bytebuddy + byte-buddy + 1.14.5 + + + com.sun.xml.bind + jaxb-impl + 3.0.2 + From e317592c38b0f68678c57ed5329efdfe220094fc Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 10 Nov 2023 11:46:11 +0100 Subject: [PATCH 017/128] Fixed mockito issues in workers. Function filtering clauses in extensions.kt WIP --- .../research/kex/util/KfgClassLoader.kt | 5 ++- .../kotlin/org/vorpal/research/kex/util/rt.kt | 3 +- .../research/kex/launcher/WorkerLauncher.kt | 3 +- .../research/kex/worker/TestExecutor.kt | 30 ++++++++++++++---- .../kex/asm/analysis/util/extensions.kt | 31 +++++++++++-------- .../runner/SymbolicExternalTracingRunner.kt | 2 +- 6 files changed, 49 insertions(+), 25 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 7bc407a13..4026a9f67 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -32,12 +32,15 @@ class KfgClassLoader( "class org.vorpal.research.kex.test.concolic.StringConcolicTests", "class org.vorpal.research.kex.test.concolic.TestEnum", "class org.vorpal.research.kex.test.concolic.UnimplementedInterfaceTests", - "class org.vorpal.research.kex.test.concolic.Unimplemented" +// "class org.vorpal.research.kex.test.concolic.Unimplemented", "class org.vorpal.research.kex.test.debug.ObjectGenerationTests", ).mapTo(mutableSetOf()) { KfgTargetFilter.parse(it) } private val EXCLUDES = setOf( "package java.*", "package org.vorpal.research.kex.*", + "package org.mockito.*", + "package net.bytebuddy.*", + "package org.objenesis.*" ).mapTo(mutableSetOf()) { KfgTargetFilter.parse(it) } } diff --git a/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index fc000d69b..c5632c32a 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -70,8 +70,7 @@ fun getJunit(): Container? { fun getMockito(): Container? { val libPath = kexConfig.libPath ?: return null val mockitoVersion = "4.11.0" // TODO: Mock. Make configurable - return JarContainer(libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath(), Package.defaultPackage) -// return JarContainer(libPath.resolve("mockito-subclass-$mockitoVersion.jar").toAbsolutePath(), Package.defaultPackage) + return JarContainer(libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath(), Package("org.mockito")) } fun getMockitoInline(): Container? { diff --git a/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt b/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt index 5c5572cc3..7cf54fd68 100644 --- a/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt +++ b/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt @@ -75,8 +75,7 @@ class WorkerLauncher(args: Array) { kexConfig.compiledCodeDirectory, getJunit()?.path, getIntrinsics()?.path, - getJunit()?.path - // TODO: Mock. Add mockito + getMockito()?.path, ) ) { kfgClass -> val instrumenter = SymbolicTraceInstrumenter(classManager) diff --git a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt index 853368b3c..11fd0c9c6 100644 --- a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt +++ b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt @@ -3,19 +3,27 @@ package org.vorpal.research.kex.worker import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.descriptor.convertToDescriptor import org.vorpal.research.kex.trace.symbolic.TraceCollectorProxy -import org.vorpal.research.kex.trace.symbolic.protocol.ExceptionResult -import org.vorpal.research.kex.trace.symbolic.protocol.ExecutionResult -import org.vorpal.research.kex.trace.symbolic.protocol.SetupFailedResult -import org.vorpal.research.kex.trace.symbolic.protocol.SuccessResult -import org.vorpal.research.kex.trace.symbolic.protocol.TestExecutionRequest +import org.vorpal.research.kex.trace.symbolic.protocol.* +import org.vorpal.research.kex.util.asArray import org.vorpal.research.kfg.ir.value.NameMapperContext +import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log class TestExecutor( val ctx: ExecutionContext ) { + companion object { + var cnt: Int = 0; + } + fun executeTest(request: TestExecutionRequest): ExecutionResult { val javaClass = ctx.loader.loadClass(request.klass) + if (cnt == 0) { + firstJunitRun(javaClass) + cnt++; + } else { + log.debug { "No JUnit run" } + } val instance = javaClass.getConstructor().newInstance() log.debug("Loaded a test class and created an instance") @@ -38,7 +46,7 @@ class TestExecutor( test.invoke(instance) } catch (e: Throwable) { exception = e - log.error("Execution failed with an exception $e") + log.error("Execution failed with an exception $e") // no stacktrace??? } TraceCollectorProxy.disableCollector() log.debug("Collected state: {}", collector.symbolicState) @@ -48,4 +56,14 @@ class TestExecutor( } } + private fun firstJunitRun(testClass: Class<*>?): Unit = try { + val jcClass = ctx.loader.loadClass("org.junit.runner.JUnitCore") + val jc = jcClass.getConstructor().newInstance() + log.debug { "Created JUnitCoreInstance" } + jcClass.getMethod("run", Class::class.java.asArray()) + .invoke(jc, arrayOf(testClass)) + log.debug { "JUnit successfully executed" } + } catch (e: Throwable) { + log.error("JUnit execution failed with an exception", e) + } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index a376ee633..3cc5457ee 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -1,6 +1,5 @@ package org.vorpal.research.kex.asm.analysis.util -import com.jetbrains.rd.util.first import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.TimeoutCancellationException import org.vorpal.research.kex.ExecutionContext @@ -8,8 +7,7 @@ import org.vorpal.research.kex.asm.analysis.crash.precondition.ConstraintExcepti import org.vorpal.research.kex.asm.manager.MethodManager import org.vorpal.research.kex.asm.util.AccessModifier import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.MockDescriptor -import org.vorpal.research.kex.descriptor.descriptor +import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType @@ -20,9 +18,14 @@ import org.vorpal.research.kex.smt.Result import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery import org.vorpal.research.kex.state.predicate.CallPredicate +import org.vorpal.research.kex.state.predicate.EqualityPredicate +import org.vorpal.research.kex.state.term.InstanceOfTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* +import org.vorpal.research.kex.trace.symbolic.Clause +import org.vorpal.research.kex.trace.symbolic.PathClause +import org.vorpal.research.kex.trace.symbolic.PathClauseType import org.vorpal.research.kex.trace.symbolic.SymbolicState import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.logging.debug @@ -67,6 +70,16 @@ fun methodCalls( .toList() } +fun isIsMockClause(clause: Clause): Boolean { + if (clause !is PathClause || clause.type != PathClauseType.OVERLOAD_CHECK) { + return false; + } + val mbName = + (((clause.predicate as? EqualityPredicate)?.lhv as? InstanceOfTerm)?.checkedType as? KexClass)?.klass + clause.predicate + return mbName != null && mbName.contains("\$MockitoMock\$") +} + suspend fun Method.checkAsync( ctx: ExecutionContext, state: SymbolicState, @@ -80,6 +93,8 @@ suspend fun Method.checkAsync( .filterValues { it.isJavaRt } .mapValues { it.value.rtMapped } .toTypeMap() +// val query = PersistentClauseList(state.clauses.filterNot { isIsMockClause(it) }.toPersistentList()).asState() +// val clauses = PersistentClauseList(state.clauses.filterNot { isIsMockClause(it) }.toPersistentList()).asState() val result = checker.prepareAndCheck(this, clauses + query, concreteTypeInfo, enableInlining) // log.error("Checking clause type ${state.path.last().type}, acquired $result") if (result !is Result.SatResult) { @@ -95,7 +110,6 @@ suspend fun Method.checkAsync( checker.state ) val methodCalls = methodCalls(state, termToDescriptor) -// println(methodCalls) val withMocks = initialDescriptors.generateMocks( methodCalls, @@ -104,15 +118,6 @@ suspend fun Method.checkAsync( ctx.accessLevel ) - // TODO: remove testing - when (val arg = withMocks.arguments[0]) { - is MockDescriptor -> { - arg.methodReturns.first().value.add(descriptor { const(1) }) - arg.methodReturns.first().value.add(descriptor { const(10) }) - } - - else -> {} - } withMocks .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt index 89970b4d2..f341d05cf 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt @@ -103,7 +103,7 @@ class SymbolicExternalTracingRunner(val ctx: ExecutionContext) { is ExecutionCompletedResult -> log.debug("Execution result: {}", result.trace) else -> log.debug("Execution result: {}", result) } - log.debug("Test {} executed with result {}", klass, result) +// log.debug("Test {} executed with result {}", klass, result) return result ?: ExecutionTimedOutResult("Connection timeout") } } From c1a5f7667193a0724a7c25031e5b0d08da2d7286 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 13 Nov 2023 13:32:34 +0100 Subject: [PATCH 018/128] Remove MockitoMock part from "is ...$MockitoMock$" predicates. --- .../trace/symbolic/SymbolicTraceBuilder.kt | 91 +++++-------------- 1 file changed, 22 insertions(+), 69 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index b8f464dbf..63e5afb7a 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -6,77 +6,18 @@ import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.manager.MethodManager import org.vorpal.research.kex.asm.state.asTermExpr import org.vorpal.research.kex.asm.transform.SymbolicTraceInstrumenter -import org.vorpal.research.kex.descriptor.ConstantDescriptor -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.Object2DescriptorConverter -import org.vorpal.research.kex.descriptor.ObjectDescriptor -import org.vorpal.research.kex.descriptor.descriptor -import org.vorpal.research.kex.ktype.KexBool -import org.vorpal.research.kex.ktype.KexByte -import org.vorpal.research.kex.ktype.KexChar -import org.vorpal.research.kex.ktype.KexClass -import org.vorpal.research.kex.ktype.KexDouble -import org.vorpal.research.kex.ktype.KexFloat -import org.vorpal.research.kex.ktype.KexInt -import org.vorpal.research.kex.ktype.KexInteger -import org.vorpal.research.kex.ktype.KexLong -import org.vorpal.research.kex.ktype.KexReal -import org.vorpal.research.kex.ktype.KexShort -import org.vorpal.research.kex.ktype.KexType -import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.descriptor.* +import org.vorpal.research.kex.ktype.* import org.vorpal.research.kex.parameters.Parameters import org.vorpal.research.kex.state.predicate.Predicate import org.vorpal.research.kex.state.predicate.path import org.vorpal.research.kex.state.predicate.state -import org.vorpal.research.kex.state.term.ConstClassTerm -import org.vorpal.research.kex.state.term.ConstStringTerm -import org.vorpal.research.kex.state.term.NullTerm -import org.vorpal.research.kex.state.term.StaticClassRefTerm -import org.vorpal.research.kex.state.term.Term -import org.vorpal.research.kex.state.term.term +import org.vorpal.research.kex.state.term.* import org.vorpal.research.kex.state.transformer.TermRenamer -import org.vorpal.research.kex.util.cmp -import org.vorpal.research.kex.util.isOuterThis -import org.vorpal.research.kex.util.isSubtypeOfCached -import org.vorpal.research.kex.util.next -import org.vorpal.research.kex.util.parseValue -import org.vorpal.research.kex.util.parseValueOrNull -import org.vorpal.research.kfg.ir.BasicBlock -import org.vorpal.research.kfg.ir.Class -import org.vorpal.research.kfg.ir.ConcreteClass -import org.vorpal.research.kfg.ir.Method -import org.vorpal.research.kfg.ir.MethodDescriptor -import org.vorpal.research.kfg.ir.value.Argument -import org.vorpal.research.kfg.ir.value.Constant -import org.vorpal.research.kfg.ir.value.NameMapperContext -import org.vorpal.research.kfg.ir.value.NullConstant -import org.vorpal.research.kfg.ir.value.ThisRef -import org.vorpal.research.kfg.ir.value.Value -import org.vorpal.research.kfg.ir.value.instruction.ArrayLoadInst -import org.vorpal.research.kfg.ir.value.instruction.ArrayStoreInst -import org.vorpal.research.kfg.ir.value.instruction.BinaryInst -import org.vorpal.research.kfg.ir.value.instruction.BranchInst -import org.vorpal.research.kfg.ir.value.instruction.CallInst -import org.vorpal.research.kfg.ir.value.instruction.CastInst -import org.vorpal.research.kfg.ir.value.instruction.CatchInst -import org.vorpal.research.kfg.ir.value.instruction.CmpInst -import org.vorpal.research.kfg.ir.value.instruction.EnterMonitorInst -import org.vorpal.research.kfg.ir.value.instruction.ExitMonitorInst -import org.vorpal.research.kfg.ir.value.instruction.FieldLoadInst -import org.vorpal.research.kfg.ir.value.instruction.FieldStoreInst -import org.vorpal.research.kfg.ir.value.instruction.Handle -import org.vorpal.research.kfg.ir.value.instruction.InstanceOfInst -import org.vorpal.research.kfg.ir.value.instruction.Instruction -import org.vorpal.research.kfg.ir.value.instruction.InvokeDynamicInst -import org.vorpal.research.kfg.ir.value.instruction.JumpInst -import org.vorpal.research.kfg.ir.value.instruction.NewArrayInst -import org.vorpal.research.kfg.ir.value.instruction.NewInst -import org.vorpal.research.kfg.ir.value.instruction.PhiInst -import org.vorpal.research.kfg.ir.value.instruction.ReturnInst -import org.vorpal.research.kfg.ir.value.instruction.SwitchInst -import org.vorpal.research.kfg.ir.value.instruction.TableSwitchInst -import org.vorpal.research.kfg.ir.value.instruction.ThrowInst -import org.vorpal.research.kfg.ir.value.instruction.UnaryInst +import org.vorpal.research.kex.util.* +import org.vorpal.research.kfg.ir.* +import org.vorpal.research.kfg.ir.value.* +import org.vorpal.research.kfg.ir.value.instruction.* import org.vorpal.research.kfg.type.SystemTypeNames import org.vorpal.research.kfg.type.Type import org.vorpal.research.kfg.type.parseDescOrNull @@ -85,6 +26,7 @@ import org.vorpal.research.kthelper.KtException import org.vorpal.research.kthelper.assert.ktassert import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.collection.stackOf +import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.toInt import org.vorpal.research.kthelper.`try` @@ -1270,7 +1212,9 @@ class SymbolicTraceBuilder( val kfgValue = parseValue(value) val termValue = mkValue(kfgValue) val descriptorValue = concreteValue.getAsDescriptor() - val kfgType = descriptorValue.type.getKfgType(ctx.types) + val realType = removeMockitoMockSuffix(descriptorValue.type.name) + log.debug { "decriptorValue type: ${descriptorValue.type.name}, realType: $realType" } + val kfgType = parseStringToType(cm.type, realType) if (termValue in typeChecked) { val checkedType = typeChecked.getValue(termValue) if (checkedType.isSubtypeOfCached(kfgType)) return@safeCall @@ -1278,7 +1222,7 @@ class SymbolicTraceBuilder( typeChecked[termValue] = kfgType val predicate = path { - (termValue `is` descriptorValue.type) equality true + (termValue `is` kfgType.kexType) equality true } processPath(PathClauseType.OVERLOAD_CHECK, instruction, predicate) @@ -1293,7 +1237,9 @@ class SymbolicTraceBuilder( val kfgValue = parseValue(value) val termValue = mkValue(kfgValue) - val expectedKfgType = parseStringToType(cm.type, type) + val realType = removeMockitoMockSuffix(type) + log.debug { "type: $type\nrealType: $realType" } + val expectedKfgType = parseStringToType(cm.type, realType) val comparisonResult = when (concreteValue) { null -> false else -> { @@ -1317,6 +1263,13 @@ class SymbolicTraceBuilder( stateBuilder += PathClause(PathClauseType.TYPE_CHECK, instruction, predicate) } + private fun removeMockitoMockSuffix(type: String): String = if (!type.contains("\$MockitoMock")) { + type + } else { + val suffixIndex = type.indexOf("\$MockitoMock") + type.removeRange(suffixIndex, type.length) + } + override fun addArrayIndexConstraints( inst: String, array: String, From 740e52781aa9ab196ee1adac6e0a022a65a6c1e4 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 13 Nov 2023 14:48:46 +0100 Subject: [PATCH 019/128] It's just works! --- .../research/kex/parameters/Parameters.kt | 36 ++++++++++++------- .../research/kex/worker/TestExecutor.kt | 13 ++++--- .../kex/asm/analysis/util/extensions.kt | 31 +++++++++++----- 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 992bc3e61..6ea699ae5 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -100,7 +100,7 @@ fun Parameters.filterIgnoredStatic(): Parameters { private fun Collection.replaceUninstantiableWithMocks( types: TypeFactory, - termToDescriptor: MutableMap + descriptorToMock: MutableMap ): Collection { return map { descriptor -> val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass @@ -112,35 +112,47 @@ private fun Collection.replaceUninstantiableWithMocks( } else { MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods) } - termToDescriptor[descriptor.term] = mock + descriptorToMock[descriptor] = mock mock } } } -private fun Parameters.generateInitialMocks( - types: TypeFactory, termToDescriptor: MutableMap -): Parameters { - val mockedArguments = arguments.replaceUninstantiableWithMocks(types, termToDescriptor).toList() - val mockedStatics = statics.replaceUninstantiableWithMocks(types, termToDescriptor).toSet() - val mockedOthers = others.replaceUninstantiableWithMocks(types, termToDescriptor).toSet() +fun Parameters.generateInitialMocks( + types: TypeFactory +): Pair, Map> { + val descriptorToMock = mutableMapOf() + val mockedArguments = arguments.replaceUninstantiableWithMocks(types, descriptorToMock).toList() + val mockedStatics = statics.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() + val mockedOthers = others.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() - return Parameters(instance, mockedArguments, mockedStatics, mockedOthers) + return Parameters(instance, mockedArguments, mockedStatics, mockedOthers) to descriptorToMock } + +/* fun Parameters.generateMocks( methodCalls: List>, termToDescriptor: MutableMap, cm: ClassManager, accessLevel: AccessModifier ): Parameters { - val withMocks = this.generateInitialMocks(cm.type, termToDescriptor) + val withMocks = this.generateInitialMocks(cm.type) + setupMocks(methodCalls, termToDescriptor) + return withMocks +} +*/ + +fun setupMocks( + methodCalls: List>, + termToDescriptor: Map, + descriptorToMock: Map +) { for ((callPredicate, value) in methodCalls) { val call = callPredicate.call as CallTerm - val mock = termToDescriptor[call.owner] + val mock = termToDescriptor[call.owner]?.let { descriptorToMock[it] ?: it } if (mock is MockDescriptor) { mock.addReturnValue(call.method, value) } } - return withMocks } \ No newline at end of file diff --git a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt index 11fd0c9c6..6a6ff742a 100644 --- a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt +++ b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt @@ -12,18 +12,21 @@ import org.vorpal.research.kthelper.logging.log class TestExecutor( val ctx: ExecutionContext ) { + // TODO: Mock. move property to class companion object { - var cnt: Int = 0; + var isFirstRun: Boolean = true } fun executeTest(request: TestExecutionRequest): ExecutionResult { val javaClass = ctx.loader.loadClass(request.klass) - if (cnt == 0) { - firstJunitRun(javaClass) - cnt++; + if (isFirstRun) { + log.debug { "First run with JUnit" } + executeTestFromJUnit(javaClass) + isFirstRun = false } else { log.debug { "No JUnit run" } } + val instance = javaClass.getConstructor().newInstance() log.debug("Loaded a test class and created an instance") @@ -56,7 +59,7 @@ class TestExecutor( } } - private fun firstJunitRun(testClass: Class<*>?): Unit = try { + private fun executeTestFromJUnit(testClass: Class<*>?): Unit = try { val jcClass = ctx.loader.loadClass("org.junit.runner.JUnitCore") val jc = jcClass.getConstructor().newInstance() log.debug { "Created JUnitCoreInstance" } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 3cc5457ee..0044080bd 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -19,6 +19,7 @@ import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.predicate.EqualityPredicate +import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.InstanceOfTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term @@ -50,9 +51,14 @@ suspend fun Method.analyzeOrTimeout( } } +fun CallTerm.replaceOwner(owner: Term): CallTerm { + return CallTerm(type, owner, method, arguments) +} + fun methodCalls( state: SymbolicState, - termToDescriptor: Map + termToDescriptor: Map, + descriptorToMock: Map ): List> { return state.clauses .asSequence() @@ -67,12 +73,13 @@ fun methodCalls( } .filter { (_, value) -> value != null } .map { (call, value) -> call to value!! } + .map { (call, value) -> call to descriptorToMock.getOrElse(value) { value } } .toList() } fun isIsMockClause(clause: Clause): Boolean { if (clause !is PathClause || clause.type != PathClauseType.OVERLOAD_CHECK) { - return false; + return false } val mbName = (((clause.predicate as? EqualityPredicate)?.lhv as? InstanceOfTerm)?.checkedType as? KexClass)?.klass @@ -80,6 +87,14 @@ fun isIsMockClause(clause: Clause): Boolean { return mbName != null && mbName.contains("\$MockitoMock\$") } +fun fixIsMockitoMockInstance(clause: Clause): Clause { + if (clause !is PathClause || clause.type != PathClauseType.OVERLOAD_CHECK) { + return clause + } + val name = (((clause.predicate as? EqualityPredicate)?.lhv as? InstanceOfTerm)?.checkedType as? KexClass)?.klass + TODO("Mock. unimplemented") +} + suspend fun Method.checkAsync( ctx: ExecutionContext, state: SymbolicState, @@ -109,14 +124,12 @@ suspend fun Method.checkAsync( result.model, checker.state ) - val methodCalls = methodCalls(state, termToDescriptor) - val withMocks = initialDescriptors.generateMocks( - methodCalls, - termToDescriptor.toMutableMap(), - ctx.cm, - ctx.accessLevel - ) + val (withMocks, descriptorToMock) = initialDescriptors.generateInitialMocks(ctx.types) + val methodCalls = methodCalls(state, termToDescriptor, descriptorToMock) + setupMocks(methodCalls, termToDescriptor, descriptorToMock) + + withMocks From f1da6f3c53f506257ab20f7c9ed86f8323e279e0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 13 Nov 2023 14:57:08 +0100 Subject: [PATCH 020/128] Fixes --- .../research/kex/parameters/Parameters.kt | 8 +++--- .../research/kex/worker/TestExecutor.kt | 5 +--- .../kex/asm/analysis/util/extensions.kt | 26 ------------------- 3 files changed, 5 insertions(+), 34 deletions(-) diff --git a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 6ea699ae5..5096b1442 100644 --- a/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -100,7 +100,7 @@ fun Parameters.filterIgnoredStatic(): Parameters { private fun Collection.replaceUninstantiableWithMocks( types: TypeFactory, - descriptorToMock: MutableMap + descriptorToMock: MutableMap ): Collection { return map { descriptor -> val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass @@ -120,8 +120,8 @@ private fun Collection.replaceUninstantiableWithMocks( fun Parameters.generateInitialMocks( types: TypeFactory -): Pair, Map> { - val descriptorToMock = mutableMapOf() +): Pair, Map> { + val descriptorToMock = mutableMapOf() val mockedArguments = arguments.replaceUninstantiableWithMocks(types, descriptorToMock).toList() val mockedStatics = statics.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() val mockedOthers = others.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() @@ -146,7 +146,7 @@ fun Parameters.generateMocks( fun setupMocks( methodCalls: List>, termToDescriptor: Map, - descriptorToMock: Map + descriptorToMock: Map ) { for ((callPredicate, value) in methodCalls) { val call = callPredicate.call as CallTerm diff --git a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt index 6a6ff742a..0dc634253 100644 --- a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt +++ b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt @@ -12,10 +12,7 @@ import org.vorpal.research.kthelper.logging.log class TestExecutor( val ctx: ExecutionContext ) { - // TODO: Mock. move property to class - companion object { - var isFirstRun: Boolean = true - } + private var isFirstRun: Boolean = true fun executeTest(request: TestExecutionRequest): ExecutionResult { val javaClass = ctx.loader.loadClass(request.klass) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 0044080bd..fd23afbed 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -7,7 +7,6 @@ import org.vorpal.research.kex.asm.analysis.crash.precondition.ConstraintExcepti import org.vorpal.research.kex.asm.manager.MethodManager import org.vorpal.research.kex.asm.util.AccessModifier import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType @@ -18,15 +17,10 @@ import org.vorpal.research.kex.smt.Result import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery import org.vorpal.research.kex.state.predicate.CallPredicate -import org.vorpal.research.kex.state.predicate.EqualityPredicate import org.vorpal.research.kex.state.term.CallTerm -import org.vorpal.research.kex.state.term.InstanceOfTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* -import org.vorpal.research.kex.trace.symbolic.Clause -import org.vorpal.research.kex.trace.symbolic.PathClause -import org.vorpal.research.kex.trace.symbolic.PathClauseType import org.vorpal.research.kex.trace.symbolic.SymbolicState import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.logging.debug @@ -77,24 +71,6 @@ fun methodCalls( .toList() } -fun isIsMockClause(clause: Clause): Boolean { - if (clause !is PathClause || clause.type != PathClauseType.OVERLOAD_CHECK) { - return false - } - val mbName = - (((clause.predicate as? EqualityPredicate)?.lhv as? InstanceOfTerm)?.checkedType as? KexClass)?.klass - clause.predicate - return mbName != null && mbName.contains("\$MockitoMock\$") -} - -fun fixIsMockitoMockInstance(clause: Clause): Clause { - if (clause !is PathClause || clause.type != PathClauseType.OVERLOAD_CHECK) { - return clause - } - val name = (((clause.predicate as? EqualityPredicate)?.lhv as? InstanceOfTerm)?.checkedType as? KexClass)?.klass - TODO("Mock. unimplemented") -} - suspend fun Method.checkAsync( ctx: ExecutionContext, state: SymbolicState, @@ -108,8 +84,6 @@ suspend fun Method.checkAsync( .filterValues { it.isJavaRt } .mapValues { it.value.rtMapped } .toTypeMap() -// val query = PersistentClauseList(state.clauses.filterNot { isIsMockClause(it) }.toPersistentList()).asState() -// val clauses = PersistentClauseList(state.clauses.filterNot { isIsMockClause(it) }.toPersistentList()).asState() val result = checker.prepareAndCheck(this, clauses + query, concreteTypeInfo, enableInlining) // log.error("Checking clause type ${state.path.last().type}, acquired $result") if (result !is Result.SatResult) { From aa01a1b505d21a7a28998f3ee2bd028565d4831d Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 13 Nov 2023 17:11:43 +0100 Subject: [PATCH 021/128] SMTConverter fix --- kex-annotation-processor/src/main/resources/SMTConverter.vm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kex-annotation-processor/src/main/resources/SMTConverter.vm b/kex-annotation-processor/src/main/resources/SMTConverter.vm index bf29acb48..6ed068760 100644 --- a/kex-annotation-processor/src/main/resources/SMTConverter.vm +++ b/kex-annotation-processor/src/main/resources/SMTConverter.vm @@ -627,7 +627,7 @@ class ${solver}Converter( } fun convert(call: CallTerm, ef: Factory_, ctx: Context_): Dynamic_ { - val expr = ef.getVarByTypeAndName(call.type, call.name) + val expr = ef.getVarByTypeAndName(call.type, call.name, fresh = true) return call.withAxioms(expr, ef, ctx) } From 8f9d0284b630169d1b679bdc9a4f50dda1acfdf6 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 13 Nov 2023 21:16:58 +0100 Subject: [PATCH 022/128] Fixes: generating MockDescriptors, printing mocks. --- .../org/vorpal/research/kex/parameters/Parameters.kt | 1 + .../reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt | 8 +++++--- .../research/kex/state/transformer/DescriptorGenerator.kt | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 5096b1442..546d2e8ef 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -103,6 +103,7 @@ private fun Collection.replaceUninstantiableWithMocks( descriptorToMock: MutableMap ): Collection { return map { descriptor -> + if (descriptorToMock[descriptor] != null) return@map descriptorToMock[descriptor]!! val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass if (klass == null || instantiationManager.isInstantiable(klass) || descriptor.type.isKexRt) { descriptor diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 983218e00..9c0b7c19b 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -452,9 +452,11 @@ class ExecutorAS2JavaPrinter( } call.returnValues.forEach { it.printAsJava() } - val returnValues = call.returnValues.joinToString(", ") { - it.forceCastIfNull(resolvedTypes[it]) - } + val returnValues = call.returnValues + .map { value -> value.cast(call.method.returnType.asType) } + .joinToString(", ") { + it + } val method = call.method val anys = call.method.argTypes.joinToString(", ") { type -> mockitoAnyFromType(type) } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index 4c49ac630..08bc44671 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -144,6 +144,7 @@ fun generateInitialDescriptors( arg ?: descriptor { default(method.argTypes[index].kexType) } }, generator.staticFields, + generator.allValues ) to generator.memory } @@ -162,6 +163,5 @@ fun generateInitialDescriptorsAndAA( arg ?: descriptor { default(method.argTypes[index].kexType) } }, generator.staticFields, -// generator.allValues ) to SMTModelALiasAnalysis(generator) } From 1723a972742e81207fdd6dac65af4144e7d95484 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 13 Nov 2023 23:08:57 +0100 Subject: [PATCH 023/128] Fixed cycle links case in Mocks --- .../research/kex/util/KfgClassLoader.kt | 4 +- .../javagen/ActionSequence2JavaPrinter.kt | 15 ++- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 125 +++++++++++++----- ...licLongTest.kt => MockConcolicLongTest.kt} | 6 +- .../research/kex/test/concolic/MockTests.java | 112 ++++++++++++++++ .../concolic/UnimplementedInterfaceTests.java | 20 --- 6 files changed, 219 insertions(+), 63 deletions(-) rename kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/{UnimplementedInterfaceConcolicLongTest.kt => MockConcolicLongTest.kt} (71%) create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java delete mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 4026a9f67..03a83d579 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -22,6 +22,7 @@ class KfgClassLoader( companion object { private val INCLUDES = setOf( + // TODO: Mock. Config file! "class org.vorpal.research.kex.test.concolic.kaf.Lesson2", "class org.vorpal.research.kex.test.concolic.kaf.Lesson6", "class org.vorpal.research.kex.test.concolic.EnumConcolicTests", @@ -31,8 +32,7 @@ class KfgClassLoader( "class org.vorpal.research.kex.test.concolic.SetConcolicTests", "class org.vorpal.research.kex.test.concolic.StringConcolicTests", "class org.vorpal.research.kex.test.concolic.TestEnum", - "class org.vorpal.research.kex.test.concolic.UnimplementedInterfaceTests", -// "class org.vorpal.research.kex.test.concolic.Unimplemented", + "class org.vorpal.research.kex.test.concolic.MockTests", "class org.vorpal.research.kex.test.debug.ObjectGenerationTests", ).mapTo(mutableSetOf()) { KfgTargetFilter.parse(it) } private val EXCLUDES = setOf( diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt index cfd26560d..61a9bc38d 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt @@ -370,10 +370,17 @@ open class ActionSequence2JavaPrinter( protected open fun printReflectionList(reflectionList: ReflectionList): List = reflectionList.flatMap { printReflectionCall(reflectionList, it) } - protected open fun printMockSequence(mockSequence: MockSequence): List = mutableListOf().apply { - addAll(mockSequence.mockCalls.flatMap { printMockCall(mockSequence, it) }) - addAll(mockSequence.reflectionCalls.flatMap { printReflectionCall(mockSequence, it)}) - } + protected open fun printMockSequence(mockSequence: MockSequence): List = + unreachable { log.error("Mock calls are not supported in AS 2 Java printer") } + + /* + = mutableListOf().apply { + val mockCalls = mockSequence.mockCalls + addAll(printMockNewInstance(mockSequence, mockCalls[0] as MockNewInstance)) + addAll(mockCalls.subList(1, mockCalls.size).flatMap { printMockCall(mockSequence, it) }) + addAll(mockSequence.reflectionCalls.flatMap { printReflectionCall(mockSequence, it) }) + } + */ protected val Class.javaString: String get() = this.asType.javaString diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 9c0b7c19b..4b7a02180 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -251,58 +251,115 @@ class ExecutorAS2JavaPrinter( return res } + override fun printMockSequence(mockSequence: MockSequence): List { + val res = mutableListOf() + printDeclarations(mockSequence, res) + printInsides(mockSequence, res) + return res + } + private fun printDeclarations( owner: ActionSequence, result: MutableList ) { - if (owner is ReflectionList) { - if (owner.name in printedDeclarations) return - printedDeclarations += owner.name - - for (api in owner) { - when (api) { - is ReflectionNewInstance -> result += printReflectionNewInstance(owner, api) - is ReflectionNewArray -> result += printReflectionNewArray(owner, api) - is ReflectionSetField -> printDeclarations(api.value, result) - is ReflectionSetStaticField -> printDeclarations(api.value, result) - is ReflectionArrayWrite -> printDeclarations(api.value, result) + val printDeclarations = { api: ReflectionCall -> + when (api) { + is ReflectionNewInstance -> result += printReflectionNewInstance(owner, api) + is ReflectionNewArray -> result += printReflectionNewArray(owner, api) + is ReflectionSetField -> printDeclarations(api.value, result) + is ReflectionSetStaticField -> printDeclarations(api.value, result) + is ReflectionArrayWrite -> printDeclarations(api.value, result) + } + } + + + when (owner) { + is ReflectionList -> { + if (owner.name in printedDeclarations) return + printedDeclarations += owner.name + + for (api in owner) { + printDeclarations(api) + } + } + + is MockSequence -> { + if (owner.name in printedDeclarations) return + printedDeclarations += owner.name + + for (mockCall in owner.mockCalls) { + when (mockCall) { + is MockNewInstance -> result += printMockNewInstance(owner, mockCall) + is MockSetupMethod -> mockCall.returnValues.forEach { printDeclarations(it, result) } + } + } + for (reflectionCall in owner.reflectionCalls) { + printDeclarations(reflectionCall) } } - } else { - owner.printAsJava() + + else -> { + owner.printAsJava() + } } } + private fun printInsides( owner: ActionSequence, result: MutableList ) { - if (owner is ReflectionList) { - if (owner.name in printedInsides) return - printedInsides += owner.name - - for (api in owner) { - when (api) { - is ReflectionSetField -> { - printInsides(api.value, result) - result += printReflectionSetField(owner, api) - } + val printInsides = { api: ReflectionCall -> + when (api) { + is ReflectionSetField -> { + printInsides(api.value, result) + result += printReflectionSetField(owner, api) + } - is ReflectionSetStaticField -> { - printInsides(api.value, result) - result += printReflectionSetStaticField(owner, api) - } + is ReflectionSetStaticField -> { + printInsides(api.value, result) + result += printReflectionSetStaticField(owner, api) + } - is ReflectionArrayWrite -> { - printInsides(api.value, result) - result += printReflectionArrayWrite(owner, api) - } + is ReflectionArrayWrite -> { + printInsides(api.value, result) + result += printReflectionArrayWrite(owner, api) + } + + else -> {} + } + } + + + when (owner) { + is ReflectionList -> { + if (owner.name in printedInsides) return + printedInsides += owner.name - else -> {} + for (api in owner) { + printInsides(api) } } - } else { - owner.printAsJava() + + is MockSequence -> { + if (owner.name in printedInsides) return + printedInsides += owner.name + + for (mockCall in owner.mockCalls) { + when (mockCall) { + is MockSetupMethod -> { + mockCall.returnValues.forEach { printInsides(it, result) } + result += printMockSetupMethod(owner, mockCall) + } + + is MockNewInstance -> {} + } + } + } + + else -> { + owner.printAsJava() + } } } diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/UnimplementedInterfaceConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt similarity index 71% rename from kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/UnimplementedInterfaceConcolicLongTest.kt rename to kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index 09046a50d..b156d7c5a 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/UnimplementedInterfaceConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -10,9 +10,9 @@ import kotlin.time.ExperimentalTime @ExperimentalSerializationApi @InternalSerializationApi @DelicateCoroutinesApi -class UnimplementedInterfaceConcolicLongTest : ConcolicTest("unimplemented-interface-concolic") { +class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test - fun unimplementedInterfaceTest() { - assertCoverage(cm["org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests"], 1.0) + fun mockTest() { + assertCoverage(cm["org/vorpal/research/kex/test/concolic/MockTests"], 1.0) } } diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java new file mode 100644 index 000000000..3b97ffe87 --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java @@ -0,0 +1,112 @@ +package org.vorpal.research.kex.test.concolic; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + + +@SuppressWarnings("ALL") +public class MockTests { + public interface ToMock { + int foo(); + + String bar(); + + ToMock recursion(); + } + +/* + public void testMockEasy(ToMock i) { + if (i.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockTwo(ToMock i) { + if (i.foo() == 42) { + if (i.foo() == 25) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockMultipleEasy(ToMock first, ToMock second) { + if (first.foo() == 42) { + if (second.foo() == 25) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockMultipleHard(ToMock first, ToMock second) { + if (first.foo() == 42) { + if (second.foo() == 29) { + if (first.foo() == second.foo() * 2) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } +*/ + + public void testMockFooBarBoth(ToMock a) { + if (a.foo() == 25) { + if (a.bar() == "Not again...") { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + if (a.bar() == null) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + } + +/* + public void testMockReturnUnimplemented(ToMock a) { + ToMock b = a.recursion(); + if (a == b) { + if (b.foo() == a.foo()) { + if (b.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + } + + public void testMockReturnUnimplementedCycle(ToMock a) { + ToMock b = a.recursion(); + if (a != b) { + ToMock c = b.recursion(); + if (c == a) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } +*/ +} diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java deleted file mode 100644 index 9c0e2e44b..000000000 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/UnimplementedInterfaceTests.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.vorpal.research.kex.test.concolic; - -import org.vorpal.research.kex.intrinsics.AssertIntrinsics; - - -@SuppressWarnings("ALL") -public class UnimplementedInterfaceTests { - public interface Unimplemented { - int foo(); - } - - public void testUnimplemented(Unimplemented i) { - if (i.foo() == 42) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } - -} From 5ea90767805684915044445b47635b8798e4e6ea Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 13 Nov 2023 23:18:22 +0100 Subject: [PATCH 024/128] Lambda to local function --- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 4b7a02180..e5803fac4 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -262,14 +262,12 @@ class ExecutorAS2JavaPrinter( owner: ActionSequence, result: MutableList ) { - val printDeclarations = { api: ReflectionCall -> - when (api) { - is ReflectionNewInstance -> result += printReflectionNewInstance(owner, api) - is ReflectionNewArray -> result += printReflectionNewArray(owner, api) - is ReflectionSetField -> printDeclarations(api.value, result) - is ReflectionSetStaticField -> printDeclarations(api.value, result) - is ReflectionArrayWrite -> printDeclarations(api.value, result) - } + fun printDeclarations(api: ReflectionCall): Unit = when (api) { + is ReflectionNewInstance -> result += printReflectionNewInstance(owner, api) + is ReflectionNewArray -> result += printReflectionNewArray(owner, api) + is ReflectionSetField -> printDeclarations(api.value, result) + is ReflectionSetStaticField -> printDeclarations(api.value, result) + is ReflectionArrayWrite -> printDeclarations(api.value, result) } @@ -309,25 +307,23 @@ class ExecutorAS2JavaPrinter( owner: ActionSequence, result: MutableList ) { - val printInsides = { api: ReflectionCall -> - when (api) { - is ReflectionSetField -> { - printInsides(api.value, result) - result += printReflectionSetField(owner, api) - } - - is ReflectionSetStaticField -> { - printInsides(api.value, result) - result += printReflectionSetStaticField(owner, api) - } + fun printInsides(api: ReflectionCall): Unit = when (api) { + is ReflectionSetField -> { + printInsides(api.value, result) + result += printReflectionSetField(owner, api) + } - is ReflectionArrayWrite -> { - printInsides(api.value, result) - result += printReflectionArrayWrite(owner, api) - } + is ReflectionSetStaticField -> { + printInsides(api.value, result) + result += printReflectionSetStaticField(owner, api) + } - else -> {} + is ReflectionArrayWrite -> { + printInsides(api.value, result) + result += printReflectionArrayWrite(owner, api) } + + else -> {} } From ed8fc900918edccd6dcca62346fb66ea4034465c Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 13 Nov 2023 23:18:40 +0100 Subject: [PATCH 025/128] Cleaning dependencies --- kex-annotation-processor/pom.xml | 10 ---------- kex-boolector/pom.xml | 10 ---------- kex-core/pom.xml | 5 ----- kex-executor/pom.xml | 5 ----- kex-ksmt/pom.xml | 10 ---------- kex-runner/pom.xml | 5 ----- kex-test/pom.xml | 5 ----- kex-z3/pom.xml | 10 ---------- pom.xml | 15 --------------- 9 files changed, 75 deletions(-) diff --git a/kex-annotation-processor/pom.xml b/kex-annotation-processor/pom.xml index 0e32786c1..b558bf05c 100644 --- a/kex-annotation-processor/pom.xml +++ b/kex-annotation-processor/pom.xml @@ -35,16 +35,6 @@ kotlinx-serialization-json-jvm ${serialization.version} - - org.mockito - mockito-core - 4.11.0 - - - net.bytebuddy - byte-buddy-agent - 1.14.5 - diff --git a/kex-boolector/pom.xml b/kex-boolector/pom.xml index 508b0c802..aebf9cb49 100644 --- a/kex-boolector/pom.xml +++ b/kex-boolector/pom.xml @@ -46,16 +46,6 @@ boolector-java ${boolector.version} - - org.mockito - mockito-core - 4.11.0 - - - net.bytebuddy - byte-buddy-agent - 1.14.5 - diff --git a/kex-core/pom.xml b/kex-core/pom.xml index 5347f22e4..e111279fd 100644 --- a/kex-core/pom.xml +++ b/kex-core/pom.xml @@ -98,11 +98,6 @@ easy-random-core ${easy-random.version} - - net.bytebuddy - byte-buddy-agent - 1.14.5 - org.mockito mockito-core diff --git a/kex-executor/pom.xml b/kex-executor/pom.xml index 5043d319a..6cdf4911a 100644 --- a/kex-executor/pom.xml +++ b/kex-executor/pom.xml @@ -57,11 +57,6 @@ mockito-core 4.11.0 - - net.bytebuddy - byte-buddy-agent - 1.14.5 - diff --git a/kex-ksmt/pom.xml b/kex-ksmt/pom.xml index f0298402d..a77912480 100644 --- a/kex-ksmt/pom.xml +++ b/kex-ksmt/pom.xml @@ -82,16 +82,6 @@ ksmt-runner ${ksmt.version} - - org.mockito - mockito-core - 4.11.0 - - - net.bytebuddy - byte-buddy-agent - 1.14.5 - diff --git a/kex-runner/pom.xml b/kex-runner/pom.xml index de753aabe..ec5c262b4 100644 --- a/kex-runner/pom.xml +++ b/kex-runner/pom.xml @@ -115,11 +115,6 @@ mockito-core 4.11.0 - - net.bytebuddy - byte-buddy-agent - 1.14.5 - diff --git a/kex-test/pom.xml b/kex-test/pom.xml index 1acb8f19f..98cd091d9 100644 --- a/kex-test/pom.xml +++ b/kex-test/pom.xml @@ -42,11 +42,6 @@ mockito-core 4.11.0 - - net.bytebuddy - byte-buddy-agent - 1.14.5 - diff --git a/kex-z3/pom.xml b/kex-z3/pom.xml index f8e409e59..92dc829c8 100644 --- a/kex-z3/pom.xml +++ b/kex-z3/pom.xml @@ -46,16 +46,6 @@ z3 ${z3.version} - - org.mockito - mockito-core - 4.11.0 - - - net.bytebuddy - byte-buddy-agent - 1.14.5 - diff --git a/pom.xml b/pom.xml index e55db1728..71bb7c1eb 100644 --- a/pom.xml +++ b/pom.xml @@ -83,21 +83,6 @@ mockito-core 4.11.0 - - net.bytebuddy - byte-buddy-agent - 1.14.5 - - - net.bytebuddy - byte-buddy - 1.14.5 - - - com.sun.xml.bind - jaxb-impl - 3.0.2 - From 26fdfa287bf5e0fc34141cf62c289d5d3d90e063 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 20 Nov 2023 15:32:06 +0100 Subject: [PATCH 026/128] More accurate printing of mocks. Trying to fix mocks when they are fields. --- .../research/kex/parameters/Parameters.kt | 68 ++++++-- .../kex/asm/analysis/util/extensions.kt | 3 +- .../javagen/ActionSequence2JavaPrinter.kt | 9 -- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 146 ++++++++++-------- .../research/kex/test/concolic/MockTests.java | 137 +++++++++++++++- 5 files changed, 272 insertions(+), 91 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 546d2e8ef..f98e0d19c 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -15,6 +15,9 @@ import org.vorpal.research.kex.util.KfgTargetFilter import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory +import org.vorpal.research.kthelper.logging.error +import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.logging.warn import kotlin.random.Random @Serializable @@ -97,28 +100,65 @@ fun Parameters.filterIgnoredStatic(): Parameters { return Parameters(instance, arguments, filteredStatics, others) } +private fun Descriptor.insertMocks(types: TypeFactory, descriptorToMock: MutableMap) { + when (this) { + is ConstantDescriptor -> {} + is ClassDescriptor -> { + this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + } -private fun Collection.replaceUninstantiableWithMocks( + is ObjectDescriptor -> { + this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + } + + is MockDescriptor -> { + this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + for ((_, returns) in methodReturns) { + returns.mapTo(returns) { value -> value.replaceWithMock(types, descriptorToMock) } + } + } + + is ArrayDescriptor -> { + this.elements.mapValuesTo(this.elements) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + } + } +} + +private fun Descriptor.notMockable(types: TypeFactory): Boolean { + val klass = (type.getKfgType(types) as? ClassType)?.klass + return klass == null || instantiationManager.isInstantiable(klass) || type.isKexRt || this is MockDescriptor +} + +private fun Descriptor.replaceWithMock( types: TypeFactory, descriptorToMock: MutableMap -): Collection { - return map { descriptor -> - if (descriptorToMock[descriptor] != null) return@map descriptorToMock[descriptor]!! - val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass - if (klass == null || instantiationManager.isInstantiable(klass) || descriptor.type.isKexRt) { - descriptor +): Descriptor { + val descriptor = this + if (descriptorToMock[descriptor] != null) return descriptorToMock[descriptor]!! + val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass + descriptor.insertMocks(types, descriptorToMock) + return if (descriptor.notMockable(types)) { + descriptor + } else { + klass ?: return descriptor.also { log.error { "Got null class to mock." } } + val mock = if (descriptor is ObjectDescriptor) { + MockDescriptor(klass.methods, descriptor).also { it.fields.putAll(descriptor.fields) } } else { - val mock = if (descriptor is ObjectDescriptor) { - MockDescriptor(klass.methods, descriptor) - } else { - MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods) - } - descriptorToMock[descriptor] = mock - mock + log.warn { "Strange descriptor to mock. Expected ObjectDescriptor. Got: $descriptor" } + MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods) } + descriptorToMock[descriptor] = mock + mock } } +private fun Collection.replaceUninstantiableWithMocks( + types: TypeFactory, + descriptorToMock: MutableMap +): Collection { + return map { it.replaceWithMock(types, descriptorToMock) } +} + fun Parameters.generateInitialMocks( types: TypeFactory ): Pair, Map> { diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index aeaa14570..24a2ea2d4 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -59,6 +59,7 @@ fun methodCalls( .map { clause -> clause.predicate } .filter { predicate -> predicate.type.name == "S" } .filterIsInstance() + .filter { predicate -> predicate.hasLhv } .map { predicate -> val term = predicate.lhv val descriptor = termToDescriptor[term] @@ -102,8 +103,6 @@ suspend fun Method.checkAsync( setupMocks(methodCalls, termToDescriptor, descriptorToMock) - - withMocks .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt index 61a9bc38d..86f3ccad7 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt @@ -373,15 +373,6 @@ open class ActionSequence2JavaPrinter( protected open fun printMockSequence(mockSequence: MockSequence): List = unreachable { log.error("Mock calls are not supported in AS 2 Java printer") } - /* - = mutableListOf().apply { - val mockCalls = mockSequence.mockCalls - addAll(printMockNewInstance(mockSequence, mockCalls[0] as MockNewInstance)) - addAll(mockCalls.subList(1, mockCalls.size).flatMap { printMockCall(mockSequence, it) }) - addAll(mockSequence.reflectionCalls.flatMap { printReflectionCall(mockSequence, it) }) - } - */ - protected val Class.javaString: String get() = this.asType.javaString diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index e5803fac4..2c7c3cf6c 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -9,6 +9,7 @@ import org.vorpal.research.kfg.type.* import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.error import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.logging.warn import org.vorpal.research.kthelper.runIf @@ -258,56 +259,105 @@ class ExecutorAS2JavaPrinter( return res } - private fun printDeclarations( - owner: ActionSequence, - result: MutableList + private fun printDeclarations(owner: ActionSequence, result: MutableList) { + when (owner) { + is ReflectionList -> printReflectionListDeclarations(owner, result) + is MockSequence -> printMockSequenceDeclarations(owner, result) + else -> { + log.warn { "Printing declarations for unsupported action sequence may break test. Unsupported AS: $owner" } + owner.printAsJava() + } + } + } + + private fun printMockSequenceDeclarations(owner: MockSequence, result: MutableList) { + if (owner.name in printedDeclarations) return + printedDeclarations += owner.name + + for (mockCall in owner.mockCalls) { + when (mockCall) { + is MockNewInstance -> result += printMockNewInstance(owner, mockCall) + is MockSetupMethod -> mockCall.returnValues.forEach { printDeclarations(it, result) } + } + } + for (reflectionCall in owner.reflectionCalls) { + printReflectionCallDeclarations(reflectionCall, result, owner) + } + } + + private fun printReflectionListDeclarations(owner: ReflectionList, result: MutableList) { + if (owner.name in printedDeclarations) return + printedDeclarations += owner.name + + for (api in owner) { + printReflectionCallDeclarations(api, result, owner) + } + } + + private fun printReflectionCallDeclarations( + api: ReflectionCall, + result: MutableList, + owner: ActionSequence ) { - fun printDeclarations(api: ReflectionCall): Unit = when (api) { + when (api) { is ReflectionNewInstance -> result += printReflectionNewInstance(owner, api) is ReflectionNewArray -> result += printReflectionNewArray(owner, api) is ReflectionSetField -> printDeclarations(api.value, result) is ReflectionSetStaticField -> printDeclarations(api.value, result) is ReflectionArrayWrite -> printDeclarations(api.value, result) } + } - when (owner) { - is ReflectionList -> { - if (owner.name in printedDeclarations) return - printedDeclarations += owner.name - - for (api in owner) { - printDeclarations(api) - } - } - - is MockSequence -> { - if (owner.name in printedDeclarations) return - printedDeclarations += owner.name + private fun printInsides(owner: ActionSequence, result: MutableList): Unit = when (owner) { + is ReflectionList -> printReflectionListInsides(owner, result) + is MockSequence -> printMockSequenceInsides(owner, result) + else -> { + log.warn { "Printing insides for unsupported action sequence may break test. Unsupported AS: $owner" } + owner.printAsJava() + } + } - for (mockCall in owner.mockCalls) { - when (mockCall) { - is MockNewInstance -> result += printMockNewInstance(owner, mockCall) - is MockSetupMethod -> mockCall.returnValues.forEach { printDeclarations(it, result) } - } - } - for (reflectionCall in owner.reflectionCalls) { - printDeclarations(reflectionCall) + private fun printMockSequenceInsides( + owner: MockSequence, + result: MutableList + ) { + if (owner.name in printedInsides) return + printedInsides += owner.name + + for (mockCall in owner.mockCalls) { + when (mockCall) { + is MockSetupMethod -> { + mockCall.returnValues.forEach { printInsides(it, result) } + result += printMockSetupMethod(owner, mockCall) } - } - else -> { - owner.printAsJava() + is MockNewInstance -> {} } } + for (reflectionCall in owner.reflectionCalls) { + printReflectionCallInsides(reflectionCall, result, owner) + } } - - private fun printInsides( - owner: ActionSequence, + private fun printReflectionListInsides( + owner: ReflectionList, result: MutableList ) { - fun printInsides(api: ReflectionCall): Unit = when (api) { + if (owner.name in printedInsides) return + printedInsides += owner.name + + for (api in owner) { + printReflectionCallInsides(api, result, owner) + } + } + + private fun printReflectionCallInsides( + api: ReflectionCall, + result: MutableList, + owner: ActionSequence + ) { + when (api) { is ReflectionSetField -> { printInsides(api.value, result) result += printReflectionSetField(owner, api) @@ -325,38 +375,6 @@ class ExecutorAS2JavaPrinter( else -> {} } - - - when (owner) { - is ReflectionList -> { - if (owner.name in printedInsides) return - printedInsides += owner.name - - for (api in owner) { - printInsides(api) - } - } - - is MockSequence -> { - if (owner.name in printedInsides) return - printedInsides += owner.name - - for (mockCall in owner.mockCalls) { - when (mockCall) { - is MockSetupMethod -> { - mockCall.returnValues.forEach { printInsides(it, result) } - result += printMockSetupMethod(owner, mockCall) - } - - is MockNewInstance -> {} - } - } - } - - else -> { - owner.printAsJava() - } - } } override fun printReflectionNewInstance(owner: ActionSequence, call: ReflectionNewInstance): List { diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java index 3b97ffe87..9a2080478 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java @@ -61,8 +61,9 @@ public void testMockMultipleHard(ToMock first, ToMock second) { AssertIntrinsics.kexAssert(true); } } -*/ +*/ +/* public void testMockFooBarBoth(ToMock a) { if (a.foo() == 25) { if (a.bar() == "Not again...") { @@ -78,8 +79,9 @@ public void testMockFooBarBoth(ToMock a) { } } } +*//* + -/* public void testMockReturnUnimplemented(ToMock a) { ToMock b = a.recursion(); if (a == b) { @@ -108,5 +110,136 @@ public void testMockReturnUnimplementedCycle(ToMock a) { AssertIntrinsics.kexAssert(true); } } + + + public void testMockStringCycle(ToMock a) { + ToMock b = a.recursion(); + if (a != b) { + String abar = a.bar(); + String bbar = b.bar(); + if (abar != null && abar == bbar) { + AssertIntrinsics.kexAssert(true); + } + } + } +*/ + + abstract class AbstractToMock { + ToMock field; + + abstract int foo(); + } + + public void testAbstractToMock(AbstractToMock a) { + ToMock b = a.field; + if (b.foo() == 42) { + if (a.foo() == 33) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + +/* + interface PrimitiveMocking { + byte bt(); + + boolean b(); + + short s(); + + int i(); + + long l(); + + char c(); + + float f(); + + double d(); + } + + static void ok() { + AssertIntrinsics.kexAssert(true); + } + + + public void testPrimitives(PrimitiveMocking mock) { + if (mock.b()) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + + if (mock.bt() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + + if (mock.s() == 50) { + ok(); + } else { + ok(); + } + + if (mock.i() == 60) { + ok(); + } else { + ok(); + } + + if (mock.c() == 200) { + ok(); + } else { + ok(); + } + + if (mock.l() == 1e5) { + ok(); + } else { + ok(); + } + + } + + + interface MockWithEnum { + TestEnum foo(); + } + + public void testMockAndEnum(MockWithEnum mock) { + if (mock.foo() == TestEnum.B) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public class Impl { + ToMock field; + } + + public void testMockIsField(Impl a) { + ToMock mock = a.field; + if (mock.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + + public void testArrayOfMocks(ToMock[][] array) { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + AssertIntrinsics.kexAssert(array[i][j].foo() == 10 * i + j); + } + } + } */ + } From 8685c44e6deab0048e2a11b530e71479935fa57d Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 20 Nov 2023 22:43:53 +0100 Subject: [PATCH 027/128] MockDescriptors creates only from ObjectDescriptors. UnknownGenerator fixes from master --- .../vorpal/research/kex/parameters/Parameters.kt | 2 +- .../actionsequence/generator/UnknownGenerator.kt | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index f98e0d19c..7fb299222 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -126,7 +126,7 @@ private fun Descriptor.insertMocks(types: TypeFactory, descriptorToMock: Mutable private fun Descriptor.notMockable(types: TypeFactory): Boolean { val klass = (type.getKfgType(types) as? ClassType)?.klass - return klass == null || instantiationManager.isInstantiable(klass) || type.isKexRt || this is MockDescriptor + return klass == null || instantiationManager.isInstantiable(klass) || type.isKexRt || this !is ObjectDescriptor } private fun Descriptor.replaceWithMock( diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/UnknownGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/UnknownGenerator.kt index 341d112ac..1b1d2212b 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/UnknownGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/UnknownGenerator.kt @@ -29,7 +29,17 @@ class UnknownGenerator( val kfgClass = (descriptor.type.getKfgType(types) as ClassType).klass actionSequence += ReflectionNewInstance(kfgClass.asType) - actionSequence.list.addSetupFieldsCalls(descriptor.fields, kfgClass, types, fallback) + for ((field, value) in descriptor.fields) { + val fieldType = field.second.getKfgType(types).rtUnmapped + val kfgField = try { + kfgClass.getField(field.first, fieldType) + } catch (e: UnknownInstanceException) { + log.warn("Field ${field.first}: ${field.second} is not found in class $kfgClass") + continue + } + val valueAS = fallback.generate(value) + actionSequence += ReflectionSetField(kfgField, valueAS) + } } is ArrayDescriptor -> { val kfgArray = (descriptor.type.getKfgType(types) as ArrayType) @@ -44,9 +54,9 @@ class UnknownGenerator( } } } + is ClassDescriptor -> { val kfgClass = (descriptor.type.getKfgType(types) as ClassType).klass - actionSequence.list.addSetupFieldsCalls(descriptor.fields, kfgClass, types, fallback) for ((field, value) in descriptor.fields) { val fieldType = field.second.getKfgType(types).rtUnmapped val kfgField = try { @@ -59,6 +69,7 @@ class UnknownGenerator( actionSequence += ReflectionSetStaticField(kfgField, valueAS) } } + else -> UnknownSequence( "${descriptor.term}", descriptor.wrappedType, From a10c33ee9232c3ad3ff3056602f776afe6398b45 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 21 Nov 2023 14:22:58 +0100 Subject: [PATCH 028/128] MockTests split into different classes. Fixed DescriptorGenerator, StaticDescriptor-s now generating correctly. --- .../research/kex/descriptor/descriptor.kt | 16 ++ .../research/kex/parameters/Parameters.kt | 42 +++- .../research/kex/util/KfgClassLoader.kt | 16 +- .../state/transformer/AbstractGenerator.kt | 4 +- .../state/transformer/DescriptorGenerator.kt | 15 +- .../kex/concolic/MockConcolicLongTest.kt | 28 ++- .../kex/test/concolic/mock/MockEnumTests.java | 21 ++ .../concolic/mock/MockPrimitivesTests.java | 69 +++++++ .../MockReturnsMockTests.java} | 181 +++++++++--------- .../test/concolic/mock/MockStaticsTests.java | 79 ++++++++ .../kex/test/concolic/mock/MockTests.java | 144 ++++++++++++++ .../concolic/mock/MockWithFieldsTests.java | 49 +++++ .../kex/test/concolic/mock/ToMock.java | 9 + 13 files changed, 555 insertions(+), 118 deletions(-) create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockEnumTests.java create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockPrimitivesTests.java rename kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/{MockTests.java => mock/MockReturnsMockTests.java} (67%) create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockStaticsTests.java create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockTests.java create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockWithFieldsTests.java create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/ToMock.java diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index a873bd058..0669a0a34 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -20,6 +20,7 @@ import org.vorpal.research.kfg.type.TypeFactory import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log +import ru.spbstu.wheels.joinToString import kotlin.random.Random sealed class Descriptor(term: Term, type: KexType) { @@ -698,6 +699,21 @@ class MockDescriptor(term: Term, type: KexClass, methods: Collection = e return allReturns.any { it.contains(other, visited) } || fields.values.any { it.contains(other, visited) } } + override fun print(map: MutableMap): String { + if (this in map) return map[this]!! + val base = super.print(map) + return base + "\n" + methodReturns.joinToString(separator = "\n") { method, values -> + "$method : ${ + values.joinToString( + separator = ", ", + prefix = "{", + postfix = "}" + ) { value -> value.print(map) } + }" + } + + } + override fun countDepth(visited: Set, cache: MutableMap): Int { if (this in cache) return cache[this]!! if (this in visited) return 0 diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 7fb299222..28a25ecef 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -100,26 +100,46 @@ fun Parameters.filterIgnoredStatic(): Parameters { return Parameters(instance, arguments, filteredStatics, others) } -private fun Descriptor.insertMocks(types: TypeFactory, descriptorToMock: MutableMap) { +private fun Descriptor.insertMocks( + types: TypeFactory, + descriptorToMock: MutableMap, + visited: MutableSet +) { + fun insertIntoMap(map: MutableMap) = + map.mapValuesTo(map) { (_, value) -> value.replaceWithMock(types, descriptorToMock, visited) } + + if (this in visited) return + visited.add(this) + when (this) { is ConstantDescriptor -> {} is ClassDescriptor -> { - this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + insertIntoMap(this.fields) +// this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } } is ObjectDescriptor -> { - this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + insertIntoMap(this.fields) +// this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } } is MockDescriptor -> { - this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } +// insertIntoMap(this.fields) + this.fields.mapValuesTo(this.fields) { (_, value) -> + value.replaceWithMock( + types, + descriptorToMock, + visited + ) + } for ((_, returns) in methodReturns) { - returns.mapTo(returns) { value -> value.replaceWithMock(types, descriptorToMock) } + returns.mapTo(returns) { value -> value.replaceWithMock(types, descriptorToMock, visited) } } } is ArrayDescriptor -> { - this.elements.mapValuesTo(this.elements) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + insertIntoMap(this.elements) +// this.elements.mapValuesTo(this.elements) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } } } } @@ -131,13 +151,14 @@ private fun Descriptor.notMockable(types: TypeFactory): Boolean { private fun Descriptor.replaceWithMock( types: TypeFactory, - descriptorToMock: MutableMap + descriptorToMock: MutableMap, + visited: MutableSet ): Descriptor { val descriptor = this if (descriptorToMock[descriptor] != null) return descriptorToMock[descriptor]!! val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass - descriptor.insertMocks(types, descriptorToMock) return if (descriptor.notMockable(types)) { + descriptor.insertMocks(types, descriptorToMock, visited) descriptor } else { klass ?: return descriptor.also { log.error { "Got null class to mock." } } @@ -147,7 +168,9 @@ private fun Descriptor.replaceWithMock( log.warn { "Strange descriptor to mock. Expected ObjectDescriptor. Got: $descriptor" } MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods) } + visited.add(descriptor) descriptorToMock[descriptor] = mock + mock.insertMocks(types, descriptorToMock, visited) mock } } @@ -156,7 +179,8 @@ private fun Collection.replaceUninstantiableWithMocks( types: TypeFactory, descriptorToMock: MutableMap ): Collection { - return map { it.replaceWithMock(types, descriptorToMock) } + val visited = mutableSetOf() + return map { it.replaceWithMock(types, descriptorToMock, visited) } } fun Parameters.generateInitialMocks( diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 03a83d579..3f78e6b42 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -32,7 +32,21 @@ class KfgClassLoader( "class org.vorpal.research.kex.test.concolic.SetConcolicTests", "class org.vorpal.research.kex.test.concolic.StringConcolicTests", "class org.vorpal.research.kex.test.concolic.TestEnum", - "class org.vorpal.research.kex.test.concolic.MockTests", + + "class org.vorpal.research.kex.test.concolic.mock.ToMock", + "class org.vorpal.research.kex.test.concolic.mock.MockTests", + "class org.vorpal.research.kex.test.concolic.mock.MockReturnsMockTests", + "class org.vorpal.research.kex.test.concolic.mock.MockPrimitivesTests", + "class org.vorpal.research.kex.test.concolic.mock.MockEnumTests", + "class org.vorpal.research.kex.test.concolic.mock.MockWithFieldsTests", + "class org.vorpal.research.kex.test.concolic.mock.MockStaticsTests", + +// "class org.vorpal.research.kex.test.concolic.mock.MockTests\$Cont", +// "class org.vorpal.research.kex.test.concolic.mock.MockTests\$WithStaticInt", +// "class org.vorpal.research.kex.test.concolic.mock.MockTests\$StaticRecursion", +// "class org.vorpal.research.kex.test.concolic.mock.MockTests\$RecursionWithStaticField", +// "class org.vorpal.research.kex.test.concolic.mock.MockTests\$RecursionWithStaticField\$ContStatic", + "class org.vorpal.research.kex.test.debug.ObjectGenerationTests", ).mapTo(mutableSetOf()) { KfgTargetFilter.parse(it) } private val EXCLUDES = setOf( diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt index 818d2c401..99e6173f7 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt @@ -59,9 +59,9 @@ interface AbstractGenerator : Transformer> { fun generateAll() { hashSetOf().apply { addAll(model.assignments.keys) + addAll(model.typeMap.keys) addAll(model.assignments.values) model.arrays.values.forEach { addAll(it.keys) } - addAll(model.typeMap.keys) } .forEach { term -> reanimateTerm(term) } } @@ -91,7 +91,7 @@ interface AbstractGenerator : Transformer> { } generateThis() generateArgs() - generateAll() +// generateAll() return instance to args } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index 08bc44671..d44cf8882 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -50,21 +50,12 @@ class DescriptorGenerator( is ConstantDescriptor.Float -> this.value is ConstantDescriptor.Double -> this.value } - else -> this } override fun checkPath(path: Predicate): Boolean = when (path) { - is EqualityPredicate -> checkTerms( - path.lhv, - path.rhv - ) { a, b -> a.numericValue == b.numericValue } - - is InequalityPredicate -> checkTerms( - path.lhv, - path.rhv - ) { a, b -> a.numericValue != b.numericValue } - + is EqualityPredicate -> checkTerms(path.lhv, path.rhv) { a, b -> a.numericValue == b.numericValue } + is InequalityPredicate -> checkTerms(path.lhv, path.rhv) { a, b -> a.numericValue != b.numericValue } is DefaultSwitchPredicate -> { val lhv = path.cond val conditions = path.cases @@ -72,7 +63,6 @@ class DescriptorGenerator( val condValues = conditions.map { (it as ConstIntTerm).value } lhvValue !in condValues } - else -> unreachable { log.error("Unexpected predicate in path: $path") } } } @@ -138,6 +128,7 @@ fun generateInitialDescriptors( ): Pair, Map> { val generator = DescriptorGenerator(method, ctx, model, InitialDescriptorReanimator(model, ctx)) generator.apply(state) + generator.generateAll() return Parameters( generator.instance, generator.args.mapIndexed { index, arg -> diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index b156d7c5a..3af4d4e50 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -11,8 +11,34 @@ import kotlin.time.ExperimentalTime @InternalSerializationApi @DelicateCoroutinesApi class MockConcolicLongTest : ConcolicTest("mock-concolic") { + val prefix = "org/vorpal/research/kex/test/concolic/mock/" + @Test fun mockTest() { - assertCoverage(cm["org/vorpal/research/kex/test/concolic/MockTests"], 1.0) + assertCoverage(cm[prefix + "MockTests"], 1.0) + } + @Test + fun mockReturnsMockTest() { + assertCoverage(cm[prefix + "MockReturnsMockTests"], 1.0) + } + + @Test + fun mockPrimitivesTest() { + assertCoverage(cm[prefix + "MockPrimitivesTests"], 1.0) + } + + @Test + fun mockEnumTest() { + assertCoverage(cm[prefix + "MockEnumTests"], 1.0) + } + + @Test + fun mockWithFieldsTests() { + assertCoverage(cm[prefix + "MockWithFieldsTests"], 1.0) + } + + @Test + fun mockStaticsTests() { + assertCoverage(cm[prefix + "MockStaticsTests"], 1.0) } } diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockEnumTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockEnumTests.java new file mode 100644 index 000000000..f5434935a --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockEnumTests.java @@ -0,0 +1,21 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; +import org.vorpal.research.kex.test.concolic.TestEnum; + + +@SuppressWarnings("ALL") +public class MockEnumTests { + + interface MockWithEnum { + TestEnum foo(); + } + + public void testMockAndEnum(MockWithEnum mock) { + if (mock.foo() == TestEnum.B) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } +} diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockPrimitivesTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockPrimitivesTests.java new file mode 100644 index 000000000..ba3a0492b --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockPrimitivesTests.java @@ -0,0 +1,69 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + + +@SuppressWarnings("ALL") +public class MockPrimitivesTests { + interface PrimitiveMocking { + byte bt(); + + boolean b(); + + short s(); + + int i(); + + long l(); + + char c(); + + float f(); + + double d(); + } + + static void ok() { + AssertIntrinsics.kexAssert(true); + } + + + public void testPrimitives(PrimitiveMocking mock) { + if (mock.b()) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + + if (mock.bt() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + + if (mock.s() == 50) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + + if (mock.i() == 60) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + + if (mock.c() == 200) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + + if (mock.l() == 1e5) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + + } +} diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockReturnsMockTests.java similarity index 67% rename from kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java rename to kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockReturnsMockTests.java index 9a2080478..9b2c861ae 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/MockTests.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockReturnsMockTests.java @@ -1,87 +1,10 @@ -package org.vorpal.research.kex.test.concolic; +package org.vorpal.research.kex.test.concolic.mock; import org.vorpal.research.kex.intrinsics.AssertIntrinsics; @SuppressWarnings("ALL") -public class MockTests { - public interface ToMock { - int foo(); - - String bar(); - - ToMock recursion(); - } - -/* - public void testMockEasy(ToMock i) { - if (i.foo() == 42) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } - - public void testMockTwo(ToMock i) { - if (i.foo() == 42) { - if (i.foo() == 25) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } else { - AssertIntrinsics.kexAssert(true); - } - } - - public void testMockMultipleEasy(ToMock first, ToMock second) { - if (first.foo() == 42) { - if (second.foo() == 25) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } else { - AssertIntrinsics.kexAssert(true); - } - } - - public void testMockMultipleHard(ToMock first, ToMock second) { - if (first.foo() == 42) { - if (second.foo() == 29) { - if (first.foo() == second.foo() * 2) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } else { - AssertIntrinsics.kexAssert(true); - } - } else { - AssertIntrinsics.kexAssert(true); - } - } - -*/ -/* - public void testMockFooBarBoth(ToMock a) { - if (a.foo() == 25) { - if (a.bar() == "Not again...") { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } else { - if (a.bar() == null) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } - } -*//* - - +public class MockReturnsMockTests { public void testMockReturnUnimplemented(ToMock a) { ToMock b = a.recursion(); if (a == b) { @@ -122,7 +45,9 @@ public void testMockStringCycle(ToMock a) { } } } -*/ + + /* + // Unstable begins abstract class AbstractToMock { ToMock field; @@ -143,7 +68,6 @@ public void testAbstractToMock(AbstractToMock a) { } } -/* interface PrimitiveMocking { byte bt(); @@ -181,27 +105,27 @@ public void testPrimitives(PrimitiveMocking mock) { } if (mock.s() == 50) { - ok(); + AssertIntrinsics.kexAssert(true); } else { - ok(); + AssertIntrinsics.kexAssert(true); } if (mock.i() == 60) { - ok(); + AssertIntrinsics.kexAssert(true); } else { - ok(); + AssertIntrinsics.kexAssert(true); } if (mock.c() == 200) { - ok(); + AssertIntrinsics.kexAssert(true); } else { - ok(); + AssertIntrinsics.kexAssert(true); } if (mock.l() == 1e5) { - ok(); + AssertIntrinsics.kexAssert(true); } else { - ok(); + AssertIntrinsics.kexAssert(true); } } @@ -219,6 +143,7 @@ public void testMockAndEnum(MockWithEnum mock) { } } + public class Impl { ToMock field; } @@ -232,14 +157,84 @@ public void testMockIsField(Impl a) { } } - public void testArrayOfMocks(ToMock[][] array) { - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { + for (int i = 0; i < 1; i++) { + for (int j = 0; j < 1; j++) { AssertIntrinsics.kexAssert(array[i][j].foo() == 10 * i + j); } } } -*/ + static class WithStaticMock { + static ToMock mock; + } + + public void testMockStatic() { + if (WithStaticMock.mock.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public static abstract class WithStaticInt { + static int staticInt; + + public abstract int foo(); + } + + public void testMockHasStaticField() { + if (WithStaticInt.staticInt == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockHasStaticField(WithStaticInt mock) { + if (WithStaticInt.staticInt == 42) { + if (mock.foo() == 11) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + static abstract class RecursionWithField { + RecursionWithField stRec; + + abstract RecursionWithField fun(); + } + + public void testMockStaticRecursion(RecursionWithField argMock) { + RecursionWithField a = argMock.fun(); + if (a.fun() == a.stRec) { + if (a.stRec.fun() == a) { + AssertIntrinsics.kexAssert(true); + } + } + } + + static abstract class RecursionWithStaticField { + + static abstract class ContStatic { + static RecursionWithStaticField stRec; + } + + abstract RecursionWithStaticField fun(); + } + + public void testMockStaticFieldRecursion(RecursionWithStaticField argMock) { + RecursionWithStaticField a = argMock.fun(); + if (a.fun() == RecursionWithStaticField.ContStatic.stRec) { + if (RecursionWithStaticField.ContStatic.stRec.fun() == a) { + AssertIntrinsics.kexAssert(true); + } + } + } +*/ } diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockStaticsTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockStaticsTests.java new file mode 100644 index 000000000..0f5852104 --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockStaticsTests.java @@ -0,0 +1,79 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + + +@SuppressWarnings("ALL") +public class MockStaticsTests { + static class WithStaticMock { + static ToMock mock; + } + + public void testMockStatic() { + if (WithStaticMock.mock.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + static abstract class WithStaticInt { + static int staticInt; + + public abstract int foo(); + } + + public void testMockHasStaticField() { + if (WithStaticInt.staticInt == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockHasStaticField(WithStaticInt mock) { + if (WithStaticInt.staticInt == 42) { + if (mock.foo() == 11) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + static abstract class RecursionWithField { + RecursionWithField stRec; + + abstract RecursionWithField fun(); + } + + public void testMockStaticRecursion(RecursionWithField argMock) { + RecursionWithField a = argMock.fun(); + if (a.fun() == a.stRec) { + if (a.stRec.fun() == a) { + AssertIntrinsics.kexAssert(true); + } + } + } + + static abstract class RecursionWithStaticField { + + static abstract class StaticWithMockField { + static RecursionWithStaticField stRec; + } + + abstract RecursionWithStaticField fun(); + } + + public void testMockStaticFieldRecursion(RecursionWithStaticField argMock) { + RecursionWithStaticField a = argMock.fun(); + if (a.fun() == RecursionWithStaticField.StaticWithMockField.stRec) { + if (RecursionWithStaticField.StaticWithMockField.stRec.fun() == a) { + AssertIntrinsics.kexAssert(true); + } + } + } +} diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockTests.java new file mode 100644 index 000000000..0dffa07dd --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockTests.java @@ -0,0 +1,144 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + + +@SuppressWarnings("ALL") +public class MockTests { + public void testMock1(ToMock i) { + if (i.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMock2(ToMock i) { + if (i.foo() == 42) { + if (i.foo() == 25) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockMany1(ToMock first, ToMock second) { + if (first.foo() == 42) { + if (second.foo() == 25) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockMany2(ToMock first, ToMock second) { + if (first.foo() == 42) { + if (second.foo() == 29) { + if (first.foo() == second.foo() * 2) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockDifferentMethods(ToMock a) { + if (a.foo() == 25) { + if (a.bar() == "Not again...") { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + if (a.bar() == null) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + } + + + static class WithStaticMock { + static ToMock mock; + } + + public void testMockStatic() { + if (WithStaticMock.mock.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + static abstract class WithStaticInt { + static int staticInt; + + public abstract int foo(); + } + + public void testMockHasStaticField() { + if (WithStaticInt.staticInt == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockHasStaticField(WithStaticInt mock) { + if (WithStaticInt.staticInt == 42) { + if (mock.foo() == 11) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + static abstract class RecursionWithField { + RecursionWithField stRec; + + abstract RecursionWithField fun(); + } + + public void testMockStaticRecursion(RecursionWithField argMock) { + RecursionWithField a = argMock.fun(); + if (a.fun() == a.stRec) { + if (a.stRec.fun() == a) { + AssertIntrinsics.kexAssert(true); + } + } + } + + static abstract class RecursionWithStaticField { + + static abstract class StaticWithMockField { + static RecursionWithStaticField stRec; + } + + abstract RecursionWithStaticField fun(); + } + + public void testMockStaticFieldRecursion(RecursionWithStaticField argMock) { + RecursionWithStaticField a = argMock.fun(); + if (a.fun() == RecursionWithStaticField.StaticWithMockField.stRec) { + if (RecursionWithStaticField.StaticWithMockField.stRec.fun() == a) { + AssertIntrinsics.kexAssert(true); + } + } + } +} diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockWithFieldsTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockWithFieldsTests.java new file mode 100644 index 000000000..7f46df876 --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockWithFieldsTests.java @@ -0,0 +1,49 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + + +@SuppressWarnings("ALL") +public class MockWithFieldsTests { + abstract class AbstractToMock { + ToMock field; + + abstract int foo(); + } + + public void testAbstractToMock(AbstractToMock a) { + ToMock b = a.field; + if (b.foo() == 42) { + if (a.foo() == 33) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + + public class Impl { + ToMock field; + } + + public void testMockIsField(Impl a) { + ToMock mock = a.field; + if (mock.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testArrayOfMocks(ToMock[][] array) { + for (int i = 0; i < 1; i++) { + for (int j = 0; j < 1; j++) { + AssertIntrinsics.kexAssert(array[i][j].foo() == 10 * i + j); + } + } + } + +} diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/ToMock.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/ToMock.java new file mode 100644 index 000000000..34317650a --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/ToMock.java @@ -0,0 +1,9 @@ +package org.vorpal.research.kex.test.concolic.mock; + +public interface ToMock { + int foo(); + + String bar(); + + ToMock recursion(); +} From 02c259ccd314526a811854f1676277e964f8ddb3 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 27 Nov 2023 12:22:22 +0100 Subject: [PATCH 029/128] Fixes. All changes before publishing --- .../asm/manager/ClassInstantiationManager.kt | 6 +- .../research/kex/descriptor/descriptor.kt | 4 +- .../research/kex/parameters/Parameters.kt | 33 ++++------ .../kex/trace/symbolic/SymbolicState.kt | 9 +-- .../research/kex/util/KfgClassLoader.kt | 3 + .../kex/concolic/MockConcolicLongTest.kt | 5 ++ kex-test.ini | 2 +- kex-test/pom.xml | 5 ++ .../research/kex/test/concolic/TestEnum.java | 14 +++- .../concolic/mock/MockCollectionsTests.java | 66 +++++++++++++++++++ pom.xml | 11 ++++ 11 files changed, 122 insertions(+), 36 deletions(-) create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockCollectionsTests.java diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt index 7cf85c69d..3b319a9d2 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt @@ -10,7 +10,9 @@ import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.ktype.kexType import org.vorpal.research.kex.util.isSubtypeOfCached import org.vorpal.research.kfg.ClassManager -import org.vorpal.research.kfg.ir.* +import org.vorpal.research.kfg.ir.Class +import org.vorpal.research.kfg.ir.ConcreteClass +import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kfg.type.ArrayType import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.SystemTypeNames @@ -22,7 +24,7 @@ import kotlin.random.Random val instantiationManager: ClassInstantiationManager get() = StringClassInstantiationManagerImpl -class NoConcreteInstanceException(val klass: Class) : Exception() +class NoConcreteInstanceException(val klass: Class) : Exception("No concrete instance for $klass") interface ClassInstantiationManager { fun isDirectlyInstantiable(klass: Class, accessLevel: AccessModifier): Boolean diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 0669a0a34..29a1d32c5 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -637,9 +637,7 @@ class MockDescriptor(term: Term, type: KexClass, methods: Collection = e original.type as KexClass, methods ) { - for ((field, value) in original.fields) { - fields[field] = value - } + fields.putAll(original.fields) } val methodReturns: Map> = diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 28a25ecef..d417fc489 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -60,7 +60,7 @@ fun Parameters.concreteParameters( instance?.concretize(cm, accessLevel, random), arguments.map { it.concretize(cm, accessLevel, random) }, statics.mapTo(mutableSetOf()) { it.concretize(cm, accessLevel, random) }, - others.mapTo(mutableSetOf()) { it.concretize(cm, accessLevel, random) } +// others.mapTo(mutableSetOf()) { it.concretize(cm, accessLevel, random) } ) fun Parameters.filterStaticFinals(cm: ClassManager): Parameters { @@ -105,8 +105,7 @@ private fun Descriptor.insertMocks( descriptorToMock: MutableMap, visited: MutableSet ) { - fun insertIntoMap(map: MutableMap) = - map.mapValuesTo(map) { (_, value) -> value.replaceWithMock(types, descriptorToMock, visited) } + fun Descriptor.replaceWithMock(): Descriptor = replaceWithMock(types, descriptorToMock, visited) if (this in visited) return visited.add(this) @@ -114,39 +113,29 @@ private fun Descriptor.insertMocks( when (this) { is ConstantDescriptor -> {} is ClassDescriptor -> { - insertIntoMap(this.fields) -// this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + fields.mapValuesTo(fields) { (_, value) -> value.replaceWithMock() } } is ObjectDescriptor -> { - insertIntoMap(this.fields) -// this.fields.mapValuesTo(this.fields) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + fields.mapValuesTo(fields) { (_, value) -> value.replaceWithMock() } } is MockDescriptor -> { -// insertIntoMap(this.fields) - this.fields.mapValuesTo(this.fields) { (_, value) -> - value.replaceWithMock( - types, - descriptorToMock, - visited - ) - } + fields.mapValuesTo(fields) { (_, value) -> value.replaceWithMock() } for ((_, returns) in methodReturns) { - returns.mapTo(returns) { value -> value.replaceWithMock(types, descriptorToMock, visited) } + returns.mapTo(returns) { value -> value.replaceWithMock() } } } is ArrayDescriptor -> { - insertIntoMap(this.elements) -// this.elements.mapValuesTo(this.elements) { (_, value) -> value.replaceWithMock(types, descriptorToMock) } + elements.mapValuesTo(elements) { (_, value) -> value.replaceWithMock() } } } } -private fun Descriptor.notMockable(types: TypeFactory): Boolean { +private fun Descriptor.isMockable(types: TypeFactory): Boolean { val klass = (type.getKfgType(types) as? ClassType)?.klass - return klass == null || instantiationManager.isInstantiable(klass) || type.isKexRt || this !is ObjectDescriptor + return klass != null && !instantiationManager.isInstantiable(klass) && !type.isKexRt && this is ObjectDescriptor } private fun Descriptor.replaceWithMock( @@ -157,11 +146,11 @@ private fun Descriptor.replaceWithMock( val descriptor = this if (descriptorToMock[descriptor] != null) return descriptorToMock[descriptor]!! val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass - return if (descriptor.notMockable(types)) { + return if (!descriptor.isMockable(types)) { descriptor.insertMocks(types, descriptorToMock, visited) descriptor } else { - klass ?: return descriptor.also { log.error { "Got null class to mock." } } + klass ?: return descriptor.also { log.error { "Got null class to mock. Descriptor: $descriptor" } } val mock = if (descriptor is ObjectDescriptor) { MockDescriptor(klass.methods, descriptor).also { it.fields.putAll(descriptor.fields) } } else { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicState.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicState.kt index 4cb1ce7a5..3c9db783a 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicState.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicState.kt @@ -2,12 +2,7 @@ package org.vorpal.research.kex.trace.symbolic -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentMapOf -import kotlinx.collections.immutable.toPersistentList -import kotlinx.collections.immutable.toPersistentMap +import kotlinx.collections.immutable.* import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -338,7 +333,7 @@ data class PersistentSymbolicState( override val concreteValues: @Contextual PersistentMap, override val termMap: @Contextual PersistentMap, ) : SymbolicState() { - override fun toString() = clauses.joinToString("\n") { it.predicate.toString() } + override fun toString() = clauses.joinToString("\n") { it.predicate.toString() } + "\n${path.lastOrNull()}" override fun plus(other: SymbolicState): PersistentSymbolicState = PersistentSymbolicState( clauses = clauses + other.clauses, diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 3f78e6b42..926ae2af2 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -40,6 +40,9 @@ class KfgClassLoader( "class org.vorpal.research.kex.test.concolic.mock.MockEnumTests", "class org.vorpal.research.kex.test.concolic.mock.MockWithFieldsTests", "class org.vorpal.research.kex.test.concolic.mock.MockStaticsTests", + "class org.vorpal.research.kex.test.concolic.mock.MockCollectionsTests", + "class org.vorpal.research.kex.test.concolic.mock.MockCollectionsTests\$Container", + "class org.vorpal.research.kex.test.concolic.mock.MockCollectionsTests\$NoMock", // "class org.vorpal.research.kex.test.concolic.mock.MockTests\$Cont", // "class org.vorpal.research.kex.test.concolic.mock.MockTests\$WithStaticInt", diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index 3af4d4e50..7b6661c12 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -41,4 +41,9 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { fun mockStaticsTests() { assertCoverage(cm[prefix + "MockStaticsTests"], 1.0) } + + @Test + fun mockCollectionsTests() { + assertCoverage(cm[prefix + "MockCollectionsTests"], 1.0) + } } diff --git a/kex-test.ini b/kex-test.ini index 33efafbe2..dbec09c0d 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -90,7 +90,7 @@ slicing = false logQuery = true logFormulae = false -logSMTLib = false +logSMTLib = true simplifyFormulae = true diff --git a/kex-test/pom.xml b/kex-test/pom.xml index 98cd091d9..839d3f820 100644 --- a/kex-test/pom.xml +++ b/kex-test/pom.xml @@ -42,6 +42,11 @@ mockito-core 4.11.0 + + + + + diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/TestEnum.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/TestEnum.java index 80f4a04b4..7e061e366 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/TestEnum.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/TestEnum.java @@ -1,5 +1,17 @@ package org.vorpal.research.kex.test.concolic; public enum TestEnum { - A, B, C + A, B, C; + + int foo() { + switch (this) { + case A: + return 42; + case B: + throw new IllegalArgumentException(); + case C: + return 22; + } + throw new IllegalStateException(); + } } diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockCollectionsTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockCollectionsTests.java new file mode 100644 index 000000000..7581e2807 --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockCollectionsTests.java @@ -0,0 +1,66 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; +import org.vorpal.research.kex.test.concolic.TestEnum; + +import java.util.*; + + +@SuppressWarnings("ALL") +public class MockCollectionsTests { + static class Container { + T elem; + } + + static class NoMock { + int field; + + int foo() { + return field; + } + } + + /* + public void TestNoMock(Container container) { + if (container.elem.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + */ + public void TestMockContainer(Container container) { + if (container.elem.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } +/* + public void testMockList(ArrayList list) { + if (list.get(0).foo() == 42) { + if (list.get(0).foo() == 33) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockSet(HashSet set) { + Iterator iterator = set.iterator(); + if (iterator.next().foo() == 35) { + if (iterator.next().foo() == -1) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + + } + } else { + AssertIntrinsics.kexAssert(true); + } + } +*/ +} diff --git a/pom.xml b/pom.xml index 71bb7c1eb..3f71940fb 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,17 @@ kotlin-maven-plugin ${kotlin.version} + From d15b17c58fb6282b7178305b22830f2deec8ae1a Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 27 Nov 2023 12:23:45 +0100 Subject: [PATCH 030/128] mockito-core.jar --- runtime-deps/lib/mockito-core-4.11.0.jar | Bin 0 -> 684944 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 runtime-deps/lib/mockito-core-4.11.0.jar diff --git a/runtime-deps/lib/mockito-core-4.11.0.jar b/runtime-deps/lib/mockito-core-4.11.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..46eff14529990a23addc0a578acd01275b489554 GIT binary patch literal 684944 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�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

&SkG^Mu4=w0Scn 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%0D1siQK*ZlZf}DK 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%0AK<@6rn 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�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%0Df_Bl~C{Y!nD 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

krVy}3vL*e~e%@!1Hm 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

-GK-Kdt0BY^ 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 z�~?n5-eVnyW2dndGYETQ@G#X%v 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 bYh6s7Cof29#^oHBb8oI3n$1RdpyvMo0HjY3 literal 0 HcmV?d00001 From 5fcb970a645b8db84221a4456bdf918fc1838030 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 27 Nov 2023 18:26:13 +0100 Subject: [PATCH 031/128] Fixed instanceof predicates generation --- .../kex/trace/symbolic/SymbolicTraceBuilder.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index a5e1b3c9c..cad57f27c 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -28,6 +28,7 @@ import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.collection.stackOf import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.logging.warn import org.vorpal.research.kthelper.toInt import org.vorpal.research.kthelper.`try` @@ -328,6 +329,7 @@ class SymbolicTraceBuilder( is KexClass -> type else -> this } + else -> this } @@ -1237,6 +1239,7 @@ class SymbolicTraceBuilder( val kfgValue = parseValue(value) val termValue = mkValue(kfgValue) + log.warn { "\nkfgValue: $kfgValue\ntermValue: $termValue" } val descriptorValue = concreteValue.getAsDescriptor() val realType = removeMockitoMockSuffix(descriptorValue.type.name) log.debug { "decriptorValue type: ${descriptorValue.type.name}, realType: $realType" } @@ -1246,6 +1249,8 @@ class SymbolicTraceBuilder( if (checkedType.isSubtypeOfCached(kfgType)) return@safeCall } typeChecked[termValue] = kfgType +// log.debug { "WOW. concreteValue: $concreteValue\n descriptorValue: $descriptorValue\n kfgType: $kfgType\nrealType: $realType" } +// log.debug { "CHECK: kfgType.kexType: ${kfgType.kexType}" } val predicate = path { (termValue `is` kfgType.kexType) equality true @@ -1271,7 +1276,8 @@ class SymbolicTraceBuilder( else -> { val descriptorValue = concreteValue.getAsDescriptor() val actualKfgType = descriptorValue.type.getKfgType(ctx.types) - actualKfgType.isSubtypeOfCached(expectedKfgType) +// actualKfgType.isSubtypeOfCached(expectedKfgType) + actualKfgType.isSubtypeOfCached(expectedKfgType) || actualKfgType.sameButMockito(expectedKfgType) } } if (kfgValue is NullConstant) return@safeCall @@ -1289,13 +1295,17 @@ class SymbolicTraceBuilder( stateBuilder += PathClause(PathClauseType.TYPE_CHECK, instruction, predicate) } - private fun removeMockitoMockSuffix(type: String): String = if (!type.contains("\$MockitoMock")) { + fun removeMockitoMockSuffix(type: String): String = if (!type.contains("\$MockitoMock")) { type } else { val suffixIndex = type.indexOf("\$MockitoMock") type.removeRange(suffixIndex, type.length) } + fun Type.sameButMockito( + expectedKfgType: Type + ) = removeMockitoMockSuffix(name) == expectedKfgType.name + override fun addArrayIndexConstraints( inst: String, array: String, From b702b9c9db718ded43c6f079ae5683989c4b1eba Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 6 Dec 2023 17:45:22 +0100 Subject: [PATCH 032/128] MockUtils. Work on fixing issue with $MockitoMock$ classes --- .../trace/symbolic/SymbolicTraceBuilder.kt | 10 ++-- .../research/kex/util/KfgClassLoader.kt | 12 +++-- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 3 +- .../reanimator/codegen/javagen/JavaBuilder.kt | 12 +++++ .../kex/concolic/MockConcolicLongTest.kt | 15 +++++- kex-test.ini | 2 +- .../test/concolic/mock/MockGenericsTests.java | 51 +++++++++++++++++++ ...llectionsTests.java => MockListTests.java} | 48 +++++++---------- .../kex/test/concolic/mock/MockSetTests.java | 50 ++++++++++++++++++ .../kex/test/concolic/mock/NoMock.java | 17 +++++++ 10 files changed, 175 insertions(+), 45 deletions(-) create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockGenericsTests.java rename kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/{MockCollectionsTests.java => MockListTests.java} (50%) create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockSetTests.java create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/NoMock.java diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index cad57f27c..5e34f4c11 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -1276,7 +1276,7 @@ class SymbolicTraceBuilder( else -> { val descriptorValue = concreteValue.getAsDescriptor() val actualKfgType = descriptorValue.type.getKfgType(ctx.types) -// actualKfgType.isSubtypeOfCached(expectedKfgType) + // TODO: fix "...$MockitoMock$..." classes on the terms/descriptors generation actualKfgType.isSubtypeOfCached(expectedKfgType) || actualKfgType.sameButMockito(expectedKfgType) } } @@ -1295,16 +1295,14 @@ class SymbolicTraceBuilder( stateBuilder += PathClause(PathClauseType.TYPE_CHECK, instruction, predicate) } - fun removeMockitoMockSuffix(type: String): String = if (!type.contains("\$MockitoMock")) { + fun removeMockitoMockSuffix(type: String): String = if (!type.contains("\$MockitoMock\$")) { type } else { - val suffixIndex = type.indexOf("\$MockitoMock") + val suffixIndex = type.indexOf("\$MockitoMock\$") type.removeRange(suffixIndex, type.length) } - fun Type.sameButMockito( - expectedKfgType: Type - ) = removeMockitoMockSuffix(name) == expectedKfgType.name + fun Type.sameButMockito(expectedKfgType: Type) = removeMockitoMockSuffix(name) == expectedKfgType.name override fun addArrayIndexConstraints( inst: String, diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 926ae2af2..feb1fee79 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -22,7 +22,7 @@ class KfgClassLoader( companion object { private val INCLUDES = setOf( - // TODO: Mock. Config file! + // TODO: Mock. Config file! Enough rebuilding all kex "class org.vorpal.research.kex.test.concolic.kaf.Lesson2", "class org.vorpal.research.kex.test.concolic.kaf.Lesson6", "class org.vorpal.research.kex.test.concolic.EnumConcolicTests", @@ -34,15 +34,19 @@ class KfgClassLoader( "class org.vorpal.research.kex.test.concolic.TestEnum", "class org.vorpal.research.kex.test.concolic.mock.ToMock", + "class org.vorpal.research.kex.test.concolic.mock.NoMock", "class org.vorpal.research.kex.test.concolic.mock.MockTests", "class org.vorpal.research.kex.test.concolic.mock.MockReturnsMockTests", "class org.vorpal.research.kex.test.concolic.mock.MockPrimitivesTests", "class org.vorpal.research.kex.test.concolic.mock.MockEnumTests", "class org.vorpal.research.kex.test.concolic.mock.MockWithFieldsTests", "class org.vorpal.research.kex.test.concolic.mock.MockStaticsTests", - "class org.vorpal.research.kex.test.concolic.mock.MockCollectionsTests", - "class org.vorpal.research.kex.test.concolic.mock.MockCollectionsTests\$Container", - "class org.vorpal.research.kex.test.concolic.mock.MockCollectionsTests\$NoMock", + "class org.vorpal.research.kex.test.concolic.mock.MockListTests", + "class org.vorpal.research.kex.test.concolic.mock.MockListTests\$Container", + "class org.vorpal.research.kex.test.concolic.mock.MockSetTests", + "class org.vorpal.research.kex.test.concolic.mock.MockSetTests", + "class org.vorpal.research.kex.test.concolic.mock.MockGenericsTests", + "class org.vorpal.research.kex.test.concolic.mock.MockGenericsTests\$GenericMock", // "class org.vorpal.research.kex.test.concolic.mock.MockTests\$Cont", // "class org.vorpal.research.kex.test.concolic.mock.MockTests\$WithStaticInt", diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 2c7c3cf6c..d7df6ce17 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -22,6 +22,7 @@ class ExecutorAS2JavaPrinter( private val surroundInTryCatch = kexConfig.getBooleanValue("testGen", "surroundInTryCatch", true) private val testParams = mutableListOf() private val reflectionUtils = ReflectionUtilsPrinter.reflectionUtils(packageName) + private val mockUtils = MockUtilsPrinter.mockUtils(packageName) private val printedDeclarations = hashSetOf() private val printedInsides = hashSetOf() @@ -62,7 +63,7 @@ class ExecutorAS2JavaPrinter( import("java.lang.reflect.Constructor") import("java.lang.reflect.Field") import("java.lang.reflect.Array") - import("org.mockito.Mockito") // TODO make configurable + import("org.mockito.Mockito") // TODO. Mock: make configurable importStatic("${reflectionUtils.klass.pkg}.${reflectionUtils.klass.name}.*") with(klass) { diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/JavaBuilder.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/JavaBuilder.kt index 1d017b694..0a6e1093c 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/JavaBuilder.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/JavaBuilder.kt @@ -323,6 +323,7 @@ class JavaBuilder(val pkg: String = "") { val constructors = mutableListOf() val staticInits = mutableListOf() val methods = mutableListOf() + val staticClasses = mutableListOf() data class JavaField( val name: String, @@ -369,6 +370,13 @@ class JavaBuilder(val pkg: String = "") { return funBuilder } + fun staticClass(name: String, body: JavaClass.() -> Unit): JavaClass { + val classBuilder = JavaClass(pkg, name) + classBuilder.body() + staticClasses += classBuilder + return classBuilder + } + fun method(name: String, typeArgs: List, body: JavaFunction.() -> Unit): JavaFunction { val funBuilder = JavaMethod(this, name, typeArgs) funBuilder.body() @@ -395,6 +403,10 @@ class JavaBuilder(val pkg: String = "") { methods.forEach { appendLine(it.print(level + 1)) } + staticClasses.forEach { + // TODO: Mock. Adding "static" here is crutch. Implement modifiers configuration (static, public, etc) + appendLine("static" + it.print(level + 1)) + } appendLine("};") } } diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index 7b6661c12..bf6d59c32 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -43,7 +43,18 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { } @Test - fun mockCollectionsTests() { - assertCoverage(cm[prefix + "MockCollectionsTests"], 1.0) + fun mockListTests() { + assertCoverage(cm[prefix + "MockListTests"], 1.0) + } + + @Test + fun mockGenericsTests(){ + assertCoverage(cm[prefix + "MockGenericsTests"], 1.0) + } + + @Test + fun mockSetTests(){ + // unstable test. Anything can happen + assertCoverage(cm[prefix + "MockSetTests"], 1.0, 0.5) } } diff --git a/kex-test.ini b/kex-test.ini index dbec09c0d..33efafbe2 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -90,7 +90,7 @@ slicing = false logQuery = true logFormulae = false -logSMTLib = true +logSMTLib = false simplifyFormulae = true diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockGenericsTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockGenericsTests.java new file mode 100644 index 000000000..a42475a82 --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockGenericsTests.java @@ -0,0 +1,51 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + + +@SuppressWarnings("ALL") +public class MockGenericsTests { + static abstract class GenericMock { + abstract T foo(); + } + + + void testGenericMock(GenericMock mock) { + if (mock.foo() == 42) { + if (mock.foo() == 22) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + void testGenericHardMock(GenericMock mock) { + NoMock aaa = new NoMock(); + aaa.field = 888; + if (mock.foo().foo() == 42) { + if (aaa.equals(mock.foo())) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + + void testGenericRaw(GenericMock mock) { + if (((NoMock) mock.foo()).foo() == 42) { + if (((int) mock.foo()) == 22) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } else { + AssertIntrinsics.kexAssert(true); + } + } + +} diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockCollectionsTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockListTests.java similarity index 50% rename from kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockCollectionsTests.java rename to kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockListTests.java index 7581e2807..cc19234e3 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockCollectionsTests.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockListTests.java @@ -1,43 +1,34 @@ package org.vorpal.research.kex.test.concolic.mock; import org.vorpal.research.kex.intrinsics.AssertIntrinsics; -import org.vorpal.research.kex.test.concolic.TestEnum; import java.util.*; @SuppressWarnings("ALL") -public class MockCollectionsTests { +public class MockListTests { static class Container { T elem; } - static class NoMock { - int field; - - int foo() { - return field; + // Not a List, but indicates if generics brokes + public void TestMockContainer(Container container) { + if (container.elem.foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); } } - /* - public void TestNoMock(Container container) { - if (container.elem.foo() == 42) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } - */ - public void TestMockContainer(Container container) { - if (container.elem.foo() == 42) { + public void testMockList1(ArrayList list) { + if (list.get(0).foo() == 42) { AssertIntrinsics.kexAssert(true); } else { AssertIntrinsics.kexAssert(true); } } -/* - public void testMockList(ArrayList list) { + + public void testMockList2(ArrayList list) { if (list.get(0).foo() == 42) { if (list.get(0).foo() == 33) { AssertIntrinsics.kexAssert(true); @@ -49,18 +40,13 @@ public void testMockList(ArrayList list) { } } - public void testMockSet(HashSet set) { - Iterator iterator = set.iterator(); - if (iterator.next().foo() == 35) { - if (iterator.next().foo() == -1) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - + public void testMockList3(ArrayList list) { + if (list.get(0).foo() == 42) { + if (list.get(0).foo() == 33) { + if (list.get(10).foo() == 25) { + AssertIntrinsics.kexAssert(true); + } } - } else { - AssertIntrinsics.kexAssert(true); } } -*/ } diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockSetTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockSetTests.java new file mode 100644 index 000000000..d4395ff4a --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockSetTests.java @@ -0,0 +1,50 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + +import java.util.HashSet; +import java.util.Iterator; + + +// Results are unstable +@SuppressWarnings("ALL") +public class MockSetTests { + public void testMockSetEasy(HashSet set) { + Iterator iterator = set.iterator(); + if (iterator.next().foo() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testMockSetMedium(HashSet set) { + Iterator iterator = set.iterator(); + HashSet values = new HashSet<>(); + values.add(iterator.next().foo()); + if (values.contains(35)) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + +/* // Uncomment if tests above pass + public void testNoMockSetHard(HashSet set) { + Iterator iterator = set.iterator(); + HashSet values = new HashSet<>(); + values.add(iterator.next().foo()); + values.add(iterator.next().foo()); + if (values.contains(35)) { + if (values.contains(22)) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + + } + } else { + AssertIntrinsics.kexAssert(true); + } + } +*/ +} diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/NoMock.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/NoMock.java new file mode 100644 index 000000000..c4c64ecd7 --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/NoMock.java @@ -0,0 +1,17 @@ +package org.vorpal.research.kex.test.concolic.mock; + +class NoMock { + int field; + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof NoMock)) { + return false; + } + return ((NoMock) obj).field == this.field; + } + + int foo() { + return field; + } +} From 977206c0954be74d2debe85116b53d307c1769bb Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 8 Dec 2023 13:55:59 +0100 Subject: [PATCH 033/128] Fixes usage of private classes from dependencies --- .../asm/manager/ClassInstantiationManager.kt | 17 +++++++ .../trace/symbolic/SymbolicTraceBuilder.kt | 14 +++--- .../org/vorpal/research/kex/util/kfg.kt | 45 +++++++++---------- .../concolic/InstructionConcolicChecker.kt | 10 +---- runtime-deps/modules.info | 1 + 5 files changed, 47 insertions(+), 40 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt index 3b319a9d2..112493f48 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt @@ -284,6 +284,23 @@ class ClassInstantiationDetector( override fun cleanup() {} override fun visit(klass: Class) { + /* Packages to filter + TODO: Mock. Config + + "package org.mockito.*", + "package net.bytebuddy.*", + "package org.objenesis.*" + */ + if (klass.pkg.canonicalName.let { + it.startsWith("org.mockito") + || it.startsWith("net.bytebuddy") + || it.startsWith("org.objenesis") +// || it.startsWith("org.vorpal.research.kex.intrinsics") + || it.startsWith("org.junit") + }) { +// log.debug { "Filtered instantiation of class: $klass" } + return + } if (StringClassInstantiationManagerImpl.isDirectlyInstantiable(klass, baseAccessLevel)) addInstantiableClass(klass, klass) super.visit(klass) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index 5e34f4c11..f6b3c8cc1 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -1241,7 +1241,7 @@ class SymbolicTraceBuilder( val termValue = mkValue(kfgValue) log.warn { "\nkfgValue: $kfgValue\ntermValue: $termValue" } val descriptorValue = concreteValue.getAsDescriptor() - val realType = removeMockitoMockSuffix(descriptorValue.type.name) + val realType = descriptorValue.type.name.removeMockitoMockSuffix() log.debug { "decriptorValue type: ${descriptorValue.type.name}, realType: $realType" } val kfgType = parseStringToType(cm.type, realType) if (termValue in typeChecked) { @@ -1268,7 +1268,7 @@ class SymbolicTraceBuilder( val kfgValue = parseValue(value) val termValue = mkValue(kfgValue) - val realType = removeMockitoMockSuffix(type) + val realType = type.removeMockitoMockSuffix() log.debug { "type: $type\nrealType: $realType" } val expectedKfgType = parseStringToType(cm.type, realType) val comparisonResult = when (concreteValue) { @@ -1295,14 +1295,14 @@ class SymbolicTraceBuilder( stateBuilder += PathClause(PathClauseType.TYPE_CHECK, instruction, predicate) } - fun removeMockitoMockSuffix(type: String): String = if (!type.contains("\$MockitoMock\$")) { - type + fun String.removeMockitoMockSuffix(): String = if (!contains("\$MockitoMock\$")) { + this } else { - val suffixIndex = type.indexOf("\$MockitoMock\$") - type.removeRange(suffixIndex, type.length) + val suffixIndex = indexOf("\$MockitoMock\$") + removeRange(suffixIndex, length) } - fun Type.sameButMockito(expectedKfgType: Type) = removeMockitoMockSuffix(name) == expectedKfgType.name + fun Type.sameButMockito(expectedKfgType: Type) = name.removeMockitoMockSuffix() == expectedKfgType.name override fun addArrayIndexConstraints( inst: String, diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index c2a0ac4dd..ad1884cf2 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -12,31 +12,11 @@ import org.vorpal.research.kfg.ir.value.Value import org.vorpal.research.kfg.ir.value.instruction.CmpOpcode import org.vorpal.research.kfg.ir.value.instruction.Instruction import org.vorpal.research.kfg.ir.value.instruction.InstructionBuilder -import org.vorpal.research.kfg.type.ArrayType -import org.vorpal.research.kfg.type.BoolType -import org.vorpal.research.kfg.type.ByteType -import org.vorpal.research.kfg.type.CharType -import org.vorpal.research.kfg.type.ClassType -import org.vorpal.research.kfg.type.DoubleType -import org.vorpal.research.kfg.type.FloatType -import org.vorpal.research.kfg.type.IntType -import org.vorpal.research.kfg.type.LongType -import org.vorpal.research.kfg.type.PrimitiveType -import org.vorpal.research.kfg.type.ShortType -import org.vorpal.research.kfg.type.Type -import org.vorpal.research.kfg.type.TypeFactory -import org.vorpal.research.kfg.type.boolWrapper -import org.vorpal.research.kfg.type.byteWrapper -import org.vorpal.research.kfg.type.charWrapper -import org.vorpal.research.kfg.type.doubleWrapper -import org.vorpal.research.kfg.type.floatWrapper -import org.vorpal.research.kfg.type.intWrapper -import org.vorpal.research.kfg.type.longWrapper -import org.vorpal.research.kfg.type.parseStringToType -import org.vorpal.research.kfg.type.shortWrapper +import org.vorpal.research.kfg.type.* import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.collection.LRUCache import org.vorpal.research.kthelper.compareTo +import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log val Type.javaDesc get() = this.name.javaString @@ -129,8 +109,16 @@ fun NameMapper.parseValueOrNull(valueName: String): Value? { valueName.matches(Regex("-?\\d+")) -> values.getInt(valueName.toInt()) valueName.matches(Regex("-?\\d+.\\d+f")) -> values.getFloat(valueName.toFloat()) valueName.matches(Regex("-?\\d+.\\d+")) -> values.getDouble(valueName.toDouble()) - valueName.matches(Regex("\".*\"", RegexOption.DOT_MATCHES_ALL)) -> values.getString(valueName.substring(1, valueName.lastIndex)) - valueName.matches(Regex(".*(/.*)+.class")) -> values.getClass("L${valueName.removeSuffix(".class")};") + valueName.matches(Regex("\".*\"", RegexOption.DOT_MATCHES_ALL)) -> values.getString( + valueName.substring(1, valueName.lastIndex) + ) + + valueName.matches(Regex(".*(/.*)+.class")) -> values.getClass( + "L${ + valueName.removeSuffix(".class").removeMockitoMockSuffix() + };" + ).also{log.debug{"MOCKITO_MOCK FIX. value: $it"}} + valueName == "null" -> values.nullConstant valueName == "true" -> values.trueConstant valueName == "false" -> values.falseConstant @@ -138,6 +126,13 @@ fun NameMapper.parseValueOrNull(valueName: String): Value? { } } +fun String.removeMockitoMockSuffix(): CharSequence = if (!contains("\$MockitoMock\$")) { + this +} else { + val suffixIndex = indexOf("\$MockitoMock\$") + removeRange(suffixIndex, length) +} + fun Type.getAllSubtypes(tf: TypeFactory): Set = when (this) { is ClassType -> tf.cm.getAllSubtypesOf(this.klass).mapTo(mutableSetOf()) { it.asType } is ArrayType -> this.component.getAllSubtypes(tf).mapTo(mutableSetOf()) { tf.getArrayType(it) } @@ -184,5 +179,5 @@ private object SubTypeInfoCache { fun Type.isSubtypeOfCached(other: Type): Boolean = SubTypeInfoCache.check(this, other) fun Field.isOuterThis(): Boolean { - return klass.outerClass != null && name.matches("this\\$\\d+".toRegex()) && type == klass.outerClass!!.asType + return klass.outerClass != null && name.matches("this\\$\\d+".toRegex()) && type == klass.outerClass!!.asType } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/concolic/InstructionConcolicChecker.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/concolic/InstructionConcolicChecker.kt index d3d45a190..33c567fc3 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/concolic/InstructionConcolicChecker.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/concolic/InstructionConcolicChecker.kt @@ -1,12 +1,6 @@ package org.vorpal.research.kex.asm.analysis.concolic -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.coroutines.yield +import kotlinx.coroutines.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import org.vorpal.research.kex.ExecutionContext @@ -158,7 +152,7 @@ class InstructionConcolicChecker( while (pathIterator.hasNext()) { val state = pathIterator.next() - log.debug { "Checking state: $state" } + log.debug { "Checking state: $state\n for method: $method" } log.debug { "Path:\n${state.path.asState()}" } yield() diff --git a/runtime-deps/modules.info b/runtime-deps/modules.info index 42c9fe5ea..c2024f664 100644 --- a/runtime-deps/modules.info +++ b/runtime-deps/modules.info @@ -3,6 +3,7 @@ java.base/sun.security.util=ALL-UNNAMED java.base/sun.reflect.annotation=ALL-UNNAMED java.base/java.text=ALL-UNNAMED java.base/java.lang.invoke=ALL-UNNAMED +java.base/java.module.invoke=ALL-UNNAMED java.base/jdk.internal.misc=ALL-UNNAMED java.base/sun.reflect.generics.repository=ALL-UNNAMED java.base/java.io=ALL-UNNAMED From c2085b6d0cc7c65fae5eb164043e8d7f5b59fed6 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 8 Dec 2023 14:38:05 +0100 Subject: [PATCH 034/128] MockUtilsPrinter --- .../codegen/javagen/MockUtilsPrinter.kt | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt new file mode 100644 index 000000000..7962b7fc6 --- /dev/null +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt @@ -0,0 +1,68 @@ +package org.vorpal.research.kex.reanimator.codegen.javagen + +import org.vorpal.research.kex.config.kexConfig +import org.vorpal.research.kex.util.asmString +import org.vorpal.research.kex.util.testcaseDirectory +import java.nio.file.Path + +class MockUtilsPrinter( + val packageName: String +) { + private val builder = JavaBuilder(packageName) + val klass = builder.run { klass(packageName, MOCK_UTILS_CLASS) } + val initMockito: JavaBuilder.JavaFunction +// val innerClass = builder.run { klass(packageName, MOCK_INIT_TEST_CLASS) } + +// val newPrimitiveArrayMap = mutableMapOf() + + companion object { + const val MOCK_UTILS_CLASS = "MockUtils" + + const val MOCK_INIT_TEST_CLASS = "InitTest" + private val mockUtilsInstances = mutableMapOf, MockUtilsPrinter>() + fun mockUtils(packageName: String): MockUtilsPrinter { + val testDirectory = kexConfig.testcaseDirectory + return mockUtilsInstances.getOrPut(testDirectory to packageName) { + val utils = MockUtilsPrinter(packageName) + val targetFileName = "MockUtils.java" + val targetFile = testDirectory + .resolve(packageName.asmString) + .resolve(targetFileName) + .toAbsolutePath() + .toFile().also { it.parentFile?.mkdirs() } + targetFile.writeText(utils.builder.toString()) + utils + } + } + + @Suppress("unused") + fun invalidateAll() { + mockUtilsInstances.clear() + } + } + + init { + with(builder) { + import("org.mockito.Mockito") + + with(klass) { + initMockito = method("mockitoInitTest") { + +"Object mock = Mockito.mock(Object.class)" + +"assert (mock.hashCode() == 0 || mock.hashCode() != 0)" + }.apply { + returnType = void + annotations += "Test" + } + staticClass(MOCK_INIT_TEST_CLASS) { + method("mockitoInitTest") { + +"Object mock = Mockito.mock(Object.class)" + +"assert (mock.hashCode() == 0 || mock.hashCode() != 0)" + }.apply { + returnType = void; + annotations += "Test" + } + } + } + } + } +} From 8a8eacc029fc65a47581b04f615c7246ee50e7b6 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 6 Jan 2024 23:56:15 +0100 Subject: [PATCH 035/128] Removed method:line information from logs --- kex-core/src/test/resources/logback-test.xml | 6 +++--- kex-executor/src/main/resources/logback.xml | 4 ++-- kex-runner/src/main/resources/logback.xml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/kex-core/src/test/resources/logback-test.xml b/kex-core/src/test/resources/logback-test.xml index 5bcfb75d6..1e17139b8 100644 --- a/kex-core/src/test/resources/logback-test.xml +++ b/kex-core/src/test/resources/logback-test.xml @@ -3,7 +3,7 @@ - %-5p [%t][%method:%line] - %m%n + %-5p [%t] - %m%n @@ -15,7 +15,7 @@ DENY - %-5p [%t][%method:%line] - %m%n + %-5p [%t] - %m%n @@ -29,7 +29,7 @@ ${kex-run-id} true - %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c][%method:%line] - %m%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c] - %m%n diff --git a/kex-executor/src/main/resources/logback.xml b/kex-executor/src/main/resources/logback.xml index 31c9f2345..24e134037 100644 --- a/kex-executor/src/main/resources/logback.xml +++ b/kex-executor/src/main/resources/logback.xml @@ -3,7 +3,7 @@ - [%-5p][%method:%line] - %m%n + [%-5p] - %m%n @@ -16,7 +16,7 @@ ${kex-run-id} true - %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c][%method:%line] - %m%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c] - %m%n diff --git a/kex-runner/src/main/resources/logback.xml b/kex-runner/src/main/resources/logback.xml index 9bcc14bc3..d81d55719 100644 --- a/kex-runner/src/main/resources/logback.xml +++ b/kex-runner/src/main/resources/logback.xml @@ -4,7 +4,7 @@ - [%-5p][%method:%line] - %m%n + [%-5p] - %m%n @@ -17,7 +17,7 @@ ${kex-run-id} false - %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c][%method:%line] - %m%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5p][%c] - %m%n From 6aa4c6db1fb39a82858c52f18302279bd58f86c3 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 7 Jan 2024 03:39:16 +0100 Subject: [PATCH 036/128] Trying to convert mockito-created classes to original interfaces in more places --- .../trace/symbolic/SymbolicTraceBuilder.kt | 21 ++++++++----------- .../org/vorpal/research/kex/util/kfg.kt | 20 ++++++++++++------ .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 3 --- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index 75b50b922..389daab0d 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -177,7 +177,13 @@ class SymbolicTraceBuilder( override fun toString() = "$clauses" - private fun String.toType() = parseDescOrNull(cm.type, this)!! + private fun String.toType(): Type = parseDescOrNull(cm.type, this)!!.let { + if (it.name.contains(filteredSubstring)) { + it.name.removeMockitoMockSuffix().toType().also { log.debug { "Prevented mockito mock type: $it" } } + } else { + it + } + } private fun safeCall(body: () -> Unit) = `try` { body() @@ -1248,7 +1254,7 @@ class SymbolicTraceBuilder( val kfgType = parseStringToType(cm.type, realType) if (termValue in typeChecked) { val checkedType = typeChecked.getValue(termValue) - if (checkedType.isSubtypeOfCached(kfgType)) return@safeCall + if (checkedType.isSubtypeOfCached(kfgType) || checkedType.sameButMockito(kfgType)) return@safeCall } typeChecked[termValue] = kfgType // log.debug { "WOW. concreteValue: $concreteValue\n descriptorValue: $descriptorValue\n kfgType: $kfgType\nrealType: $realType" } @@ -1285,7 +1291,7 @@ class SymbolicTraceBuilder( if (kfgValue is NullConstant) return@safeCall if (termValue in typeChecked) { val checkedType = typeChecked.getValue(termValue) - if (checkedType.isSubtypeOfCached(expectedKfgType)) return@safeCall + if (checkedType.isSubtypeOfCached(expectedKfgType) || checkedType.sameButMockito(expectedKfgType)) return@safeCall } typeChecked[termValue] = expectedKfgType @@ -1297,15 +1303,6 @@ class SymbolicTraceBuilder( stateBuilder += PathClause(PathClauseType.TYPE_CHECK, instruction, predicate) } - fun String.removeMockitoMockSuffix(): String = if (!contains("\$MockitoMock\$")) { - this - } else { - val suffixIndex = indexOf("\$MockitoMock\$") - removeRange(suffixIndex, length) - } - - fun Type.sameButMockito(expectedKfgType: Type) = name.removeMockitoMockSuffix() == expectedKfgType.name - override fun addArrayIndexConstraints( inst: String, array: String, diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index ad1884cf2..72402ec4a 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -126,11 +126,16 @@ fun NameMapper.parseValueOrNull(valueName: String): Value? { } } -fun String.removeMockitoMockSuffix(): CharSequence = if (!contains("\$MockitoMock\$")) { - this -} else { - val suffixIndex = indexOf("\$MockitoMock\$") - removeRange(suffixIndex, length) +const val filteredSubstring = "\$MockitoMock\$" + +fun String.removeMockitoMockSuffix(): String { + return if (!contains(filteredSubstring)) { + this + } else { + log.debug { "MOCKITO_MOCK FIX. value: $this" } + val suffixIndex = indexOf(filteredSubstring) + removeRange(suffixIndex, length) + } } fun Type.getAllSubtypes(tf: TypeFactory): Set = when (this) { @@ -170,12 +175,15 @@ private object SubTypeInfoCache { private val subtypeCache = LRUCache, Boolean>(100_000U) fun check(lhv: Type, rhv: Type): Boolean { val key = lhv.toString() to rhv.toString() - return subtypeCache[key] ?: lhv.isSubtypeOf(rhv, outerClassBehavior = false).also { + return subtypeCache[key] ?: (lhv.isSubtypeOf(rhv, outerClassBehavior = false) || lhv.sameButMockito(rhv)).also { subtypeCache[key] = it } } } +fun Type.sameButMockito(expectedKfgType: Type) = + name.removeMockitoMockSuffix() == expectedKfgType.name.removeMockitoMockSuffix() + fun Type.isSubtypeOfCached(other: Type): Boolean = SubTypeInfoCache.check(this, other) fun Field.isOuterThis(): Boolean { diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index d7df6ce17..8ebf0d709 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -9,7 +9,6 @@ import org.vorpal.research.kfg.type.* import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.error import org.vorpal.research.kthelper.logging.log -import org.vorpal.research.kthelper.logging.warn import org.vorpal.research.kthelper.runIf @@ -265,7 +264,6 @@ class ExecutorAS2JavaPrinter( is ReflectionList -> printReflectionListDeclarations(owner, result) is MockSequence -> printMockSequenceDeclarations(owner, result) else -> { - log.warn { "Printing declarations for unsupported action sequence may break test. Unsupported AS: $owner" } owner.printAsJava() } } @@ -314,7 +312,6 @@ class ExecutorAS2JavaPrinter( is ReflectionList -> printReflectionListInsides(owner, result) is MockSequence -> printMockSequenceInsides(owner, result) else -> { - log.warn { "Printing insides for unsupported action sequence may break test. Unsupported AS: $owner" } owner.printAsJava() } } From b7df3f32b7f8e46b14d58304dd81dcf99d7d823c Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 8 Jan 2024 12:56:00 +0100 Subject: [PATCH 037/128] Logging information of creating mock descriptors --- .../kotlin/org/vorpal/research/kex/parameters/Parameters.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index d417fc489..cfba0350d 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -15,6 +15,7 @@ import org.vorpal.research.kex.util.KfgTargetFilter import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory +import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.error import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn @@ -156,7 +157,7 @@ private fun Descriptor.replaceWithMock( } else { log.warn { "Strange descriptor to mock. Expected ObjectDescriptor. Got: $descriptor" } MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods) - } + }.also { log.debug { "Created mock descriptor for ${it.term}" } } visited.add(descriptor) descriptorToMock[descriptor] = mock mock.insertMocks(types, descriptorToMock, visited) @@ -205,6 +206,7 @@ fun setupMocks( for ((callPredicate, value) in methodCalls) { val call = callPredicate.call as CallTerm val mock = termToDescriptor[call.owner]?.let { descriptorToMock[it] ?: it } + mock ?: log.warn { "No mock for $call" } if (mock is MockDescriptor) { mock.addReturnValue(call.method, value) } From 261d11ba19f920634cf063f19cb3a64ef01ab0b1 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 9 Jan 2024 18:21:12 +0100 Subject: [PATCH 038/128] Removed duplicated tests from MockTests --- .../kex/test/concolic/mock/MockTests.java | 73 ------------------- 1 file changed, 73 deletions(-) diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockTests.java index 0dffa07dd..a1c8428c5 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockTests.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockTests.java @@ -68,77 +68,4 @@ public void testMockDifferentMethods(ToMock a) { } } } - - - static class WithStaticMock { - static ToMock mock; - } - - public void testMockStatic() { - if (WithStaticMock.mock.foo() == 42) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } - - static abstract class WithStaticInt { - static int staticInt; - - public abstract int foo(); - } - - public void testMockHasStaticField() { - if (WithStaticInt.staticInt == 42) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - } - } - - public void testMockHasStaticField(WithStaticInt mock) { - if (WithStaticInt.staticInt == 42) { - if (mock.foo() == 11) { - AssertIntrinsics.kexAssert(true); - } else { - AssertIntrinsics.kexAssert(true); - - } - } else { - AssertIntrinsics.kexAssert(true); - } - } - - static abstract class RecursionWithField { - RecursionWithField stRec; - - abstract RecursionWithField fun(); - } - - public void testMockStaticRecursion(RecursionWithField argMock) { - RecursionWithField a = argMock.fun(); - if (a.fun() == a.stRec) { - if (a.stRec.fun() == a) { - AssertIntrinsics.kexAssert(true); - } - } - } - - static abstract class RecursionWithStaticField { - - static abstract class StaticWithMockField { - static RecursionWithStaticField stRec; - } - - abstract RecursionWithStaticField fun(); - } - - public void testMockStaticFieldRecursion(RecursionWithStaticField argMock) { - RecursionWithStaticField a = argMock.fun(); - if (a.fun() == RecursionWithStaticField.StaticWithMockField.stRec) { - if (RecursionWithStaticField.StaticWithMockField.stRec.fun() == a) { - AssertIntrinsics.kexAssert(true); - } - } - } } From aad09c28bf7920820295b65662a8a2ba4f4355ee Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 9 Jan 2024 20:37:50 +0100 Subject: [PATCH 039/128] Avoid collecting mock setup information if no mocks created --- .../org/vorpal/research/kex/asm/analysis/util/extensions.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 24a2ea2d4..ca7306868 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -99,8 +99,10 @@ suspend fun Method.checkAsync( ) val (withMocks, descriptorToMock) = initialDescriptors.generateInitialMocks(ctx.types) - val methodCalls = methodCalls(state, termToDescriptor, descriptorToMock) - setupMocks(methodCalls, termToDescriptor, descriptorToMock) + if (descriptorToMock.isNotEmpty()) { + val methodCalls = methodCalls(state, termToDescriptor, descriptorToMock) + setupMocks(methodCalls, termToDescriptor, descriptorToMock) + } withMocks From 128655d22bd4040d78e876319ebb12d7bfb57adb Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 10 Jan 2024 10:34:55 +0100 Subject: [PATCH 040/128] Remove unnecessary code --- .../trace/symbolic/SymbolicTraceBuilder.kt | 78 +++---------------- .../org/vorpal/research/kex/util/kfg.kt | 15 ++-- 2 files changed, 20 insertions(+), 73 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index 895d6d027..04b867942 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -6,72 +6,18 @@ import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.manager.MethodManager import org.vorpal.research.kex.asm.state.asTermExpr import org.vorpal.research.kex.asm.transform.SymbolicTraceInstrumenter -import org.vorpal.research.kex.descriptor.ConstantDescriptor -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.Object2DescriptorConverter -import org.vorpal.research.kex.descriptor.ObjectDescriptor -import org.vorpal.research.kex.descriptor.descriptor -import org.vorpal.research.kex.ktype.KexBool -import org.vorpal.research.kex.ktype.KexByte -import org.vorpal.research.kex.ktype.KexChar -import org.vorpal.research.kex.ktype.KexClass -import org.vorpal.research.kex.ktype.KexDouble -import org.vorpal.research.kex.ktype.KexFloat -import org.vorpal.research.kex.ktype.KexInt -import org.vorpal.research.kex.ktype.KexInteger -import org.vorpal.research.kex.ktype.KexLong -import org.vorpal.research.kex.ktype.KexReal -import org.vorpal.research.kex.ktype.KexShort -import org.vorpal.research.kex.ktype.KexType -import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.descriptor.* +import org.vorpal.research.kex.ktype.* import org.vorpal.research.kex.parameters.Parameters import org.vorpal.research.kex.state.predicate.Predicate import org.vorpal.research.kex.state.predicate.path import org.vorpal.research.kex.state.predicate.state -import org.vorpal.research.kex.state.term.ConstClassTerm -import org.vorpal.research.kex.state.term.ConstStringTerm -import org.vorpal.research.kex.state.term.NullTerm -import org.vorpal.research.kex.state.term.StaticClassRefTerm -import org.vorpal.research.kex.state.term.Term -import org.vorpal.research.kex.state.term.term +import org.vorpal.research.kex.state.term.* import org.vorpal.research.kex.state.transformer.TermRenamer import org.vorpal.research.kex.util.* -import org.vorpal.research.kfg.ir.BasicBlock -import org.vorpal.research.kfg.ir.Class -import org.vorpal.research.kfg.ir.ConcreteClass -import org.vorpal.research.kfg.ir.Method -import org.vorpal.research.kfg.ir.MethodDescriptor -import org.vorpal.research.kfg.ir.value.Argument -import org.vorpal.research.kfg.ir.value.Constant -import org.vorpal.research.kfg.ir.value.NameMapperContext -import org.vorpal.research.kfg.ir.value.NullConstant -import org.vorpal.research.kfg.ir.value.ThisRef -import org.vorpal.research.kfg.ir.value.Value -import org.vorpal.research.kfg.ir.value.instruction.ArrayLoadInst -import org.vorpal.research.kfg.ir.value.instruction.ArrayStoreInst -import org.vorpal.research.kfg.ir.value.instruction.BinaryInst -import org.vorpal.research.kfg.ir.value.instruction.BranchInst -import org.vorpal.research.kfg.ir.value.instruction.CallInst -import org.vorpal.research.kfg.ir.value.instruction.CastInst -import org.vorpal.research.kfg.ir.value.instruction.CatchInst -import org.vorpal.research.kfg.ir.value.instruction.CmpInst -import org.vorpal.research.kfg.ir.value.instruction.EnterMonitorInst -import org.vorpal.research.kfg.ir.value.instruction.ExitMonitorInst -import org.vorpal.research.kfg.ir.value.instruction.FieldLoadInst -import org.vorpal.research.kfg.ir.value.instruction.FieldStoreInst -import org.vorpal.research.kfg.ir.value.instruction.Handle -import org.vorpal.research.kfg.ir.value.instruction.InstanceOfInst -import org.vorpal.research.kfg.ir.value.instruction.Instruction -import org.vorpal.research.kfg.ir.value.instruction.InvokeDynamicInst -import org.vorpal.research.kfg.ir.value.instruction.JumpInst -import org.vorpal.research.kfg.ir.value.instruction.NewArrayInst -import org.vorpal.research.kfg.ir.value.instruction.NewInst -import org.vorpal.research.kfg.ir.value.instruction.PhiInst -import org.vorpal.research.kfg.ir.value.instruction.ReturnInst -import org.vorpal.research.kfg.ir.value.instruction.SwitchInst -import org.vorpal.research.kfg.ir.value.instruction.TableSwitchInst -import org.vorpal.research.kfg.ir.value.instruction.ThrowInst -import org.vorpal.research.kfg.ir.value.instruction.UnaryInst +import org.vorpal.research.kfg.ir.* +import org.vorpal.research.kfg.ir.value.* +import org.vorpal.research.kfg.ir.value.instruction.* import org.vorpal.research.kfg.type.SystemTypeNames import org.vorpal.research.kfg.type.Type import org.vorpal.research.kfg.type.parseDescOrNull @@ -82,7 +28,6 @@ import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.collection.stackOf import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log -import org.vorpal.research.kthelper.logging.warn import org.vorpal.research.kthelper.toInt import org.vorpal.research.kthelper.`try` @@ -232,7 +177,7 @@ class SymbolicTraceBuilder( override fun toString() = "$clauses" private fun String.toType(): Type = parseDescOrNull(cm.type, this)!!.let { - if (it.name.contains(filteredSubstring)) { + if (it.name.contains(SUBSTRING_TO_FILTER_FROM_TYPE)) { it.name.removeMockitoMockSuffix().toType().also { log.debug { "Prevented mockito mock type: $it" } } } else { it @@ -1304,7 +1249,7 @@ class SymbolicTraceBuilder( val kfgType = concreteType.getKfgType(ctx.types) if (termValue in typeChecked) { val checkedType = typeChecked.getValue(termValue) - if (checkedType.isSubtypeOfCached(kfgType) || checkedType.sameButMockito(kfgType)) return@safeCall + if (checkedType.isSubtypeOfCached(kfgType)) return@safeCall } typeChecked[termValue] = kfgType @@ -1324,7 +1269,8 @@ class SymbolicTraceBuilder( val kfgValue = parseValue(value) val termValue = mkValue(kfgValue) - val realType = type.removeMockitoMockSuffix() +// val realType = type.removeMockitoMockSuffix() + val realType = type log.debug { "type: $type\nrealType: $realType" } val expectedKfgType = parseStringToType(cm.type, realType) val comparisonResult = when (concreteValue) { @@ -1332,13 +1278,13 @@ class SymbolicTraceBuilder( else -> { val actualKfgType = concreteValue.getConcreteType(termValue.type).getKfgType(ctx.types) // TODO: fix "...$MockitoMock$..." classes on the terms/descriptors generation - actualKfgType.isSubtypeOfCached(expectedKfgType) || actualKfgType.sameButMockito(expectedKfgType) + actualKfgType.isSubtypeOfCached(expectedKfgType) } } if (kfgValue is NullConstant) return@safeCall if (termValue in typeChecked) { val checkedType = typeChecked.getValue(termValue) - if (checkedType.isSubtypeOfCached(expectedKfgType) || checkedType.sameButMockito(expectedKfgType)) return@safeCall + if (checkedType.isSubtypeOfCached(expectedKfgType)) return@safeCall } typeChecked[termValue] = expectedKfgType diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index 72402ec4a..69cabb164 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -117,7 +117,7 @@ fun NameMapper.parseValueOrNull(valueName: String): Value? { "L${ valueName.removeSuffix(".class").removeMockitoMockSuffix() };" - ).also{log.debug{"MOCKITO_MOCK FIX. value: $it"}} + ) valueName == "null" -> values.nullConstant valueName == "true" -> values.trueConstant @@ -126,14 +126,15 @@ fun NameMapper.parseValueOrNull(valueName: String): Value? { } } -const val filteredSubstring = "\$MockitoMock\$" +const val SUBSTRING_TO_FILTER_FROM_TYPE = "\$MockitoMock\$" fun String.removeMockitoMockSuffix(): String { - return if (!contains(filteredSubstring)) { + val suffixIndex = lastIndexOf(SUBSTRING_TO_FILTER_FROM_TYPE) + return if (suffixIndex == -1) { this } else { - log.debug { "MOCKITO_MOCK FIX. value: $this" } - val suffixIndex = indexOf(filteredSubstring) + log.debug { "MOCKITO_MOCK FIX. value: $this. Stack trace:" } +// log.debug(Thread.currentThread().stackTrace.joinToString("\n")) removeRange(suffixIndex, length) } } @@ -146,7 +147,7 @@ fun Type.getAllSubtypes(tf: TypeFactory): Set = when (this) { fun parseAsConcreteType(typeFactory: TypeFactory, name: String): KexType? { - val type = parseStringToType(typeFactory, name) + val type = parseStringToType(typeFactory, name.removeMockitoMockSuffix()) return when { type.isConcrete -> type.kexType else -> null @@ -181,7 +182,7 @@ private object SubTypeInfoCache { } } -fun Type.sameButMockito(expectedKfgType: Type) = +private fun Type.sameButMockito(expectedKfgType: Type) = name.removeMockitoMockSuffix() == expectedKfgType.name.removeMockitoMockSuffix() fun Type.isSubtypeOfCached(other: Type): Boolean = SubTypeInfoCache.check(this, other) From 5a82cf4a184cb4563d2a3f69986123f5009bdeeb Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 10 Jan 2024 16:38:04 +0100 Subject: [PATCH 041/128] Excluding classes from InstantiationManager in config --- .../asm/manager/ClassInstantiationManager.kt | 28 +++++++++---------- kex-test.ini | 4 +++ kex.ini | 4 +++ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt index e972fcf4a..1e899c45e 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt @@ -3,11 +3,13 @@ package org.vorpal.research.kex.asm.manager import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.util.AccessModifier import org.vorpal.research.kex.asm.util.accessModifier +import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexReference import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.util.KfgTargetFilter import org.vorpal.research.kex.util.isSubtypeOfCached import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.Class @@ -287,6 +289,15 @@ private object StringClassInstantiationManagerImpl : ClassInstantiationManager { } } + +private val ignoredInstantiations: Set by lazy { + kexConfig.getMultipleStringValue("testGen", "ignoreInstantiation").flatMapTo(mutableSetOf()) { + val filter = KfgTargetFilter.parse(it) + listOf(filter, filter.rtMapped) + } +} + + class ClassInstantiationDetector( private val ctx: ExecutionContext, private val baseAccessLevel: AccessModifier = AccessModifier.Private @@ -296,23 +307,10 @@ class ClassInstantiationDetector( override fun cleanup() {} override fun visit(klass: Class) { - /* Packages to filter - TODO: Mock. Config - - "package org.mockito.*", - "package net.bytebuddy.*", - "package org.objenesis.*" - */ - if (klass.pkg.canonicalName.let { - it.startsWith("org.mockito") - || it.startsWith("net.bytebuddy") - || it.startsWith("org.objenesis") -// || it.startsWith("org.vorpal.research.kex.intrinsics") - || it.startsWith("org.junit") - }) { -// log.debug { "Filtered instantiation of class: $klass" } + if (ignoredInstantiations.any { it.matches(klass.asType.kexType.toString()) }) { return } + if (StringClassInstantiationManagerImpl.isDirectlyInstantiable(klass, baseAccessLevel)) addInstantiableClass(klass, klass) super.visit(klass) diff --git a/kex-test.ini b/kex-test.ini index 651e8d62c..2f1af57dd 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -21,6 +21,10 @@ generateSetup = true logJUnit = true surroundInTryCatch = false +ignoreInstantiation = package org.junit.* +ignoreInstantiation = package org.objenesis.* +ignoreInstantiation = package net.bytebuddy.* + [reanimator] enabled = true maxStackSize = 5 diff --git a/kex.ini b/kex.ini index 5e8b1cee2..e11be3d31 100644 --- a/kex.ini +++ b/kex.ini @@ -34,6 +34,10 @@ ignoreStatic = class kex.java.util.Arrays ignoreStatic = package java.time.* ignoreStatic = package org.slf4j.* +ignoreInstantiation = package org.junit.* +ignoreInstantiation = package org.objenesis.* +ignoreInstantiation = package net.bytebuddy.* + maxArrayLength = 1000 [reanimator] From ef6b8b02105e6696b35699f91f8907d38b2767da Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 10 Jan 2024 18:10:23 +0100 Subject: [PATCH 042/128] Increased heap size in kex.py, because mocks cause more memory usage --- kex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kex.py b/kex.py index 8320fb316..25972c75a 100755 --- a/kex.py +++ b/kex.py @@ -7,7 +7,7 @@ import os.path KEX_VERSION = "0.0.1" -HEAP_MEMORY_SIZE = "8g" +HEAP_MEMORY_SIZE = "12g" STACK_MEMORY_SIZE = "1g" MODULES_FILE = os.path.join("runtime-deps", "modules.info") From 60e07ce56a21c477b75204ba4152343fdb180b0b Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 11 Jan 2024 13:34:02 +0100 Subject: [PATCH 043/128] Add comment about potential low coverage in MockListTests --- .../vorpal/research/kex/test/concolic/mock/MockListTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockListTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockListTests.java index cc19234e3..7ea2ef7aa 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockListTests.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockListTests.java @@ -40,6 +40,7 @@ public void testMockList2(ArrayList list) { } } + // coverage may be below 100% public void testMockList3(ArrayList list) { if (list.get(0).foo() == 42) { if (list.get(0).foo() == 33) { From 16c371b097eeb419092a27cbf0d067f566bcbe5d Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 12 Jan 2024 00:00:28 +0100 Subject: [PATCH 044/128] Preparing to generate additional descriptors only if mocks are required for this test --- .../research/kex/parameters/Parameters.kt | 2 +- .../kex/asm/analysis/util/extensions.kt | 37 +++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index cfba0350d..56d7dbb0e 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -134,7 +134,7 @@ private fun Descriptor.insertMocks( } } -private fun Descriptor.isMockable(types: TypeFactory): Boolean { +fun Descriptor.isMockable(types: TypeFactory): Boolean { val klass = (type.getKfgType(types) as? ClassType)?.klass return klass != null && !instantiationManager.isInstantiable(klass) && !type.isKexRt && this is ObjectDescriptor } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index ca7306868..8a67cb0a2 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -6,7 +6,7 @@ import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.analysis.crash.precondition.ConstraintExceptionPrecondition import org.vorpal.research.kex.asm.manager.MethodManager import org.vorpal.research.kex.asm.util.AccessModifier -import org.vorpal.research.kex.descriptor.Descriptor +import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType @@ -23,6 +23,7 @@ import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState import org.vorpal.research.kfg.ir.Method +import org.vorpal.research.kfg.type.TypeFactory import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn @@ -72,6 +73,23 @@ fun methodCalls( .toList() } + +fun Descriptor.requireMocks(visited: MutableSet, types: TypeFactory): Boolean { + if (this in visited) return false + if (isMockable(types)) return true + visited += this + + fun Descriptor.requireMocks(): Boolean = this.requireMocks(visited, types) + return when (this) { + is ConstantDescriptor -> false + + is MockDescriptor -> fields.values.any { it.requireMocks() } || allReturns.any { it.requireMocks() } + is ClassDescriptor -> fields.values.any { it.requireMocks() } + is ObjectDescriptor -> fields.values.any { it.requireMocks() } + is ArrayDescriptor -> elements.values.any { it.requireMocks() } + } +} + suspend fun Method.checkAsync( ctx: ExecutionContext, state: SymbolicState, @@ -98,14 +116,19 @@ suspend fun Method.checkAsync( checker.state ) - val (withMocks, descriptorToMock) = initialDescriptors.generateInitialMocks(ctx.types) - if (descriptorToMock.isNotEmpty()) { - val methodCalls = methodCalls(state, termToDescriptor, descriptorToMock) - setupMocks(methodCalls, termToDescriptor, descriptorToMock) + val visited = mutableSetOf() + val finalDescriptors = if (initialDescriptors.asList.any { it.requireMocks(visited, ctx.types) }) { + val (withMocks, descriptorToMock) = initialDescriptors.generateInitialMocks(ctx.types) + if (descriptorToMock.isNotEmpty()) { + val methodCalls = methodCalls(state, termToDescriptor, descriptorToMock) + setupMocks(methodCalls, termToDescriptor, descriptorToMock) + } + withMocks + } else { + initialDescriptors } - - withMocks + finalDescriptors .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } } From e6a0abcb1f2bd72d45c3f990d10118f9806f4849 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 12 Jan 2024 11:46:08 +0100 Subject: [PATCH 045/128] Generating additional descriptors only if mocks appears --- .../kex/asm/analysis/util/extensions.kt | 19 +++++++------------ .../state/transformer/DescriptorGenerator.kt | 6 +++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 8a67cb0a2..aaf831479 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -17,7 +17,6 @@ import org.vorpal.research.kex.smt.Result import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery import org.vorpal.research.kex.state.predicate.CallPredicate -import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* @@ -46,9 +45,6 @@ suspend fun Method.analyzeOrTimeout( } } -fun CallTerm.replaceOwner(owner: Term): CallTerm { - return CallTerm(type, owner, method, arguments) -} fun methodCalls( state: SymbolicState, @@ -109,20 +105,19 @@ suspend fun Method.checkAsync( return try { - val (initialDescriptors, termToDescriptor) = generateInitialDescriptors( + val (initialDescriptors, generator) = generateInitialDescriptors( this, ctx, result.model, checker.state ) - val visited = mutableSetOf() - val finalDescriptors = if (initialDescriptors.asList.any { it.requireMocks(visited, ctx.types) }) { - val (withMocks, descriptorToMock) = initialDescriptors.generateInitialMocks(ctx.types) - if (descriptorToMock.isNotEmpty()) { - val methodCalls = methodCalls(state, termToDescriptor, descriptorToMock) - setupMocks(methodCalls, termToDescriptor, descriptorToMock) - } + val finalDescriptors = if (initialDescriptors.asList.any { it.isMockable(ctx.types) }) { + generator.generateAll() + val descriptors = with(initialDescriptors) { Parameters(instance, arguments, statics, generator.allValues) } + val (withMocks, descriptorToMock) = descriptors.generateInitialMocks(ctx.types) + val methodCalls = methodCalls(state, generator.memory, descriptorToMock) + setupMocks(methodCalls, generator.memory, descriptorToMock) withMocks } else { initialDescriptors diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index d44cf8882..6ac77930f 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -125,10 +125,10 @@ fun generateInitialDescriptors( ctx: ExecutionContext, model: SMTModel, state: PredicateState -): Pair, Map> { +): Pair, DescriptorGenerator> { val generator = DescriptorGenerator(method, ctx, model, InitialDescriptorReanimator(model, ctx)) generator.apply(state) - generator.generateAll() +// generator.generateAll() return Parameters( generator.instance, generator.args.mapIndexed { index, arg -> @@ -136,7 +136,7 @@ fun generateInitialDescriptors( }, generator.staticFields, generator.allValues - ) to generator.memory + ) to generator } From 9f5503433202b6711c0ca079d41a4731d44a07a4 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 12:48:38 +0100 Subject: [PATCH 046/128] Missing include --- .../research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt index 7962b7fc6..3444480e3 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt @@ -44,6 +44,7 @@ class MockUtilsPrinter( init { with(builder) { import("org.mockito.Mockito") + import ("org.junit.Test") with(klass) { initMockito = method("mockitoInitTest") { From 84643717bce4c583c3bfb3f0afbaa744d4e20ac6 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 13:13:50 +0100 Subject: [PATCH 047/128] TODO for timeout --- .../research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt index 3444480e3..295261e39 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt @@ -52,7 +52,7 @@ class MockUtilsPrinter( +"assert (mock.hashCode() == 0 || mock.hashCode() != 0)" }.apply { returnType = void - annotations += "Test" + annotations += "Test" // TODO timeout } staticClass(MOCK_INIT_TEST_CLASS) { method("mockitoInitTest") { From 8e0d518f4e51c3ee0861ab3c9d534d26e711f933 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 20:16:48 +0100 Subject: [PATCH 048/128] mock section in config. Some settings --- .../src/main/kotlin/org/vorpal/research/kex/util/rt.kt | 6 ++++-- kex.ini | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index dad7c7151..64a77a3f7 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -73,8 +73,10 @@ fun getJunit(): Container? { fun getMockito(): Container? { val libPath = kexConfig.libPath ?: return null - val mockitoVersion = "4.11.0" // TODO: Mock. Make configurable - return JarContainer(libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath(), Package("org.mockito")) + val mockitoVersion = kexConfig.getStringValue("mock", "mockitoVersion") ?: return null + print(mockitoVersion) + val mockitoPath = libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath() + return JarContainer(mockitoPath, Package("org.mockito")) } fun getMockitoInline(): Container? { diff --git a/kex.ini b/kex.ini index b73c6e68b..66edd1a49 100644 --- a/kex.ini +++ b/kex.ini @@ -34,12 +34,19 @@ ignoreStatic = class kex.java.util.Arrays ignoreStatic = package java.time.* ignoreStatic = package org.slf4j.* +; classes won't appear in instantiationManager ignoreInstantiation = package org.junit.* ignoreInstantiation = package org.objenesis.* ignoreInstantiation = package net.bytebuddy.* maxArrayLength = 1000 +[mock] +enabled = true +mockitoVersion = 4.11.0 +mode = basic +; full to mock as much as possible, basic to mock only when can't create otherwise + [reanimator] enabled = true maxStackSize = 5 From acb62f5e02f8adc2ab235dfdb0d42b899374b607 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 20:17:01 +0100 Subject: [PATCH 049/128] MockUtilsPrinter fixes --- .../javagen/ActionSequence2JavaPrinter.kt | 2 +- .../codegen/javagen/MockUtilsPrinter.kt | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt index 86f3ccad7..c2b0a8783 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt @@ -18,7 +18,7 @@ import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.tryOrNull import java.lang.reflect.* -private val testTimeout by lazy { +val testTimeout by lazy { kexConfig.getIntValue("testGen", "testTimeout", 10) } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt index 295261e39..e564c30f4 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt @@ -1,5 +1,6 @@ package org.vorpal.research.kex.reanimator.codegen.javagen +import org.vorpal.research.kex.asm.util.Visibility import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.util.asmString import org.vorpal.research.kex.util.testcaseDirectory @@ -10,15 +11,14 @@ class MockUtilsPrinter( ) { private val builder = JavaBuilder(packageName) val klass = builder.run { klass(packageName, MOCK_UTILS_CLASS) } - val initMockito: JavaBuilder.JavaFunction // val innerClass = builder.run { klass(packageName, MOCK_INIT_TEST_CLASS) } // val newPrimitiveArrayMap = mutableMapOf() companion object { const val MOCK_UTILS_CLASS = "MockUtils" - const val MOCK_INIT_TEST_CLASS = "InitTest" + private val mockUtilsInstances = mutableMapOf, MockUtilsPrinter>() fun mockUtils(packageName: String): MockUtilsPrinter { val testDirectory = kexConfig.testcaseDirectory @@ -47,14 +47,12 @@ class MockUtilsPrinter( import ("org.junit.Test") with(klass) { - initMockito = method("mockitoInitTest") { - +"Object mock = Mockito.mock(Object.class)" - +"assert (mock.hashCode() == 0 || mock.hashCode() != 0)" - }.apply { - returnType = void - annotations += "Test" // TODO timeout - } staticClass(MOCK_INIT_TEST_CLASS) { + field("globalTimeout", type("Timeout")) { + visibility = Visibility.PUBLIC + initializer = "new Timeout($testTimeout, TimeUnit.SECONDS)" + annotations += "Rule" + } method("mockitoInitTest") { +"Object mock = Mockito.mock(Object.class)" +"assert (mock.hashCode() == 0 || mock.hashCode() != 0)" From 3eb2feae872053b5d92220373c366590bb8d5ec8 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 20:52:48 +0100 Subject: [PATCH 050/128] Add mocking configuration to kex-test.ini The Mockito configuration has been added in the kex-test.ini file. The function 'getMockitoInline' has been removed in kt.rt file and 'getMockito' function has been updated to fetch Mockito version from kex-test.ini file, making it configurable. --- .../src/main/kotlin/org/vorpal/research/kex/util/rt.kt | 8 -------- kex-test.ini | 6 ++++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index 64a77a3f7..03d9edfd8 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -74,18 +74,10 @@ fun getJunit(): Container? { fun getMockito(): Container? { val libPath = kexConfig.libPath ?: return null val mockitoVersion = kexConfig.getStringValue("mock", "mockitoVersion") ?: return null - print(mockitoVersion) val mockitoPath = libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath() return JarContainer(mockitoPath, Package("org.mockito")) } -fun getMockitoInline(): Container? { - val libPath = kexConfig.libPath ?: return null - val mockitoVersion = "4.11.0" // TODO: Mock. Make configurable - return JarContainer(libPath.resolve("mockito-inline-$mockitoVersion.jar").toAbsolutePath(), Package.defaultPackage) -} - - fun getKexRuntime(): Container? { if (!kexConfig.getBooleanValue("kex", "useKexRuntime", true)) return null val libPath = kexConfig.libPath ?: return null diff --git a/kex-test.ini b/kex-test.ini index 2f1af57dd..b583bd79e 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -25,6 +25,12 @@ ignoreInstantiation = package org.junit.* ignoreInstantiation = package org.objenesis.* ignoreInstantiation = package net.bytebuddy.* +[mock] +enabled = true +mockitoVersion = 4.11.0 +mode = basic +; full to mock as much as possible, basic to mock only when can't create otherwise + [reanimator] enabled = true maxStackSize = 5 From 8185c55c281cb096d8da21d3f260ad7fed65b77c Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 21:52:47 +0100 Subject: [PATCH 051/128] Refactoring, functions for reading mocking config --- .../research/kex/parameters/Parameters.kt | 4 +- .../kotlin/org/vorpal/research/kex/util/rt.kt | 24 +++++++- .../kex/asm/analysis/util/extensions.kt | 57 ++++++++++--------- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 56d7dbb0e..99cafac19 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -134,7 +134,7 @@ private fun Descriptor.insertMocks( } } -fun Descriptor.isMockable(types: TypeFactory): Boolean { +fun Descriptor.isBasicMockable(types: TypeFactory): Boolean { val klass = (type.getKfgType(types) as? ClassType)?.klass return klass != null && !instantiationManager.isInstantiable(klass) && !type.isKexRt && this is ObjectDescriptor } @@ -147,7 +147,7 @@ private fun Descriptor.replaceWithMock( val descriptor = this if (descriptorToMock[descriptor] != null) return descriptorToMock[descriptor]!! val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass - return if (!descriptor.isMockable(types)) { + return if (!descriptor.isBasicMockable(types)) { descriptor.insertMocks(types, descriptorToMock, visited) descriptor } else { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index 03d9edfd8..02596cbb3 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -9,6 +9,7 @@ import org.vorpal.research.kfg.container.Container import org.vorpal.research.kfg.container.JarContainer import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.tryOrNull import java.io.File import java.nio.file.Path import kotlin.io.path.readLines @@ -60,7 +61,10 @@ fun getRuntime(): Container? { fun getIntrinsics(): Container? { val libPath = kexConfig.libPath ?: return null val intrinsicsVersion = kexConfig.getStringValue("kex", "intrinsicsVersion") ?: return null - return JarContainer(libPath.resolve("kex-intrinsics-${intrinsicsVersion}.jar"), Package.defaultPackage) + return JarContainer( + libPath.resolve("kex-intrinsics-${intrinsicsVersion}.jar"), + Package.defaultPackage + ) } fun getPathSeparator(): String = File.pathSeparator @@ -68,7 +72,10 @@ fun getPathSeparator(): String = File.pathSeparator fun getJunit(): Container? { val libPath = kexConfig.libPath ?: return null val junitVersion = kexConfig.getStringValue("kex", "junitVersion") ?: return null - return JarContainer(libPath.resolve("junit-$junitVersion.jar").toAbsolutePath(), Package.defaultPackage) + return JarContainer( + libPath.resolve("junit-$junitVersion.jar").toAbsolutePath(), + Package.defaultPackage + ) } fun getMockito(): Container? { @@ -78,6 +85,19 @@ fun getMockito(): Container? { return JarContainer(mockitoPath, Package("org.mockito")) } +enum class MockingMode { + BASIC, FULL +} + +fun getMockingMode(): MockingMode? { + val mockingMode = kexConfig.getStringValue("mock", "mode") ?: return null + return tryOrNull { MockingMode.valueOf(mockingMode.uppercase()) } +} + +fun getMockingEnabled(): Boolean { + return kexConfig.getBooleanValue("mock", "enabled", false) +} + fun getKexRuntime(): Container? { if (!kexConfig.getBooleanValue("kex", "useKexRuntime", true)) return null val libPath = kexConfig.libPath ?: return null diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index aaf831479..92a034dfb 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -21,8 +21,11 @@ import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState +import org.vorpal.research.kex.util.MockingMode +import org.vorpal.research.kex.util.getMockingEnabled +import org.vorpal.research.kex.util.getMockingMode +import org.vorpal.research.kex.util.getMockito import org.vorpal.research.kfg.ir.Method -import org.vorpal.research.kfg.type.TypeFactory import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn @@ -65,27 +68,11 @@ fun methodCalls( } .filter { (_, value) -> value != null } .map { (call, value) -> call to value!! } - .map { (call, value) -> call to descriptorToMock.getOrElse(value) { value } } + .map { (call, value) -> call to (descriptorToMock[value] ?: value) } .toList() } -fun Descriptor.requireMocks(visited: MutableSet, types: TypeFactory): Boolean { - if (this in visited) return false - if (isMockable(types)) return true - visited += this - - fun Descriptor.requireMocks(): Boolean = this.requireMocks(visited, types) - return when (this) { - is ConstantDescriptor -> false - - is MockDescriptor -> fields.values.any { it.requireMocks() } || allReturns.any { it.requireMocks() } - is ClassDescriptor -> fields.values.any { it.requireMocks() } - is ObjectDescriptor -> fields.values.any { it.requireMocks() } - is ArrayDescriptor -> elements.values.any { it.requireMocks() } - } -} - suspend fun Method.checkAsync( ctx: ExecutionContext, state: SymbolicState, @@ -112,16 +99,7 @@ suspend fun Method.checkAsync( checker.state ) - val finalDescriptors = if (initialDescriptors.asList.any { it.isMockable(ctx.types) }) { - generator.generateAll() - val descriptors = with(initialDescriptors) { Parameters(instance, arguments, statics, generator.allValues) } - val (withMocks, descriptorToMock) = descriptors.generateInitialMocks(ctx.types) - val methodCalls = methodCalls(state, generator.memory, descriptorToMock) - setupMocks(methodCalls, generator.memory, descriptorToMock) - withMocks - } else { - initialDescriptors - } + val finalDescriptors = initialDescriptors.finalizeDescriptors(ctx, generator, state) finalDescriptors .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { @@ -135,6 +113,29 @@ suspend fun Method.checkAsync( } } +private fun Parameters.finalizeDescriptors( + ctx: ExecutionContext, + generator: DescriptorGenerator, + state: SymbolicState +): Parameters { + if (!getMockingEnabled() || getMockito() == null || getMockingMode() == null) { + return this + } + if (getMockingMode() == MockingMode.FULL) { + TODO("Not implemented") + } + if (!asList.any { it.isBasicMockable(ctx.types) }) { + return this + } + + generator.generateAll() + val descriptors = Parameters(instance, arguments, statics, generator.allValues) + val (withMocks, descriptorToMock) = descriptors.generateInitialMocks(ctx.types) + val methodCalls = methodCalls(state, generator.memory, descriptorToMock) + setupMocks(methodCalls, generator.memory, descriptorToMock) + return withMocks +} + @Suppress("unused") suspend fun Method.checkAsyncAndSlice( ctx: ExecutionContext, From 8dbd3617eaecc9faa3d190418fc0eeef40eb1f5c Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 22:21:19 +0100 Subject: [PATCH 052/128] Refactoring --- .../research/kex/parameters/Parameters.kt | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 99cafac19..b682b3aa3 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -104,12 +104,12 @@ fun Parameters.filterIgnoredStatic(): Parameters { private fun Descriptor.insertMocks( types: TypeFactory, descriptorToMock: MutableMap, - visited: MutableSet + withMocksInserted: MutableSet ) { - fun Descriptor.replaceWithMock(): Descriptor = replaceWithMock(types, descriptorToMock, visited) + fun Descriptor.replaceWithMock() = replaceWithMock(types, descriptorToMock, withMocksInserted) - if (this in visited) return - visited.add(this) + if (this in withMocksInserted) return + withMocksInserted.add(this) when (this) { is ConstantDescriptor -> {} @@ -142,27 +142,27 @@ fun Descriptor.isBasicMockable(types: TypeFactory): Boolean { private fun Descriptor.replaceWithMock( types: TypeFactory, descriptorToMock: MutableMap, - visited: MutableSet + withMocksInserted: MutableSet ): Descriptor { - val descriptor = this - if (descriptorToMock[descriptor] != null) return descriptorToMock[descriptor]!! - val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass - return if (!descriptor.isBasicMockable(types)) { - descriptor.insertMocks(types, descriptorToMock, visited) - descriptor - } else { - klass ?: return descriptor.also { log.error { "Got null class to mock. Descriptor: $descriptor" } } - val mock = if (descriptor is ObjectDescriptor) { - MockDescriptor(klass.methods, descriptor).also { it.fields.putAll(descriptor.fields) } - } else { - log.warn { "Strange descriptor to mock. Expected ObjectDescriptor. Got: $descriptor" } - MockDescriptor(descriptor.term, descriptor.type as KexClass, klass.methods) - }.also { log.debug { "Created mock descriptor for ${it.term}" } } - visited.add(descriptor) - descriptorToMock[descriptor] = mock - mock.insertMocks(types, descriptorToMock, visited) - mock + if (descriptorToMock[this] != null) return descriptorToMock[this]!! + if (!this.isBasicMockable(types)) { + this.insertMocks(types, descriptorToMock, withMocksInserted) + return this } + + val klass = (this.type.getKfgType(types) as? ClassType)?.klass + klass ?: return this.also { log.error { "Got null class to mock. Descriptor: $this" } } + val mock = if (this is ObjectDescriptor) { + MockDescriptor(klass.methods, this).also { it.fields.putAll(this.fields) } + } else { + log.warn { "Strange descriptor to mock. Expected ObjectDescriptor. Got: $this" } + MockDescriptor(this.term, this.type as KexClass, klass.methods) + }.also { log.debug { "Created mock descriptor for ${it.term}" } } + withMocksInserted.add(this) + descriptorToMock[this] = mock + descriptorToMock[mock] = mock + mock.insertMocks(types, descriptorToMock, withMocksInserted) + return mock } private fun Collection.replaceUninstantiableWithMocks( From 2ee0557249dcee428429ef0cf70efc0c0af76847 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 22:35:29 +0100 Subject: [PATCH 053/128] Update memory values filtering in AbstractGenerator and other classes The 'allValues' getter in AbstractGenerator, extensions.kt, and DescriptorGenerator.kt has been replaced with 'others'. This change excludes descriptors already collected in `generateInitialDescriptors` from Parameters.others property --- .../research/kex/asm/analysis/util/extensions.kt | 2 +- .../kex/state/transformer/AbstractGenerator.kt | 2 +- .../kex/state/transformer/DescriptorGenerator.kt | 14 +++++++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 92a034dfb..45212bfcc 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -129,7 +129,7 @@ private fun Parameters.finalizeDescriptors( } generator.generateAll() - val descriptors = Parameters(instance, arguments, statics, generator.allValues) + val descriptors = Parameters(instance, arguments, statics, generator.others) val (withMocks, descriptorToMock) = descriptors.generateInitialMocks(ctx.types) val methodCalls = methodCalls(state, generator.memory, descriptorToMock) setupMocks(methodCalls, generator.memory, descriptorToMock) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt index 99e6173f7..c2a1a7827 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt @@ -45,7 +45,7 @@ interface AbstractGenerator : Transformer> { val instance get() = thisTerm?.let { memory[it] } val args get() = argTerms.map { memory[it.value] } val staticFields get() = staticFieldOwners.mapTo(mutableSetOf()) { memory[it]!! } - val allValues get() = allTerms.mapTo(mutableSetOf()) { memory[it]!! } // TODO check is !! correct + val others get() = memory.values.filterNotTo(mutableSetOf()) { it == instance && it in staticFields && it in args } fun generateThis() = thisTerm?.let { memory[it] = modelReanimator.reanimate(it) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index 2e4e4f7de..0980c18d2 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -55,8 +55,16 @@ class DescriptorGenerator( } override fun checkPath(path: Predicate): Boolean = when (path) { - is EqualityPredicate -> checkTerms(path.lhv, path.rhv) { a, b -> a.numericValue == b.numericValue } - is InequalityPredicate -> checkTerms(path.lhv, path.rhv) { a, b -> a.numericValue != b.numericValue } + is EqualityPredicate -> checkTerms( + path.lhv, + path.rhv + ) { a, b -> a.numericValue == b.numericValue } + + is InequalityPredicate -> checkTerms( + path.lhv, + path.rhv + ) { a, b -> a.numericValue != b.numericValue } + is DefaultSwitchPredicate -> { val lhv = path.cond val conditions = path.cases @@ -137,7 +145,7 @@ fun generateInitialDescriptors( arg ?: descriptor { default(method.argTypes[index].kexType) } }, generator.staticFields, - generator.allValues + generator.others ) to generator } From 381df0525a4ed501efd061fb4a8257a999e1b6ca Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 22:37:36 +0100 Subject: [PATCH 054/128] Modify tolerances in mockStaticsTests and mockListTests Tolerance values were added to the assertCoverage function in mockStaticsTests and mockListTests. This will give a little flexibility when checking for test coverage and potentially reduce the occurrence of false negatives in the tests. --- .../org/vorpal/research/kex/concolic/MockConcolicLongTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index bf6d59c32..ffebf56d5 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -39,12 +39,12 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockStaticsTests() { - assertCoverage(cm[prefix + "MockStaticsTests"], 1.0) + assertCoverage(cm[prefix + "MockStaticsTests"], 1.0, 0.03) } @Test fun mockListTests() { - assertCoverage(cm[prefix + "MockListTests"], 1.0) + assertCoverage(cm[prefix + "MockListTests"], 1.0, 0.12) } @Test From dc7af303bcb73952c2629096d08cd2289c12bdba Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 22 Jan 2024 22:39:27 +0100 Subject: [PATCH 055/128] Remove mention of full mock mode, because it's unimplemented --- kex-test.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kex-test.ini b/kex-test.ini index b583bd79e..a53609700 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -29,7 +29,7 @@ ignoreInstantiation = package net.bytebuddy.* enabled = true mockitoVersion = 4.11.0 mode = basic -; full to mock as much as possible, basic to mock only when can't create otherwise +; basic to mock only when can't create otherwise [reanimator] enabled = true From b71c5754bc0b1c01b7f9dcc8e6a7abb9115a944a Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 24 Jan 2024 14:50:50 +0100 Subject: [PATCH 056/128] Changes from sbst-mocks --- .../research/kex/descriptor/descriptor.kt | 151 +++++++++--------- .../research/kex/parameters/Parameters.kt | 57 +++---- .../research/kex/worker/TestExecutor.kt | 2 +- .../kex/asm/analysis/util/extensions.kt | 2 +- 4 files changed, 99 insertions(+), 113 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 02414848b..2be6f5527 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -242,8 +242,7 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty } } -@Suppress("UNCHECKED_CAST") -sealed class AbstractFieldContainingDescriptor>( +sealed class AbstractFieldContainingDescriptor( term: Term, klass: KexClass ) : @@ -265,18 +264,24 @@ sealed class AbstractFieldContainingDescriptor): Descriptor? = fields.remove(field) - fun merge(other: T): T { - val newFields = other.fields + this.fields - this.fields.clear() - this.fields.putAll(newFields) - return this as T - } - fun accept(other: T): T { - val newFields = other.fields.mapValues { it.value.deepCopy(mutableMapOf(other to this)) } - this.fields.clear() - this.fields.putAll(newFields) - return this as T + protected fun concretizeFields( + cm: ClassManager, + accessLevel: AccessModifier, + random: Random, + visited: MutableSet + ) { + for ((field, value) in fields.toMap()) { + fields[field] = value.concretize(cm, accessLevel, random, visited) + } + } + protected fun reduceFields(visited: MutableSet) { + for ((field, value) in fields.toMap()) { + when { + value eq descriptor { default(field.second) } -> fields.remove(field) + else -> fields[field] = value.reduce(visited) + } + } } override fun print(map: MutableMap): String { @@ -313,6 +318,7 @@ sealed class AbstractFieldContainingDescriptor): PredicateState { if (this in set) return emptyState() set += this @@ -326,6 +332,51 @@ sealed class AbstractFieldContainingDescriptor): Boolean { + if (this in visited) return false + if (this == other) return true + visited += this + return fields.values.any { it.contains(other, visited) } + } + override fun generateTypeInfo(visited: MutableSet): PredicateState { + if (this in visited) return emptyState() + visited += this + + val instanceOfTerm = term { generate(KexBool) } + return basic { + axiom { instanceOfTerm equality (term `is` this@AbstractFieldContainingDescriptor.type) } + axiom { instanceOfTerm equality true } + for ((key, field) in this@AbstractFieldContainingDescriptor.fields) { + val typeInfo = field.generateTypeInfo(visited) + if (typeInfo.isNotEmpty) { + state { + field.term equality this@AbstractFieldContainingDescriptor.term.field(key.second, key.first).load() + } + append(typeInfo) + } + } + } + } + override fun countDepth(visited: Set, cache: MutableMap): Int { + if (this in cache) return cache[this]!! + if (this in visited) return 0 + val newVisited = visited + this + var maxDepth = 0 + for (value in fields.values) { + maxDepth = maxOf(maxDepth, value.countDepth(newVisited, cache)) + } + cache[this] = maxDepth + 1 + return maxDepth + 1 + } +} + +@Suppress("UNCHECKED_CAST") +sealed class FieldContainingDescriptor>( + term: Term, + klass: KexClass +) : + AbstractFieldContainingDescriptor(term, klass) { + override fun concretize( cm: ClassManager, accessLevel: AccessModifier, @@ -345,22 +396,18 @@ sealed class AbstractFieldContainingDescriptor - ) { - for ((field, value) in fields.toMap()) { - fields[field] = value.concretize(cm, accessLevel, random, visited) - } + fun merge(other: T): T { + val newFields = other.fields + this.fields + this.fields.clear() + this.fields.putAll(newFields) + return this as T } - override fun contains(other: Descriptor, visited: MutableSet): Boolean { - if (this in visited) return false - if (this == other) return true - visited += this - return fields.values.any { it.contains(other, visited) } + fun accept(other: T): T { + val newFields = other.fields.mapValues { it.value.deepCopy(mutableMapOf(other to this)) } + this.fields.clear() + this.fields.putAll(newFields) + return this as T } override fun reduce(visited: MutableSet): T { @@ -370,35 +417,6 @@ sealed class AbstractFieldContainingDescriptor) { - for ((field, value) in fields.toMap()) { - when { - value eq descriptor { default(field.second) } -> fields.remove(field) - else -> fields[field] = value.reduce(visited) - } - } - } - - override fun generateTypeInfo(visited: MutableSet): PredicateState { - if (this in visited) return emptyState() - visited += this - - val instanceOfTerm = term { generate(KexBool) } - return basic { - axiom { instanceOfTerm equality (term `is` this@AbstractFieldContainingDescriptor.type) } - axiom { instanceOfTerm equality true } - for ((key, field) in this@AbstractFieldContainingDescriptor.fields) { - val typeInfo = field.generateTypeInfo(visited) - if (typeInfo.isNotEmpty) { - state { - field.term equality this@AbstractFieldContainingDescriptor.term.field(key.second, key.first).load() - } - append(typeInfo) - } - } - } - } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { if (this == other) return true if (other !is FieldContainingDescriptor<*>) return false @@ -413,25 +431,6 @@ sealed class AbstractFieldContainingDescriptor, cache: MutableMap): Int { - if (this in cache) return cache[this]!! - if (this in visited) return 0 - val newVisited = visited + this - var maxDepth = 0 - for (value in fields.values) { - maxDepth = maxOf(maxDepth, value.countDepth(newVisited, cache)) - } - cache[this] = maxDepth + 1 - return maxDepth + 1 - } -} - -sealed class FieldContainingDescriptor>( - term: Term, - klass: KexClass -) : - AbstractFieldContainingDescriptor(term, klass) { } class ObjectDescriptor(klass: KexClass) : @@ -647,7 +646,7 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : } class MockDescriptor(term: Term, type: KexClass, methods: Collection = emptyList()) : - AbstractFieldContainingDescriptor(term, type) { + AbstractFieldContainingDescriptor(term, type) { constructor(type: KexClass, methods: Collection) : this(term { generate(type) }, type, methods) constructor(methods: Collection, original: ObjectDescriptor) : this( diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index b682b3aa3..ec8d5502d 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -101,6 +101,26 @@ fun Parameters.filterIgnoredStatic(): Parameters { return Parameters(instance, arguments, filteredStatics, others) } + +fun Parameters.generateInitialMocks( + types: TypeFactory +): Pair, Map> { + val descriptorToMock = mutableMapOf() + val mockedArguments = arguments.replaceUninstantiableWithMocks(types, descriptorToMock).toList() + val mockedStatics = statics.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() + val mockedOthers = others.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() + + return Parameters(instance, mockedArguments, mockedStatics, mockedOthers) to descriptorToMock +} + +private fun Collection.replaceUninstantiableWithMocks( + types: TypeFactory, + descriptorToMock: MutableMap +): Collection { + val visited = mutableSetOf() + return map { it.replaceWithMock(types, descriptorToMock, visited) } +} + private fun Descriptor.insertMocks( types: TypeFactory, descriptorToMock: MutableMap, @@ -134,7 +154,7 @@ private fun Descriptor.insertMocks( } } -fun Descriptor.isBasicMockable(types: TypeFactory): Boolean { +fun Descriptor.isMockable(types: TypeFactory): Boolean { val klass = (type.getKfgType(types) as? ClassType)?.klass return klass != null && !instantiationManager.isInstantiable(klass) && !type.isKexRt && this is ObjectDescriptor } @@ -145,7 +165,7 @@ private fun Descriptor.replaceWithMock( withMocksInserted: MutableSet ): Descriptor { if (descriptorToMock[this] != null) return descriptorToMock[this]!! - if (!this.isBasicMockable(types)) { + if (!this.isMockable(types)) { this.insertMocks(types, descriptorToMock, withMocksInserted) return this } @@ -165,39 +185,6 @@ private fun Descriptor.replaceWithMock( return mock } -private fun Collection.replaceUninstantiableWithMocks( - types: TypeFactory, - descriptorToMock: MutableMap -): Collection { - val visited = mutableSetOf() - return map { it.replaceWithMock(types, descriptorToMock, visited) } -} - -fun Parameters.generateInitialMocks( - types: TypeFactory -): Pair, Map> { - val descriptorToMock = mutableMapOf() - val mockedArguments = arguments.replaceUninstantiableWithMocks(types, descriptorToMock).toList() - val mockedStatics = statics.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() - val mockedOthers = others.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() - - return Parameters(instance, mockedArguments, mockedStatics, mockedOthers) to descriptorToMock -} - - -/* -fun Parameters.generateMocks( - methodCalls: List>, - termToDescriptor: MutableMap, - cm: ClassManager, - accessLevel: AccessModifier -): Parameters { - val withMocks = this.generateInitialMocks(cm.type) - setupMocks(methodCalls, termToDescriptor) - return withMocks -} -*/ - fun setupMocks( methodCalls: List>, termToDescriptor: Map, diff --git a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt index 506505cfa..c92ec8992 100644 --- a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt +++ b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt @@ -46,7 +46,7 @@ class TestExecutor( test.invoke(instance) } catch (e: Throwable) { exception = e - log.error("Execution failed with an exception $e") // no stacktrace??? + log.error("Execution failed with an exception $e") } TraceCollectorProxy.disableCollector() log.debug("Collected state: {}", collector.symbolicState) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 45212bfcc..85a74a052 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -124,7 +124,7 @@ private fun Parameters.finalizeDescriptors( if (getMockingMode() == MockingMode.FULL) { TODO("Not implemented") } - if (!asList.any { it.isBasicMockable(ctx.types) }) { + if (!asList.any { it.isMockable(ctx.types) }) { return this } From 674059771208cc8c74f2110d0504f61b6b4d2124 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 26 Jan 2024 21:53:47 +0100 Subject: [PATCH 057/128] Enabled mocks in symbolic mode. Tests for mock-symbolic --- .../analysis/crash/precondition/builders.kt | 70 ++++++++++++++++++- .../kex/asm/analysis/util/extensions.kt | 10 +-- .../kex/symbolic/MockSymbolicLongTest.kt | 62 ++++++++++++++++ 3 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 kex-runner/src/test/kotlin/org/vorpal/research/kex/symbolic/MockSymbolicLongTest.kt diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt index a288ab687..bc9fe2810 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt @@ -196,7 +196,7 @@ class DescriptorExceptionPreconditionBuilder( is ConstantDescriptor -> this.asSymbolicState(location, mapping, visited) is FieldContainingDescriptor<*> -> this.asSymbolicState(location, mapping, visited) is ArrayDescriptor -> this.asSymbolicState(location, mapping, visited) - is MockDescriptor -> TODO("Mock. Implement later") + is MockDescriptor -> this.asSymbolicState(location, mapping, visited) } private fun ConstantDescriptor.asSymbolicState( @@ -273,6 +273,70 @@ class DescriptorExceptionPreconditionBuilder( return current } + private fun MockDescriptor.asSymbolicState( + location: Instruction, + mapping: MutableMap, + visited: MutableSet + ): PersistentSymbolicState { + TODO("Not implemented") +/* + visited += this + var current = persistentSymbolicState() + val objectTerm = run { + val objectTerm = mapping.getOrDefault(this.term, this.term) + when { + objectTerm.type != this.type -> term { generate(this@asSymbolicState.type) }.also { replacement -> + current += persistentSymbolicState( + state = persistentClauseStateOf( + StateClause(location, state { + replacement equality (objectTerm `as` this@asSymbolicState.type) + }) + ) + ) + mapping[this.term] = replacement + } + + else -> objectTerm + } + } + current += persistentSymbolicState( + path = persistentPathConditionOf( + PathClause(PathClauseType.NULL_CHECK, location, path { + (objectTerm eq null) equality false + }) + ) + ) + for ((field, descriptor) in this.fields) { + current += descriptor.asSymbolicState(location, mapping, visited) + current += persistentSymbolicState( + path = persistentPathConditionOf( + PathClause(PathClauseType.CONDITION_CHECK, location, path { + val fieldTerm = mapping.getOrDefault(descriptor.term, descriptor.term) + (objectTerm.field(field).load() eq fieldTerm) equality true + }) + ) + ) + } + for ((method, values) in this.methodReturns) { + for (descriptor in values) { + current += descriptor.asSymbolicState(location, mapping, visited) + current += persistentSymbolicState( + path = persistentPathConditionOf( + PathClause(PathClauseType.CONDITION_CHECK, location, path { + TODO() + } + + ) + ) + ) + } + } + TODO("Unimplemented") + return current + +*/ + } + private fun ArrayDescriptor.asSymbolicState( location: Instruction, mapping: MutableMap, @@ -362,7 +426,9 @@ class ConstraintExceptionPreconditionBuilder( location, state { (mapped `is` original.type) equality true } ) - typePrecondition += StateClause(location, state { casted equality (mapped `as` original.type) }) + typePrecondition += StateClause( + location, + state { casted equality (mapped `as` original.type) }) this[original] = casted } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 85a74a052..6040049a7 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -21,10 +21,7 @@ import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState -import org.vorpal.research.kex.util.MockingMode -import org.vorpal.research.kex.util.getMockingEnabled -import org.vorpal.research.kex.util.getMockingMode -import org.vorpal.research.kex.util.getMockito +import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log @@ -215,7 +212,10 @@ suspend fun Method.checkAsyncIncremental( when (result) { is Result.SatResult -> try { val fullPS = checker.state + checker.queries[index].hardConstraints - generateInitialDescriptors(this, ctx, result.model, fullPS).first + generateInitialDescriptors(this, ctx, result.model, fullPS) + .let { (descriptors, generator) -> + descriptors.finalizeDescriptors(ctx, generator, state) + } .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } } diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/symbolic/MockSymbolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/symbolic/MockSymbolicLongTest.kt new file mode 100644 index 000000000..b1343dcac --- /dev/null +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/symbolic/MockSymbolicLongTest.kt @@ -0,0 +1,62 @@ +package org.vorpal.research.kex.symbolic + +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.InternalSerializationApi +import org.junit.Test +import kotlin.time.ExperimentalTime + +@ExperimentalTime +@ExperimentalSerializationApi +@InternalSerializationApi +@DelicateCoroutinesApi +class MockSymbolicLongTest : SymbolicTest("mock-symbolic") { + val prefix = "org/vorpal/research/kex/test/concolic/mock/" + + @Test + fun mockTest() { + assertCoverage(cm[prefix + "MockTests"], 1.0) + } + @Test + fun mockReturnsMockTest() { + assertCoverage(cm[prefix + "MockReturnsMockTests"], 1.0) + } + + @Test + fun mockPrimitivesTest() { + assertCoverage(cm[prefix + "MockPrimitivesTests"], 1.0) + } + + @Test + fun mockEnumTest() { + assertCoverage(cm[prefix + "MockEnumTests"], 1.0) + } + + @Test + fun mockWithFieldsTests() { + val eps = 0.03 + assertCoverage(cm[prefix + "MockWithFieldsTests"], 1.0, eps) + } + + @Test + fun mockStaticsTests() { + assertCoverage(cm[prefix + "MockStaticsTests"], 1.0, 0.0) + } + + @Test + fun mockListTests() { + assertCoverage(cm[prefix + "MockListTests"], 1.0, 0.0) + } + + @Test + fun mockGenericsTests(){ + assertCoverage(cm[prefix + "MockGenericsTests"], 1.0) + } + + @Test + fun mockSetTests(){ + // unstable test. Anything can happen + val eps = 0.5 + assertCoverage(cm[prefix + "MockSetTests"], 1.0, eps) + } +} From 059c10f41b585da8c3092fcdecae4e3e553212f1 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 30 Jan 2024 19:31:26 +0100 Subject: [PATCH 058/128] Mocking mode "full": mock everything --- .../org/vorpal/research/kex/descriptor/converter.kt | 13 +++++++++---- .../vorpal/research/kex/parameters/Parameters.kt | 12 ++++++++++-- .../kex/trace/symbolic/SymbolicTraceBuilder.kt | 4 ++-- .../main/kotlin/org/vorpal/research/kex/util/kfg.kt | 9 +++++++-- .../main/kotlin/org/vorpal/research/kex/util/rt.kt | 8 ++++++++ .../research/kex/asm/analysis/util/extensions.kt | 4 ---- kex-test.ini | 6 +++++- kex.ini | 5 ++++- 8 files changed, 45 insertions(+), 16 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt index 11c599144..170bb9698 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt @@ -16,11 +16,10 @@ import org.vorpal.research.kex.ktype.KexShort import org.vorpal.research.kex.ktype.KexString import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.ktype.asArray -import org.vorpal.research.kex.util.allFields -import org.vorpal.research.kex.util.isStatic -import org.vorpal.research.kex.util.kex +import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.type.SystemTypeNames import org.vorpal.research.kthelper.assert.unreachable +import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import java.util.* @@ -66,11 +65,17 @@ class Object2DescriptorConverter : DescriptorBuilder() { is DoubleArray -> KexDouble.asArray() is Array<*> -> any.javaClass.componentType.kex.asArray() is String -> KexString() - else -> any.javaClass.kex + else -> if (!any.javaClass.name.contains(SUBSTRING_TO_FILTER_FROM_TYPE)) any.javaClass.kex else fixClass(any.javaClass) } } } + private fun fixClass(javaClass: Class): KexType { + return javaClass.classLoader.loadClass(javaClass.name.removeMockitoMockSuffix()).also{ + log.debug{"Fixed class: $javaClass, got: $it"} + }.kex + } + fun convert(any: Any?, depth: Int = 0): Descriptor { if (any == null) return `null` if (any in objectToDescriptor) return objectToDescriptor[any]!! diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index ec8d5502d..f55923a26 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -12,6 +12,8 @@ import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.util.KfgTargetFilter +import org.vorpal.research.kex.util.MockingMode +import org.vorpal.research.kex.util.getMockingMode import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory @@ -155,8 +157,14 @@ private fun Descriptor.insertMocks( } fun Descriptor.isMockable(types: TypeFactory): Boolean { - val klass = (type.getKfgType(types) as? ClassType)?.klass - return klass != null && !instantiationManager.isInstantiable(klass) && !type.isKexRt && this is ObjectDescriptor + val klass = (type.getKfgType(types) as? ClassType)?.klass ?: return false + val necessaryConditions = !klass.isFinal && !type.isKexRt && this is ObjectDescriptor + return necessaryConditions && when (getMockingMode()) { + MockingMode.FULL -> true + MockingMode.BASIC -> !instantiationManager.isInstantiable(klass) + null -> false + } + } private fun Descriptor.replaceWithMock( diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index e5ac7cb1e..d612e04fe 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -1270,8 +1270,8 @@ class SymbolicTraceBuilder( val kfgValue = parseValue(value) val termValue = mkValue(kfgValue) -// val realType = type.removeMockitoMockSuffix() - val realType = type + val realType = type.removeMockitoMockSuffix() +// val realType = type log.debug { "type: $type\nrealType: $realType" } val expectedKfgType = parseStringToType(cm.type, realType) val comparisonResult = when (concreteValue) { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index 2e476b6ed..2c0143a83 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -152,8 +152,12 @@ fun String.removeMockitoMockSuffix(): String { return if (suffixIndex == -1) { this } else { - log.debug { "MOCKITO_MOCK FIX. value: $this. Stack trace:" } -// log.debug(Thread.currentThread().stackTrace.joinToString("\n")) + if (getLogTypeFix().also{log.debug{"getLogTypeFix: $it"}}){ + log.debug { "MOCKITO_MOCK FIX. value: $this. Stack trace:" } + if (getLogStackTraceTypeFix().also{log.debug{"getLogStackTraceTypeFix: $it"}}){ + log.debug(Thread.currentThread().stackTrace.joinToString("\n")) + } + } removeRange(suffixIndex, length) } } @@ -197,6 +201,7 @@ private object SubTypeInfoCache { val key = lhv.toString() to rhv.toString() return subtypeCache[key] ?: (lhv.isSubtypeOf(rhv, outerClassBehavior = false) || lhv.sameButMockito(rhv)).also { subtypeCache[key] = it + subtypeCache[lhv.name.removeMockitoMockSuffix() to rhv.name.removeMockitoMockSuffix()] = it } } } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index 02596cbb3..520cc34fa 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -98,6 +98,14 @@ fun getMockingEnabled(): Boolean { return kexConfig.getBooleanValue("mock", "enabled", false) } +fun getLogTypeFix(): Boolean { + return kexConfig.getBooleanValue("mock", "logTypeFix", false) +} + +fun getLogStackTraceTypeFix(): Boolean { + return kexConfig.getBooleanValue("mock", "logStackTraceTypeFix", false) +} + fun getKexRuntime(): Container? { if (!kexConfig.getBooleanValue("kex", "useKexRuntime", true)) return null val libPath = kexConfig.libPath ?: return null diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 85a74a052..b54360240 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -21,7 +21,6 @@ import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState -import org.vorpal.research.kex.util.MockingMode import org.vorpal.research.kex.util.getMockingEnabled import org.vorpal.research.kex.util.getMockingMode import org.vorpal.research.kex.util.getMockito @@ -121,9 +120,6 @@ private fun Parameters.finalizeDescriptors( if (!getMockingEnabled() || getMockito() == null || getMockingMode() == null) { return this } - if (getMockingMode() == MockingMode.FULL) { - TODO("Not implemented") - } if (!asList.any { it.isMockable(ctx.types) }) { return this } diff --git a/kex-test.ini b/kex-test.ini index a53609700..3f4901880 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -28,8 +28,12 @@ ignoreInstantiation = package net.bytebuddy.* [mock] enabled = true mockitoVersion = 4.11.0 -mode = basic +mode = full ; basic to mock only when can't create otherwise +; full to mock as much as possible + +logTypeFix = true +logStackTraceTypeFix = true [reanimator] enabled = true diff --git a/kex.ini b/kex.ini index 66edd1a49..a163dfcf6 100644 --- a/kex.ini +++ b/kex.ini @@ -44,9 +44,12 @@ maxArrayLength = 1000 [mock] enabled = true mockitoVersion = 4.11.0 -mode = basic +mode = full ; full to mock as much as possible, basic to mock only when can't create otherwise +logTypeFix = true +logStackTraceTypeFix = true + [reanimator] enabled = true maxStackSize = 5 From 63c5def02e24d3d8802dc7c9a8ce9ee86c3aed94 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 30 Jan 2024 20:08:19 +0100 Subject: [PATCH 059/128] Fix $MockitoMock$ classes problem --- .../org/vorpal/research/kex/descriptor/converter.kt | 11 +++++++---- .../kex/trace/symbolic/SymbolicTraceBuilder.kt | 8 +------- .../research/kex/concolic/MockConcolicLongTest.kt | 12 +++++++++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt index 11c599144..308f3bc22 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt @@ -16,9 +16,7 @@ import org.vorpal.research.kex.ktype.KexShort import org.vorpal.research.kex.ktype.KexString import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.ktype.asArray -import org.vorpal.research.kex.util.allFields -import org.vorpal.research.kex.util.isStatic -import org.vorpal.research.kex.util.kex +import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.type.SystemTypeNames import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.log @@ -66,11 +64,16 @@ class Object2DescriptorConverter : DescriptorBuilder() { is DoubleArray -> KexDouble.asArray() is Array<*> -> any.javaClass.componentType.kex.asArray() is String -> KexString() - else -> any.javaClass.kex + else -> any.javaClass.toKexType() } } } + private fun Class<*>.toKexType(): KexType{ + if (!this.name.contains(SUBSTRING_TO_FILTER_FROM_TYPE)) return this.kex + return this.classLoader.loadClass(this.name.removeMockitoMockSuffix()).kex + } + fun convert(any: Any?, depth: Int = 0): Descriptor { if (any == null) return `null` if (any in objectToDescriptor) return objectToDescriptor[any]!! diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index e5ac7cb1e..41d16e48b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -176,13 +176,7 @@ class SymbolicTraceBuilder( override fun toString() = "$clauses" - private fun String.toType(): Type = parseDescOrNull(cm.type, this)!!.let { - if (it.name.contains(SUBSTRING_TO_FILTER_FROM_TYPE)) { - it.name.removeMockitoMockSuffix().toType().also { log.debug { "Prevented mockito mock type: $it" } } - } else { - it - } - } + private fun String.toType(): Type = parseDescOrNull(cm.type, this)!! private fun safeCall(body: () -> Unit) = `try` { body() diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index ffebf56d5..a7b650c06 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -39,12 +39,16 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockStaticsTests() { - assertCoverage(cm[prefix + "MockStaticsTests"], 1.0, 0.03) +// val eps = 0.03 + val eps = 0.0 + assertCoverage(cm[prefix + "MockStaticsTests"], 1.0, eps) } @Test fun mockListTests() { - assertCoverage(cm[prefix + "MockListTests"], 1.0, 0.12) +// val eps = 0.12 + val eps = 0.0 + assertCoverage(cm[prefix + "MockListTests"], 1.0, eps) } @Test @@ -55,6 +59,8 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockSetTests(){ // unstable test. Anything can happen - assertCoverage(cm[prefix + "MockSetTests"], 1.0, 0.5) +// val eps = 0.5 + val eps = 0.0 + assertCoverage(cm[prefix + "MockSetTests"], 1.0, eps) } } From 6129011691ac88f5988c90a1be3715a3101a59c0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 30 Jan 2024 20:21:54 +0100 Subject: [PATCH 060/128] Reverted eps in tests --- .../vorpal/research/kex/concolic/MockConcolicLongTest.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index a7b650c06..006fe4fa6 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -39,15 +39,13 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockStaticsTests() { -// val eps = 0.03 - val eps = 0.0 + val eps = 0.03 assertCoverage(cm[prefix + "MockStaticsTests"], 1.0, eps) } @Test fun mockListTests() { -// val eps = 0.12 - val eps = 0.0 + val eps = 0.12 assertCoverage(cm[prefix + "MockListTests"], 1.0, eps) } @@ -59,8 +57,7 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockSetTests(){ // unstable test. Anything can happen -// val eps = 0.5 - val eps = 0.0 + val eps = 0.5 assertCoverage(cm[prefix + "MockSetTests"], 1.0, eps) } } From c86bbe029bfb14245be46395da9be8a9a0482979 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 30 Jan 2024 20:24:38 +0100 Subject: [PATCH 061/128] Removed unused code --- .../analysis/crash/precondition/builders.kt | 66 +------------------ 1 file changed, 1 insertion(+), 65 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt index bc9fe2810..991511ba1 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/crash/precondition/builders.kt @@ -196,7 +196,7 @@ class DescriptorExceptionPreconditionBuilder( is ConstantDescriptor -> this.asSymbolicState(location, mapping, visited) is FieldContainingDescriptor<*> -> this.asSymbolicState(location, mapping, visited) is ArrayDescriptor -> this.asSymbolicState(location, mapping, visited) - is MockDescriptor -> this.asSymbolicState(location, mapping, visited) + is MockDescriptor -> TODO("Not implemented") } private fun ConstantDescriptor.asSymbolicState( @@ -273,70 +273,6 @@ class DescriptorExceptionPreconditionBuilder( return current } - private fun MockDescriptor.asSymbolicState( - location: Instruction, - mapping: MutableMap, - visited: MutableSet - ): PersistentSymbolicState { - TODO("Not implemented") -/* - visited += this - var current = persistentSymbolicState() - val objectTerm = run { - val objectTerm = mapping.getOrDefault(this.term, this.term) - when { - objectTerm.type != this.type -> term { generate(this@asSymbolicState.type) }.also { replacement -> - current += persistentSymbolicState( - state = persistentClauseStateOf( - StateClause(location, state { - replacement equality (objectTerm `as` this@asSymbolicState.type) - }) - ) - ) - mapping[this.term] = replacement - } - - else -> objectTerm - } - } - current += persistentSymbolicState( - path = persistentPathConditionOf( - PathClause(PathClauseType.NULL_CHECK, location, path { - (objectTerm eq null) equality false - }) - ) - ) - for ((field, descriptor) in this.fields) { - current += descriptor.asSymbolicState(location, mapping, visited) - current += persistentSymbolicState( - path = persistentPathConditionOf( - PathClause(PathClauseType.CONDITION_CHECK, location, path { - val fieldTerm = mapping.getOrDefault(descriptor.term, descriptor.term) - (objectTerm.field(field).load() eq fieldTerm) equality true - }) - ) - ) - } - for ((method, values) in this.methodReturns) { - for (descriptor in values) { - current += descriptor.asSymbolicState(location, mapping, visited) - current += persistentSymbolicState( - path = persistentPathConditionOf( - PathClause(PathClauseType.CONDITION_CHECK, location, path { - TODO() - } - - ) - ) - ) - } - } - TODO("Unimplemented") - return current - -*/ - } - private fun ArrayDescriptor.asSymbolicState( location: Instruction, mapping: MutableMap, From 0e3dbb275da5bf8b18aa1ab0da3290fc85205416 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 30 Jan 2024 20:55:37 +0100 Subject: [PATCH 062/128] Removed unnecessary logging --- .../src/main/kotlin/org/vorpal/research/kex/util/kfg.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index 2c0143a83..0e5174c4b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -152,10 +152,10 @@ fun String.removeMockitoMockSuffix(): String { return if (suffixIndex == -1) { this } else { - if (getLogTypeFix().also{log.debug{"getLogTypeFix: $it"}}){ - log.debug { "MOCKITO_MOCK FIX. value: $this. Stack trace:" } - if (getLogStackTraceTypeFix().also{log.debug{"getLogStackTraceTypeFix: $it"}}){ - log.debug(Thread.currentThread().stackTrace.joinToString("\n")) + if (getLogTypeFix()) { + log.debug { "MOCKITO_MOCK FIX. value: $this" } + if (getLogStackTraceTypeFix()) { + log.debug { "Stack trace: ${Thread.currentThread().stackTrace.joinToString("\n")}" } } } removeRange(suffixIndex, length) From edc0d9ec2ca80d17d029ffe36c55a328dfc86ff3 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 30 Jan 2024 21:50:33 +0100 Subject: [PATCH 063/128] Removed old unnecessary MockitoMock workarounds --- .../org/vorpal/research/kex/descriptor/converter.kt | 8 +++++++- .../kex/trace/symbolic/SymbolicTraceBuilder.kt | 2 -- .../main/kotlin/org/vorpal/research/kex/util/kfg.kt | 13 +++++++------ kex.ini | 3 ++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt index 308f3bc22..cdf6a9408 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt @@ -69,10 +69,16 @@ class Object2DescriptorConverter : DescriptorBuilder() { } } +// private val cache : MutableMap, KexType> = hashMapOf() private fun Class<*>.toKexType(): KexType{ - if (!this.name.contains(SUBSTRING_TO_FILTER_FROM_TYPE)) return this.kex + if (!this.name.containsMockitoMock) return this.kex return this.classLoader.loadClass(this.name.removeMockitoMockSuffix()).kex } +// if (!this.name.containsMockitoMock) return this.kex +// cache[this]?.let { return it} +// return this.classLoader.loadClass(this.name.removeMockitoMockSuffix()).kex.also{ +// cache[this] = it +// } fun convert(any: Any?, depth: Int = 0): Descriptor { if (any == null) return `null` diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index 5f9464466..5a2e6f304 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -1265,14 +1265,12 @@ class SymbolicTraceBuilder( val kfgValue = parseValue(value) val termValue = mkValue(kfgValue) val realType = type.removeMockitoMockSuffix() -// val realType = type log.debug { "type: $type\nrealType: $realType" } val expectedKfgType = parseStringToType(cm.type, realType) val comparisonResult = when (concreteValue) { null -> false else -> { val actualKfgType = concreteValue.getConcreteType(termValue.type).getKfgType(ctx.types) - // TODO: fix "...$MockitoMock$..." classes on the terms/descriptors generation actualKfgType.isSubtypeOfCached(expectedKfgType) } } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index 0e5174c4b..f04fa2efc 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -39,6 +39,7 @@ import org.vorpal.research.kthelper.collection.LRUCache import org.vorpal.research.kthelper.compareTo import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.logging.warn val Type.javaDesc get() = this.name.javaString @@ -147,6 +148,9 @@ fun NameMapper.parseValueOrNull(valueName: String): Value? { const val SUBSTRING_TO_FILTER_FROM_TYPE = "\$MockitoMock\$" + +val String.containsMockitoMock: Boolean get() = contains(SUBSTRING_TO_FILTER_FROM_TYPE) + fun String.removeMockitoMockSuffix(): String { val suffixIndex = lastIndexOf(SUBSTRING_TO_FILTER_FROM_TYPE) return if (suffixIndex == -1) { @@ -170,7 +174,8 @@ fun Type.getAllSubtypes(tf: TypeFactory): Set = when (this) { fun parseAsConcreteType(typeFactory: TypeFactory, name: String): KexType? { - val type = parseStringToType(typeFactory, name.removeMockitoMockSuffix()) + if (name.containsMockitoMock) log.warn { "utils.kt parseAsConcreteType got type with MockitoMock: $name" } + val type = parseStringToType(typeFactory, name) return when { type.isConcrete -> type.kexType else -> null @@ -199,16 +204,12 @@ private object SubTypeInfoCache { private val subtypeCache = LRUCache, Boolean>(100_000U) fun check(lhv: Type, rhv: Type): Boolean { val key = lhv.toString() to rhv.toString() - return subtypeCache[key] ?: (lhv.isSubtypeOf(rhv, outerClassBehavior = false) || lhv.sameButMockito(rhv)).also { + return subtypeCache[key] ?: lhv.isSubtypeOf(rhv, outerClassBehavior = false).also { subtypeCache[key] = it - subtypeCache[lhv.name.removeMockitoMockSuffix() to rhv.name.removeMockitoMockSuffix()] = it } } } -private fun Type.sameButMockito(expectedKfgType: Type) = - name.removeMockitoMockSuffix() == expectedKfgType.name.removeMockitoMockSuffix() - fun Type.isSubtypeOfCached(other: Type): Boolean = SubTypeInfoCache.check(this, other) fun Field.isOuterThis(): Boolean { diff --git a/kex.ini b/kex.ini index a163dfcf6..a2955e2a0 100644 --- a/kex.ini +++ b/kex.ini @@ -45,7 +45,8 @@ maxArrayLength = 1000 enabled = true mockitoVersion = 4.11.0 mode = full -; full to mock as much as possible, basic to mock only when can't create otherwise +; basic to mock only when can't create otherwise +; full to mock as much as possible logTypeFix = true logStackTraceTypeFix = true From 85af0fe6c396429cbdfaf00820cbfda034a05ece Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 30 Jan 2024 23:06:41 +0100 Subject: [PATCH 064/128] MockitoMock fix refactoring, now caching fix results --- .../research/kex/descriptor/converter.kt | 22 +++++++++---------- .../trace/symbolic/SymbolicTraceBuilder.kt | 5 +---- .../org/vorpal/research/kex/util/kfg.kt | 1 - 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt index cdf6a9408..d6baf7e1e 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt @@ -64,21 +64,21 @@ class Object2DescriptorConverter : DescriptorBuilder() { is DoubleArray -> KexDouble.asArray() is Array<*> -> any.javaClass.componentType.kex.asArray() is String -> KexString() - else -> any.javaClass.toKexType() + else -> any.javaClass.kexTypeMockitoMockFixed } } } -// private val cache : MutableMap, KexType> = hashMapOf() - private fun Class<*>.toKexType(): KexType{ - if (!this.name.containsMockitoMock) return this.kex - return this.classLoader.loadClass(this.name.removeMockitoMockSuffix()).kex - } -// if (!this.name.containsMockitoMock) return this.kex -// cache[this]?.let { return it} -// return this.classLoader.loadClass(this.name.removeMockitoMockSuffix()).kex.also{ -// cache[this] = it -// } + private val classToKexType: MutableMap, KexType> = hashMapOf() + private val Class<*>.kexTypeMockitoMockFixed: KexType + get() { + if (classToKexType[this] != null) return classToKexType[this]!! + return if (!this.name.containsMockitoMock) { + this.kex + } else { + this.classLoader.loadClass(this.name.removeMockitoMockSuffix()).kex + }.also { classToKexType[this] = it } + } fun convert(any: Any?, depth: Int = 0): Descriptor { if (any == null) return `null` diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt index 5a2e6f304..0c89842d7 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/trace/symbolic/SymbolicTraceBuilder.kt @@ -26,7 +26,6 @@ import org.vorpal.research.kthelper.KtException import org.vorpal.research.kthelper.assert.ktassert import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.collection.stackOf -import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.toInt import org.vorpal.research.kthelper.`try` @@ -1264,9 +1263,7 @@ class SymbolicTraceBuilder( val kfgValue = parseValue(value) val termValue = mkValue(kfgValue) - val realType = type.removeMockitoMockSuffix() - log.debug { "type: $type\nrealType: $realType" } - val expectedKfgType = parseStringToType(cm.type, realType) + val expectedKfgType = parseStringToType(cm.type, type) val comparisonResult = when (concreteValue) { null -> false else -> { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index f04fa2efc..ed6c3aec8 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -174,7 +174,6 @@ fun Type.getAllSubtypes(tf: TypeFactory): Set = when (this) { fun parseAsConcreteType(typeFactory: TypeFactory, name: String): KexType? { - if (name.containsMockitoMock) log.warn { "utils.kt parseAsConcreteType got type with MockitoMock: $name" } val type = parseStringToType(typeFactory, name) return when { type.isConcrete -> type.kexType From 7922926f9fa80cef115db8d7aaabc4d2304f8086 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 30 Jan 2024 23:06:56 +0100 Subject: [PATCH 065/128] Unused import --- kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index ed6c3aec8..e46142471 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -39,7 +39,6 @@ import org.vorpal.research.kthelper.collection.LRUCache import org.vorpal.research.kthelper.compareTo import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log -import org.vorpal.research.kthelper.logging.warn val Type.javaDesc get() = this.name.javaString From 1b4485b946ece8fed74932f7f5f80e89ca16def0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 31 Jan 2024 00:27:58 +0100 Subject: [PATCH 066/128] HashMap -> LRUCache in MockitoMock fix. Basic mocking mode by default. --- .../org/vorpal/research/kex/descriptor/converter.kt | 8 ++++++-- .../vorpal/research/kex/concolic/MockConcolicLongTest.kt | 1 - kex-test.ini | 4 ++-- kex.ini | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt index d6baf7e1e..7936626b7 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt @@ -19,6 +19,8 @@ import org.vorpal.research.kex.ktype.asArray import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.type.SystemTypeNames import org.vorpal.research.kthelper.assert.unreachable +import org.vorpal.research.kthelper.collection.Cache +import org.vorpal.research.kthelper.collection.LRUCache import org.vorpal.research.kthelper.logging.log import java.util.* @@ -62,14 +64,16 @@ class Object2DescriptorConverter : DescriptorBuilder() { is LongArray -> KexLong.asArray() is FloatArray -> KexFloat.asArray() is DoubleArray -> KexDouble.asArray() - is Array<*> -> any.javaClass.componentType.kex.asArray() + is Array<*> -> any.javaClass.componentType.kexTypeMockitoMockFixed.asArray() is String -> KexString() else -> any.javaClass.kexTypeMockitoMockFixed } } } - private val classToKexType: MutableMap, KexType> = hashMapOf() + companion object{ + private val classToKexType: Cache, KexType> = LRUCache(250u) + } private val Class<*>.kexTypeMockitoMockFixed: KexType get() { if (classToKexType[this] != null) return classToKexType[this]!! diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index 006fe4fa6..00bb7a7b4 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -56,7 +56,6 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockSetTests(){ - // unstable test. Anything can happen val eps = 0.5 assertCoverage(cm[prefix + "MockSetTests"], 1.0, eps) } diff --git a/kex-test.ini b/kex-test.ini index 3f4901880..891fa8ee5 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -28,12 +28,12 @@ ignoreInstantiation = package net.bytebuddy.* [mock] enabled = true mockitoVersion = 4.11.0 -mode = full +mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible logTypeFix = true -logStackTraceTypeFix = true +logStackTraceTypeFix = false [reanimator] enabled = true diff --git a/kex.ini b/kex.ini index a2955e2a0..5977387d4 100644 --- a/kex.ini +++ b/kex.ini @@ -44,12 +44,12 @@ maxArrayLength = 1000 [mock] enabled = true mockitoVersion = 4.11.0 -mode = full +mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible logTypeFix = true -logStackTraceTypeFix = true +logStackTraceTypeFix = false [reanimator] enabled = true From 60460dfaf3ad6466e5e9b5c6f358b70b8f32c3b1 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 31 Jan 2024 00:41:56 +0100 Subject: [PATCH 067/128] Updated MockitoMock fix, no more classLoader and cache usage. --- .../vorpal/research/kex/descriptor/converter.kt | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt index 7936626b7..caf9d8e61 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt @@ -19,8 +19,6 @@ import org.vorpal.research.kex.ktype.asArray import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.type.SystemTypeNames import org.vorpal.research.kthelper.assert.unreachable -import org.vorpal.research.kthelper.collection.Cache -import org.vorpal.research.kthelper.collection.LRUCache import org.vorpal.research.kthelper.logging.log import java.util.* @@ -71,17 +69,11 @@ class Object2DescriptorConverter : DescriptorBuilder() { } } - companion object{ - private val classToKexType: Cache, KexType> = LRUCache(250u) - } private val Class<*>.kexTypeMockitoMockFixed: KexType - get() { - if (classToKexType[this] != null) return classToKexType[this]!! - return if (!this.name.containsMockitoMock) { - this.kex - } else { - this.classLoader.loadClass(this.name.removeMockitoMockSuffix()).kex - }.also { classToKexType[this] = it } + get() = if (!this.name.containsMockitoMock) { + this.kex + } else { + KexClass(this.kex.name.removeMockitoMockSuffix()) } fun convert(any: Any?, depth: Int = 0): Descriptor { From c363ceee95a287d1cae4f810724cb7f3d80cc92f Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 1 Feb 2024 00:19:29 +0100 Subject: [PATCH 068/128] Changes from sbst-mock --- .../asm/manager/ClassInstantiationManager.kt | 2 +- .../research/kex/descriptor/converter.kt | 9 +-- .../research/kex/descriptor/descriptor.kt | 10 +-- .../research/kex/parameters/Parameters.kt | 69 ++++++++++--------- .../research/kex/serialization/descriptor.kt | 2 +- .../org/vorpal/research/kex/util/kfg.kt | 5 +- .../kotlin/org/vorpal/research/kex/util/rt.kt | 59 ++++++++-------- .../research/kex/launcher/WorkerLauncher.kt | 2 +- .../kex/asm/analysis/util/extensions.kt | 32 ++++++--- .../research/kex/reanimator/Reanimator.kt | 6 +- .../state/transformer/AbstractGenerator.kt | 2 +- .../state/transformer/DescriptorGenerator.kt | 4 +- .../kex/trace/runner/ObjectTracingRunner.kt | 7 +- kex-test.ini | 7 +- kex.ini | 6 +- pom.xml | 4 +- 16 files changed, 126 insertions(+), 100 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt index 1e899c45e..808a4ef04 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt @@ -307,7 +307,7 @@ class ClassInstantiationDetector( override fun cleanup() {} override fun visit(klass: Class) { - if (ignoredInstantiations.any { it.matches(klass.asType.kexType.toString()) }) { + if (ignoredInstantiations.any { it.matches(klass) }) { return } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt index caf9d8e61..02e24c8c2 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt @@ -70,10 +70,11 @@ class Object2DescriptorConverter : DescriptorBuilder() { } private val Class<*>.kexTypeMockitoMockFixed: KexType - get() = if (!this.name.containsMockitoMock) { - this.kex - } else { - KexClass(this.kex.name.removeMockitoMockSuffix()) + get() = when { + !kexConfig.isMockingEnabled || !kexConfig.isMockitoClassesWorkaroundEnabled -> this.kex + + !this.name.containsMockitoMock -> this.kex + else -> KexClass(this.kex.name.removeMockitoMockSuffix()) } fun convert(any: Any?, depth: Int = 0): Descriptor { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 2be6f5527..c22dfc886 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -645,11 +645,11 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : } } -class MockDescriptor(term: Term, type: KexClass, methods: Collection = emptyList()) : +class MockDescriptor(term: Term, type: KexClass, methods: Set = emptySet()) : AbstractFieldContainingDescriptor(term, type) { - constructor(type: KexClass, methods: Collection) : this(term { generate(type) }, type, methods) - constructor(methods: Collection, original: ObjectDescriptor) : this( + constructor(type: KexClass, methods: Set) : this(term { generate(type) }, type, methods) + constructor(methods: Set, original: ObjectDescriptor) : this( original.term, original.type as KexClass, methods @@ -824,7 +824,7 @@ open class DescriptorBuilder : StringInfoContext() { fun array(length: Int, elementType: KexType): ArrayDescriptor = ArrayDescriptor(elementType, length) - fun mock(type: KexClass, methods: Collection) = MockDescriptor(type, methods) + fun mock(type: KexClass, methods: Set) = MockDescriptor(type, methods) fun mock(type: KexClass, types: TypeFactory): MockDescriptor = MockDescriptor(type, (type.getKfgType(types) as ClassType).klass.methods) @@ -926,7 +926,7 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde MockDescriptor( descriptor.term, descriptor.type.mapped as KexClass, - descriptor.methods.map { method -> method.mapped }) + descriptor.methods.mapTo(mutableSetOf()) { method -> method.mapped }) cache[descriptor] = mockMapped for ((field, value) in descriptor.fields) { mockMapped[field.first, field.second.mapped] = map(value) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index f55923a26..021c93a00 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -13,7 +13,7 @@ import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.util.KfgTargetFilter import org.vorpal.research.kex.util.MockingMode -import org.vorpal.research.kex.util.getMockingMode +import org.vorpal.research.kex.util.mockingMode import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory @@ -27,10 +27,9 @@ import kotlin.random.Random data class Parameters( val instance: T?, val arguments: List, - val statics: Set = setOf(), - val others: Set = setOf() + val statics: Set = setOf() ) { - val asList get() = listOfNotNull(instance) + arguments + statics + others + val asList get() = listOfNotNull(instance) + arguments + statics override fun toString(): String = buildString { appendLine("instance: $instance") @@ -38,12 +37,17 @@ data class Parameters( appendLine("args: ${arguments.joinToString("\n")}") if (statics.isNotEmpty()) appendLine("statics: ${statics.joinToString("\n")}") - if (others.isNotEmpty()) { - appendLine("others: ${others.joinToString("\n")}") - } } } +fun Parameters.map(transform: (T) -> U): Parameters { + return Parameters( + this.instance?.let(transform), + this.arguments.map(transform), + this.statics.mapTo(mutableSetOf(), transform) + ) +} + val Parameters.asDescriptors: Parameters get() { val context = Object2DescriptorConverter() @@ -51,7 +55,6 @@ val Parameters.asDescriptors: Parameters context.convert(instance), arguments.map { context.convert(it) }, statics.mapTo(mutableSetOf()) { context.convert(it) }, - others.mapTo(mutableSetOf()) { context.convert(it) } ) } @@ -63,7 +66,6 @@ fun Parameters.concreteParameters( instance?.concretize(cm, accessLevel, random), arguments.map { it.concretize(cm, accessLevel, random) }, statics.mapTo(mutableSetOf()) { it.concretize(cm, accessLevel, random) }, -// others.mapTo(mutableSetOf()) { it.concretize(cm, accessLevel, random) } ) fun Parameters.filterStaticFinals(cm: ClassManager): Parameters { @@ -81,7 +83,7 @@ fun Parameters.filterStaticFinals(cm: ClassManager): Parameters null } } - return Parameters(instance, arguments, filteredStatics, others) + return Parameters(instance, arguments, filteredStatics) } private val ignoredStatics: Set by lazy { @@ -100,27 +102,18 @@ fun Parameters.filterIgnoredStatic(): Parameters { !ignored.matches(typeName) } } - return Parameters(instance, arguments, filteredStatics, others) + return Parameters(instance, arguments, filteredStatics) } -fun Parameters.generateInitialMocks( +fun createDescriptorToMock( + allDescriptors: Collection, types: TypeFactory -): Pair, Map> { +): Map { val descriptorToMock = mutableMapOf() - val mockedArguments = arguments.replaceUninstantiableWithMocks(types, descriptorToMock).toList() - val mockedStatics = statics.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() - val mockedOthers = others.replaceUninstantiableWithMocks(types, descriptorToMock).toSet() - - return Parameters(instance, mockedArguments, mockedStatics, mockedOthers) to descriptorToMock -} - -private fun Collection.replaceUninstantiableWithMocks( - types: TypeFactory, - descriptorToMock: MutableMap -): Collection { val visited = mutableSetOf() - return map { it.replaceWithMock(types, descriptorToMock, visited) } + allDescriptors.map { it.replaceWithMock(types, descriptorToMock, visited) } + return descriptorToMock } private fun Descriptor.insertMocks( @@ -159,12 +152,25 @@ private fun Descriptor.insertMocks( fun Descriptor.isMockable(types: TypeFactory): Boolean { val klass = (type.getKfgType(types) as? ClassType)?.klass ?: return false val necessaryConditions = !klass.isFinal && !type.isKexRt && this is ObjectDescriptor - return necessaryConditions && when (getMockingMode()) { + return necessaryConditions && when (kexConfig.mockingMode) { MockingMode.FULL -> true MockingMode.BASIC -> !instantiationManager.isInstantiable(klass) null -> false } +} +fun Descriptor.requireMocks(types: TypeFactory, visited: MutableSet): Boolean { + if (this in visited) return false + if (this.isMockable(types)) return true + visited.add(this) + fun Descriptor.requireMocks() = requireMocks(types, visited) + return when (this) { + is ConstantDescriptor -> false + is ClassDescriptor -> fields.values.any { it.requireMocks() } + is ObjectDescriptor -> fields.values.any { it.requireMocks() } + is MockDescriptor -> (fields.values + allReturns).any { it.requireMocks() } + is ArrayDescriptor -> elements.values.any { it.requireMocks() } + } } private fun Descriptor.replaceWithMock( @@ -178,14 +184,13 @@ private fun Descriptor.replaceWithMock( return this } + this as ObjectDescriptor val klass = (this.type.getKfgType(types) as? ClassType)?.klass klass ?: return this.also { log.error { "Got null class to mock. Descriptor: $this" } } - val mock = if (this is ObjectDescriptor) { - MockDescriptor(klass.methods, this).also { it.fields.putAll(this.fields) } - } else { - log.warn { "Strange descriptor to mock. Expected ObjectDescriptor. Got: $this" } - MockDescriptor(this.term, this.type as KexClass, klass.methods) - }.also { log.debug { "Created mock descriptor for ${it.term}" } } + + val mock = MockDescriptor(klass.methods, this) + .also { it.fields.putAll(this@replaceWithMock.fields) } + .also { log.debug { "Created mock descriptor for ${it.term}" } } withMocksInserted.add(this) descriptorToMock[this] = mock descriptorToMock[mock] = mock diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt index 03811565e..13bd3f421 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt @@ -76,7 +76,7 @@ internal sealed class DescriptorWrapper { ) : DescriptorWrapper() { override fun convert(map: Map, output: MutableMap) { if (id in output) return - val methods = methodReturns.map { (method, _) -> method } + val methods = methodReturns.mapTo(mutableSetOf()) { (method, _) -> method } val instance = descriptor { mock(type as KexClass, methods) }.also { output[id] = it } as MockDescriptor diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index e46142471..90749eb1a 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -2,6 +2,7 @@ package org.vorpal.research.kex.util +import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.ktype.kexType import org.vorpal.research.kfg.Package @@ -155,9 +156,9 @@ fun String.removeMockitoMockSuffix(): String { return if (suffixIndex == -1) { this } else { - if (getLogTypeFix()) { + if (kexConfig.logTypeFix) { log.debug { "MOCKITO_MOCK FIX. value: $this" } - if (getLogStackTraceTypeFix()) { + if (kexConfig.logStackTraceTypeFix) { log.debug { "Stack trace: ${Thread.currentThread().stackTrace.joinToString("\n")}" } } } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index 520cc34fa..c6122fc51 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -9,7 +9,6 @@ import org.vorpal.research.kfg.container.Container import org.vorpal.research.kfg.container.JarContainer import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.log -import org.vorpal.research.kthelper.tryOrNull import java.io.File import java.nio.file.Path import kotlin.io.path.readLines @@ -51,6 +50,36 @@ val Config.libPath: Path? runtimeDepsPath?.resolve(it)?.normalize() } +val Config.isMockingEnabled: Boolean + get() = getBooleanValue("mock", "enabled", false) + +enum class MockingMode { + BASIC, FULL +} + +val Config.mockingMode: MockingMode? + get() = getEnumValue("mock", "mode", ignoreCase = true) + +val Config.isMockitoClassesWorkaroundEnabled: Boolean + get() = getBooleanValue("mock", "mockitoClassesWorkaround", true) + +val Config.logTypeFix: Boolean + get() = getBooleanValue("mock", "logTypeFix", false) + +val Config.logStackTraceTypeFix: Boolean + get() = getBooleanValue("mock", "logStackTraceTypeFix", false) + +val Config.isMockPessimizationEnabled: Boolean + get() = getBooleanValue("mock", "mockPessimizationEnabled", true) + +val Config.mockito: Container? + get() { + val libPath = libPath ?: return null + val mockitoVersion = getStringValue("mock", "mockitoVersion") ?: return null + val mockitoPath = libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath() + return JarContainer(mockitoPath, Package("org.mockito")) + } + fun getRuntime(): Container? { if (!kexConfig.getBooleanValue("kex", "useJavaRuntime", true)) return null val libPath = kexConfig.libPath ?: return null @@ -78,34 +107,6 @@ fun getJunit(): Container? { ) } -fun getMockito(): Container? { - val libPath = kexConfig.libPath ?: return null - val mockitoVersion = kexConfig.getStringValue("mock", "mockitoVersion") ?: return null - val mockitoPath = libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath() - return JarContainer(mockitoPath, Package("org.mockito")) -} - -enum class MockingMode { - BASIC, FULL -} - -fun getMockingMode(): MockingMode? { - val mockingMode = kexConfig.getStringValue("mock", "mode") ?: return null - return tryOrNull { MockingMode.valueOf(mockingMode.uppercase()) } -} - -fun getMockingEnabled(): Boolean { - return kexConfig.getBooleanValue("mock", "enabled", false) -} - -fun getLogTypeFix(): Boolean { - return kexConfig.getBooleanValue("mock", "logTypeFix", false) -} - -fun getLogStackTraceTypeFix(): Boolean { - return kexConfig.getBooleanValue("mock", "logStackTraceTypeFix", false) -} - fun getKexRuntime(): Container? { if (!kexConfig.getBooleanValue("kex", "useKexRuntime", true)) return null val libPath = kexConfig.libPath ?: return null diff --git a/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt b/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt index 7cf54fd68..3726b7a68 100644 --- a/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt +++ b/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt @@ -75,7 +75,7 @@ class WorkerLauncher(args: Array) { kexConfig.compiledCodeDirectory, getJunit()?.path, getIntrinsics()?.path, - getMockito()?.path, + kexConfig.mockito?.path, ) ) { kfgClass -> val instrumenter = SymbolicTraceInstrumenter(classManager) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index e8926e99a..42b6133ce 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -6,6 +6,7 @@ import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.analysis.crash.precondition.ConstraintExceptionPrecondition import org.vorpal.research.kex.asm.manager.MethodManager import org.vorpal.research.kex.asm.util.AccessModifier +import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped @@ -17,13 +18,14 @@ import org.vorpal.research.kex.smt.Result import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery import org.vorpal.research.kex.state.predicate.CallPredicate +import org.vorpal.research.kex.state.predicate.PredicateType import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState -import org.vorpal.research.kex.util.getMockingEnabled -import org.vorpal.research.kex.util.getMockingMode -import org.vorpal.research.kex.util.getMockito +import org.vorpal.research.kex.util.isMockPessimizationEnabled +import org.vorpal.research.kex.util.isMockingEnabled +import org.vorpal.research.kex.util.mockingMode import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log @@ -56,7 +58,7 @@ fun methodCalls( return state.clauses .asSequence() .map { clause -> clause.predicate } - .filter { predicate -> predicate.type.name == "S" } + .filter { predicate -> predicate.type is PredicateType.State } .filterIsInstance() .filter { predicate -> predicate.hasLhv } .map { predicate -> @@ -117,16 +119,26 @@ private fun Parameters.finalizeDescriptors( generator: DescriptorGenerator, state: SymbolicState ): Parameters { - if (!getMockingEnabled() || getMockito() == null || getMockingMode() == null) { + if (!kexConfig.isMockingEnabled || kexConfig.mockingMode == null) { return this } - if (!asList.any { it.isMockable(ctx.types) }) { - return this + val visited = mutableSetOf() + + if (kexConfig.isMockPessimizationEnabled.also{log.debug{"Pessimization: $it"}}) { + if (this.asList.none { it.requireMocks(ctx.types, visited) }) { + return this + } else { + generator.generateAll() + } + } else { + generator.generateAll() + if (generator.allValues.none { it.requireMocks(ctx.types, visited) }) { + return this + } } - generator.generateAll() - val descriptors = Parameters(instance, arguments, statics, generator.others) - val (withMocks, descriptorToMock) = descriptors.generateInitialMocks(ctx.types) + val descriptorToMock = createDescriptorToMock(generator.allValues, ctx.types) + val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = methodCalls(state, generator.memory, descriptorToMock) setupMocks(methodCalls, generator.memory, descriptorToMock) return withMocks diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/Reanimator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/Reanimator.kt index 7e8be513d..b1a0bae1d 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/Reanimator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/Reanimator.kt @@ -35,8 +35,7 @@ val Parameters.rtMapped: Parameters val instance = instance?.let { mapper.map(it) } val args = arguments.map { mapper.map(it) } val statics = statics.mapTo(mutableSetOf()) { mapper.map(it) } - val others = others.mapTo(mutableSetOf()) { mapper.map(it) } - return Parameters(instance, args, statics, others) + return Parameters(instance, args, statics) } val Parameters.rtUnmapped: Parameters @@ -45,8 +44,7 @@ val Parameters.rtUnmapped: Parameters val instance = instance?.let { mapper.map(it) } val args = arguments.map { mapper.map(it) } val statics = statics.mapTo(mutableSetOf()) { mapper.map(it) } - val others = others.mapTo(mutableSetOf()) { mapper.map(it) } - return Parameters(instance, args, statics, others) + return Parameters(instance, args, statics) } class Reanimator( diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt index c2a1a7827..be6270a7c 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/AbstractGenerator.kt @@ -45,7 +45,7 @@ interface AbstractGenerator : Transformer> { val instance get() = thisTerm?.let { memory[it] } val args get() = argTerms.map { memory[it.value] } val staticFields get() = staticFieldOwners.mapTo(mutableSetOf()) { memory[it]!! } - val others get() = memory.values.filterNotTo(mutableSetOf()) { it == instance && it in staticFields && it in args } + val allValues: Collection get() = memory.values fun generateThis() = thisTerm?.let { memory[it] = modelReanimator.reanimate(it) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index 0980c18d2..233aaf9f2 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -138,14 +138,12 @@ fun generateInitialDescriptors( ): Pair, DescriptorGenerator> { val generator = DescriptorGenerator(method, ctx, model, InitialDescriptorReanimator(model, ctx)) generator.apply(state) -// generator.generateAll() return Parameters( generator.instance, generator.args.mapIndexed { index, arg -> arg ?: descriptor { default(method.argTypes[index].kexType) } }, - generator.staticFields, - generator.others + generator.staticFields ) to generator } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/ObjectTracingRunner.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/ObjectTracingRunner.kt index 895403051..a1204358b 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/ObjectTracingRunner.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/ObjectTracingRunner.kt @@ -80,8 +80,7 @@ class ReanimatingRandomObjectTracingRunner( get() = with(Object2DescriptorConverter()) { Parameters(convert(instance), arguments.map { convert(it) }, - statics.mapTo(mutableSetOf()) { convert(it) }, - others.mapTo(mutableSetOf()) { convert(it) }) + statics.mapTo(mutableSetOf()) { convert(it) }) } override fun generateArguments(): Parameters? { @@ -90,14 +89,14 @@ class ReanimatingRandomObjectTracingRunner( log.error("Cannot generate parameters to invoke method $method") return null } - val parameters = Parameters(randomInstance, randomArgs.toList(), setOf(), setOf()) + val parameters = Parameters(randomInstance, randomArgs.toList(), setOf()) val (instance, args) = with(reanimator) { val descriptors = parameters.descriptors val callStacks = descriptors.actionSequences printer.print("test_$testCounter", method, callStacks) callStacks.executed } - return Parameters(instance, args, setOf(), setOf()) + return Parameters(instance, args, setOf()) } fun emit() { diff --git a/kex-test.ini b/kex-test.ini index 891fa8ee5..fc42dca5c 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -24,6 +24,7 @@ surroundInTryCatch = false ignoreInstantiation = package org.junit.* ignoreInstantiation = package org.objenesis.* ignoreInstantiation = package net.bytebuddy.* +ignoreInstantiation = package org.mockito.* [mock] enabled = true @@ -32,7 +33,11 @@ mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible -logTypeFix = true +mockitoClassesWorkaround = true + +mockPessimizationEnabled = false + +logTypeFix = false logStackTraceTypeFix = false [reanimator] diff --git a/kex.ini b/kex.ini index 5977387d4..fc81b4a2c 100644 --- a/kex.ini +++ b/kex.ini @@ -48,7 +48,11 @@ mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible -logTypeFix = true +mockitoClassesWorkaround = true + +mockPessimizationEnabled = false + +logTypeFix = false logStackTraceTypeFix = false [reanimator] diff --git a/pom.xml b/pom.xml index d49712634..40ed31160 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,8 @@ 1.7.36 1.5.0 + 4.11.0 + -XX:+IgnoreUnrecognizedVMOptions --add-opens=java.base/java.lang=ALL-UNNAMED @@ -90,7 +92,7 @@ org.mockito mockito-core - 4.11.0 + ${mockito.version} From 44b051ed7bca0c50092a4253b0f852fa95edbe9d Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 1 Feb 2024 20:05:02 +0100 Subject: [PATCH 069/128] Refactoring --- .../research/kex/parameters/Parameters.kt | 9 +++-- .../kotlin/org/vorpal/research/kex/util/rt.kt | 5 +++ .../kex/asm/analysis/util/extensions.kt | 36 ++++--------------- kex-test.ini | 5 ++- kex.ini | 5 ++- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 021c93a00..bb303ff10 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -199,15 +199,18 @@ private fun Descriptor.replaceWithMock( } fun setupMocks( - methodCalls: List>, + methodCalls: List, termToDescriptor: Map, descriptorToMock: Map ) { - for ((callPredicate, value) in methodCalls) { + for (callPredicate in methodCalls) { val call = callPredicate.call as CallTerm + val mock = termToDescriptor[call.owner]?.let { descriptorToMock[it] ?: it } + val value = termToDescriptor[callPredicate.lhvUnsafe]?.let { descriptorToMock[it] ?: it } mock ?: log.warn { "No mock for $call" } - if (mock is MockDescriptor) { + + if (mock is MockDescriptor && value != null) { mock.addReturnValue(call.method, value) } } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index c6122fc51..b8b3325c9 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -80,6 +80,11 @@ val Config.mockito: Container? return JarContainer(mockitoPath, Package("org.mockito")) } +// debug purposes, normally should be false +val Config.isMockTest: Boolean + get() = getBooleanValue("mock", "test", false).also{if (it) println("Test feature invoked!")} + + fun getRuntime(): Container? { if (!kexConfig.getBooleanValue("kex", "useJavaRuntime", true)) return null val libPath = kexConfig.libPath ?: return null diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 42b6133ce..448bd8105 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -18,14 +18,10 @@ import org.vorpal.research.kex.smt.Result import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery import org.vorpal.research.kex.state.predicate.CallPredicate -import org.vorpal.research.kex.state.predicate.PredicateType -import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState -import org.vorpal.research.kex.util.isMockPessimizationEnabled -import org.vorpal.research.kex.util.isMockingEnabled -import org.vorpal.research.kex.util.mockingMode +import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log @@ -50,27 +46,8 @@ suspend fun Method.analyzeOrTimeout( } -fun methodCalls( - state: SymbolicState, - termToDescriptor: Map, - descriptorToMock: Map -): List> { - return state.clauses - .asSequence() - .map { clause -> clause.predicate } - .filter { predicate -> predicate.type is PredicateType.State } - .filterIsInstance() - .filter { predicate -> predicate.hasLhv } - .map { predicate -> - val term = predicate.lhv - val descriptor = termToDescriptor[term] - if (descriptor == null) log.debug { "Error. No descriptor for $term, type: ${term.type}\n" } - predicate to descriptor - } - .filter { (_, value) -> value != null } - .map { (call, value) -> call to value!! } - .map { (call, value) -> call to (descriptorToMock[value] ?: value) } - .toList() +fun SymbolicState.methodCalls(): List { + return clauses.map { clause -> clause.predicate }.filterIsInstance() } @@ -86,7 +63,8 @@ suspend fun Method.checkAsync( .filterValues { it.isJavaRt } .mapValues { it.value.rtMapped } .toTypeMap() - val result = checker.prepareAndCheck(this, clauses + query, concreteTypeInfo, enableInlining) + val result = + checker.prepareAndCheck(this, clauses + query, concreteTypeInfo, enableInlining) if (result !is Result.SatResult) { return null } @@ -124,7 +102,7 @@ private fun Parameters.finalizeDescriptors( } val visited = mutableSetOf() - if (kexConfig.isMockPessimizationEnabled.also{log.debug{"Pessimization: $it"}}) { + if (kexConfig.isMockPessimizationEnabled.also { log.debug { "Pessimization: $it" } }) { if (this.asList.none { it.requireMocks(ctx.types, visited) }) { return this } else { @@ -139,7 +117,7 @@ private fun Parameters.finalizeDescriptors( val descriptorToMock = createDescriptorToMock(generator.allValues, ctx.types) val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } - val methodCalls = methodCalls(state, generator.memory, descriptorToMock) + val methodCalls = state.methodCalls() setupMocks(methodCalls, generator.memory, descriptorToMock) return withMocks } diff --git a/kex-test.ini b/kex-test.ini index fc42dca5c..1f5383a0f 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -35,7 +35,10 @@ mode = basic mockitoClassesWorkaround = true -mockPessimizationEnabled = false +mockPessimizationEnabled = true + +test = true +; normally should be missing or false logTypeFix = false logStackTraceTypeFix = false diff --git a/kex.ini b/kex.ini index fc81b4a2c..f5865dfab 100644 --- a/kex.ini +++ b/kex.ini @@ -48,9 +48,12 @@ mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible +test = true +; normally should be missing or false + mockitoClassesWorkaround = true -mockPessimizationEnabled = false +mockPessimizationEnabled = true logTypeFix = false logStackTraceTypeFix = false From ada7f1e9f323ab0a7fa4613abe3c464ff5748a01 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 8 Feb 2024 19:47:07 +0100 Subject: [PATCH 070/128] Fixed instance mocking --- .../kex/asm/analysis/util/extensions.kt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 448bd8105..b538a81ea 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -100,22 +100,20 @@ private fun Parameters.finalizeDescriptors( if (!kexConfig.isMockingEnabled || kexConfig.mockingMode == null) { return this } + fun Collection.removeInstance() = this.filterNot { it == instance } val visited = mutableSetOf() - if (kexConfig.isMockPessimizationEnabled.also { log.debug { "Pessimization: $it" } }) { - if (this.asList.none { it.requireMocks(ctx.types, visited) }) { - return this - } else { - generator.generateAll() - } - } else { - generator.generateAll() - if (generator.allValues.none { it.requireMocks(ctx.types, visited) }) { + if (kexConfig.isMockPessimizationEnabled) { + if (this.asList.removeInstance().none { it.requireMocks(ctx.types, visited) }) { return this } } + generator.generateAll() + if (generator.allValues.removeInstance().none { it.requireMocks(ctx.types, visited) }) { + return this + } - val descriptorToMock = createDescriptorToMock(generator.allValues, ctx.types) + val descriptorToMock = createDescriptorToMock(generator.allValues.removeInstance(), ctx.types) val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = state.methodCalls() setupMocks(methodCalls, generator.memory, descriptorToMock) From 15b831754352cd736c847b53b56a7fdd177f1726 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 10 Feb 2024 23:13:12 +0100 Subject: [PATCH 071/128] Refactoring --- kex-core/pom.xml | 5 ----- kex-executor/pom.xml | 2 +- kex-runner/pom.xml | 2 +- kex-test/pom.xml | 3 ++- pom.xml | 5 ----- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/kex-core/pom.xml b/kex-core/pom.xml index 2c78cfab9..12d972577 100644 --- a/kex-core/pom.xml +++ b/kex-core/pom.xml @@ -98,11 +98,6 @@ easy-random-core ${easy-random.version} - - org.mockito - mockito-core - 4.11.0 - diff --git a/kex-executor/pom.xml b/kex-executor/pom.xml index 6cdf4911a..70e1cc51e 100644 --- a/kex-executor/pom.xml +++ b/kex-executor/pom.xml @@ -55,7 +55,7 @@ org.mockito mockito-core - 4.11.0 + ${mockito.version} diff --git a/kex-runner/pom.xml b/kex-runner/pom.xml index fb2437925..bbc43f80f 100644 --- a/kex-runner/pom.xml +++ b/kex-runner/pom.xml @@ -113,7 +113,7 @@ org.mockito mockito-core - 4.11.0 + ${mockito.version} diff --git a/kex-test/pom.xml b/kex-test/pom.xml index 839d3f820..e8c3ab15b 100644 --- a/kex-test/pom.xml +++ b/kex-test/pom.xml @@ -18,6 +18,7 @@ 1.6.21 3.5.1 0.1.0 + 4.11.0 @@ -40,7 +41,7 @@ org.mockito mockito-core - 4.11.0 + ${mockito.version} diff --git a/pom.xml b/pom.xml index 40ed31160..4d33cdce2 100644 --- a/pom.xml +++ b/pom.xml @@ -89,11 +89,6 @@ kotlinx-collections-immutable-jvm ${collections.version} - - org.mockito - mockito-core - ${mockito.version} - From 439f1a766e8dab654c263c3a9c97ffee0e8f1f79 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 12 Feb 2024 12:39:19 +0100 Subject: [PATCH 072/128] Print import Mockito only when need --- .../kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 8ebf0d709..bb6ed127c 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -21,7 +21,7 @@ class ExecutorAS2JavaPrinter( private val surroundInTryCatch = kexConfig.getBooleanValue("testGen", "surroundInTryCatch", true) private val testParams = mutableListOf() private val reflectionUtils = ReflectionUtilsPrinter.reflectionUtils(packageName) - private val mockUtils = MockUtilsPrinter.mockUtils(packageName) + private val mockUtils by lazy { MockUtilsPrinter.mockUtils(packageName) } private val printedDeclarations = hashSetOf() private val printedInsides = hashSetOf() @@ -62,7 +62,6 @@ class ExecutorAS2JavaPrinter( import("java.lang.reflect.Constructor") import("java.lang.reflect.Field") import("java.lang.reflect.Array") - import("org.mockito.Mockito") // TODO. Mock: make configurable importStatic("${reflectionUtils.klass.pkg}.${reflectionUtils.klass.name}.*") with(klass) { @@ -474,6 +473,7 @@ class ExecutorAS2JavaPrinter( } override fun printMockNewInstance(owner: ActionSequence, call: MockNewInstance): List { + builder.import("org.mockito.Mockito") val actualType = ASClass(ctx.types.objectType) val kfgClass = call.klass return listOf( From fe4af9fec9e65d77c902c136221b2ee0e026e2f7 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 13 Feb 2024 14:55:55 +0100 Subject: [PATCH 073/128] Print methods of MockDescriptor only if they contain return values --- .../research/kex/descriptor/descriptor.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index c22dfc886..ed03abfea 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -717,15 +717,16 @@ class MockDescriptor(term: Term, type: KexClass, methods: Set = emptySet override fun print(map: MutableMap): String { if (this in map) return map[this]!! val base = super.print(map) - return base + "\n" + methodReturns.joinToString(separator = "\n") { method, values -> - "$method : ${ - values.joinToString( - separator = ", ", - prefix = "{", - postfix = "}" - ) { value -> value.print(map) } - }" - } + return base + "\n" + methodReturns.filter { (_, values) -> values.isNotEmpty() } + .joinToString(separator = "\n") { method, values -> + "$method : ${ + values.joinToString( + separator = ", ", + prefix = "{", + postfix = "}" + ) { value -> value.print(map) } + }" + } } From 1eee35774ee41e94b8a5e4341d12b74b5960c3fa Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 13 Feb 2024 15:32:27 +0100 Subject: [PATCH 074/128] Methods in MockDescriptor now may be changed after construction --- .../research/kex/descriptor/descriptor.kt | 141 ++++++++++++------ .../research/kex/parameters/Parameters.kt | 4 +- .../research/kex/serialization/descriptor.kt | 3 +- .../kex/serialization/KexSerializerTest.kt | 2 +- 4 files changed, 99 insertions(+), 51 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index ed03abfea..87f489d21 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -15,10 +15,7 @@ import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.util.StringInfoContext import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.Method -import org.vorpal.research.kfg.type.ClassType -import org.vorpal.research.kfg.type.TypeFactory import org.vorpal.research.kthelper.assert.unreachable -import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import ru.spbstu.wheels.joinToString import kotlin.random.Random @@ -56,7 +53,11 @@ sealed class Descriptor(term: Term, type: KexType) { infix fun neq(other: Descriptor) = !(this eq other) abstract fun print(map: MutableMap): String - abstract fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean + abstract fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean + abstract fun collectQuery(set: MutableSet): PredicateState abstract fun collectInitializerState(set: MutableSet): PredicateState @@ -88,7 +89,8 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty override fun generateTypeInfo(visited: MutableSet) = emptyState() override fun countDepth(visited: Set, cache: MutableMap) = 1 - override fun contains(other: Descriptor, visited: MutableSet): Boolean = this.term == other.term + override fun contains(other: Descriptor, visited: MutableSet): Boolean = + this.term == other.term object Null : ConstantDescriptor(term { generate(KexNull()) }, KexNull()) { override fun print(map: MutableMap) = "$term = null" @@ -101,7 +103,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty state { term equality null } } - override fun structuralEquality(other: Descriptor, map: MutableSet>) = + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ) = other is Null } @@ -116,7 +121,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty state { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Bool) return false return this.value == other.value } @@ -133,7 +141,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty state { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Byte) return false return this.value == other.value } @@ -150,13 +161,17 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty state { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Char) return false return this.value == other.value } } - class Short(val value: kotlin.Short) : ConstantDescriptor(term { generate(KexShort) }, KexShort) { + class Short(val value: kotlin.Short) : + ConstantDescriptor(term { generate(KexShort) }, KexShort) { override fun print(map: MutableMap) = "$term = $value" override fun collectQuery(set: MutableSet): PredicateState = basic { @@ -167,7 +182,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty state { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Short) return false return this.value == other.value } @@ -184,7 +202,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty state { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Int) return false return this.value == other.value } @@ -201,13 +222,17 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty state { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Long) return false return this.value == other.value } } - class Float(val value: kotlin.Float) : ConstantDescriptor(term { generate(KexFloat) }, KexFloat) { + class Float(val value: kotlin.Float) : + ConstantDescriptor(term { generate(KexFloat) }, KexFloat) { override fun print(map: MutableMap) = "$term = $value" override fun collectQuery(set: MutableSet): PredicateState = basic { @@ -218,13 +243,17 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty state { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Float) return false return this.value == other.value } } - class Double(val value: kotlin.Double) : ConstantDescriptor(term { generate(KexDouble) }, KexDouble) { + class Double(val value: kotlin.Double) : + ConstantDescriptor(term { generate(KexDouble) }, KexDouble) { override fun print(map: MutableMap) = "$term = $value" override fun collectQuery(set: MutableSet): PredicateState = basic { @@ -235,7 +264,10 @@ sealed class ConstantDescriptor(term: Term, type: KexType) : Descriptor(term, ty state { term equality value } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (other !is Double) return false return this.value == other.value } @@ -275,6 +307,7 @@ sealed class AbstractFieldContainingDescriptor( fields[field] = value.concretize(cm, accessLevel, random, visited) } } + protected fun reduceFields(visited: MutableSet) { for ((field, value) in fields.toMap()) { when { @@ -338,6 +371,7 @@ sealed class AbstractFieldContainingDescriptor( visited += this return fields.values.any { it.contains(other, visited) } } + override fun generateTypeInfo(visited: MutableSet): PredicateState { if (this in visited) return emptyState() visited += this @@ -350,13 +384,17 @@ sealed class AbstractFieldContainingDescriptor( val typeInfo = field.generateTypeInfo(visited) if (typeInfo.isNotEmpty) { state { - field.term equality this@AbstractFieldContainingDescriptor.term.field(key.second, key.first).load() + field.term equality this@AbstractFieldContainingDescriptor.term.field( + key.second, + key.first + ).load() } append(typeInfo) } } } } + override fun countDepth(visited: Set, cache: MutableMap): Int { if (this in cache) return cache[this]!! if (this in visited) return 0 @@ -417,7 +455,10 @@ sealed class FieldContainingDescriptor>( return this as T } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (this == other) return true if (other !is FieldContainingDescriptor<*>) return false if (this to other in map) return true @@ -467,7 +508,8 @@ class ClassDescriptor(type: KexClass) : val typeInfo = field.generateTypeInfo(visited) if (typeInfo.isNotEmpty) { state { - field.term equality this@ClassDescriptor.term.field(key.second, key.first).load() + field.term equality this@ClassDescriptor.term.field(key.second, key.first) + .load() } append(typeInfo) } @@ -545,7 +587,8 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : if (this in set) return emptyState() set += this return basic { - val fullElements = (0 until length).map { elements[it] ?: descriptor { default(elementType) } } + val fullElements = + (0 until length).map { elements[it] ?: descriptor { default(elementType) } } fullElements.forEach { append(it.collectInitializerState(set)) } state { term.initializeNew(length, fullElements.map { it.term }) } } @@ -615,7 +658,10 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : } } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (this == other) return true if (other !is ArrayDescriptor) return false if (this to other in map) return true @@ -645,22 +691,15 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : } } -class MockDescriptor(term: Term, type: KexClass, methods: Set = emptySet()) : +class MockDescriptor(term: Term, type: KexClass) : AbstractFieldContainingDescriptor(term, type) { - constructor(type: KexClass, methods: Set) : this(term { generate(type) }, type, methods) - constructor(methods: Set, original: ObjectDescriptor) : this( - original.term, - original.type as KexClass, - methods - ) { + constructor(type: KexClass) : this(term { generate(type) }, type) + constructor(original: ObjectDescriptor) : this(original.term, original.type as KexClass) { fields.putAll(original.fields) } - val methodReturns: Map> = - mutableMapOf>().apply { - methods.forEach { method -> put(method, mutableListOf()) } - } + val methodReturns: MutableMap> = mutableMapOf() val allReturns: Iterable get() = methodReturns.values.asSequence().flatMap { it.asSequence() }.asIterable() @@ -670,12 +709,11 @@ class MockDescriptor(term: Term, type: KexClass, methods: Set = emptySet operator fun get(method: Method) = methodReturns[method] operator fun set(method: Method, values: List) { - methodReturns[method]?.clear() - methodReturns[method]?.addAll(values) + methodReturns[method] = values.toMutableList() } fun addReturnValue(method: Method, value: Descriptor) { - methodReturns[method]?.add(value) ?: log.debug { "Error. No method $method in mockDescriptor $this" } + methodReturns.getOrPut(method) { mutableListOf() }.add(value) } override fun concretize( @@ -688,7 +726,14 @@ class MockDescriptor(term: Term, type: KexClass, methods: Set = emptySet visited += this concretizeFields(cm, accessLevel, random, visited) for (list in methodReturns.values) { - list.replaceAll { descriptor -> descriptor.concretize(cm, accessLevel, random, visited) } + list.replaceAll { descriptor -> + descriptor.concretize( + cm, + accessLevel, + random, + visited + ) + } } return this } @@ -711,7 +756,12 @@ class MockDescriptor(term: Term, type: KexClass, methods: Set = emptySet if (this in visited) return false if (this == other) return true visited += this - return allReturns.any { it.contains(other, visited) } || fields.values.any { it.contains(other, visited) } + return allReturns.any { it.contains(other, visited) } || fields.values.any { + it.contains( + other, + visited + ) + } } override fun print(map: MutableMap): String { @@ -745,7 +795,10 @@ class MockDescriptor(term: Term, type: KexClass, methods: Set = emptySet return maxDepth + 1 } - override fun structuralEquality(other: Descriptor, map: MutableSet>): Boolean { + override fun structuralEquality( + other: Descriptor, + map: MutableSet> + ): Boolean { if (this == other) return true if (other !is MockDescriptor) return false if (this to other in map) return true @@ -775,7 +828,7 @@ class MockDescriptor(term: Term, type: KexClass, methods: Set = emptySet override fun deepCopy(copied: MutableMap): Descriptor { if (this in copied) return copied[this]!! - val copy = MockDescriptor(term, type as KexClass, methodReturns.keys) + val copy = MockDescriptor(term, type as KexClass) copied[this] = copy for ((method, list) in methodReturns) { @@ -825,9 +878,7 @@ open class DescriptorBuilder : StringInfoContext() { fun array(length: Int, elementType: KexType): ArrayDescriptor = ArrayDescriptor(elementType, length) - fun mock(type: KexClass, methods: Set) = MockDescriptor(type, methods) - fun mock(type: KexClass, types: TypeFactory): MockDescriptor = - MockDescriptor(type, (type.getKfgType(types) as ClassType).klass.methods) + fun mock(type: KexClass) = MockDescriptor(type) fun default(type: KexType, nullable: Boolean): Descriptor = descriptor { when (type) { @@ -926,8 +977,8 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde val mockMapped = MockDescriptor( descriptor.term, - descriptor.type.mapped as KexClass, - descriptor.methods.mapTo(mutableSetOf()) { method -> method.mapped }) + descriptor.type.mapped as KexClass + ) cache[descriptor] = mockMapped for ((field, value) in descriptor.fields) { mockMapped[field.first, field.second.mapped] = map(value) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index bb303ff10..f6dc6e2a2 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -188,9 +188,7 @@ private fun Descriptor.replaceWithMock( val klass = (this.type.getKfgType(types) as? ClassType)?.klass klass ?: return this.also { log.error { "Got null class to mock. Descriptor: $this" } } - val mock = MockDescriptor(klass.methods, this) - .also { it.fields.putAll(this@replaceWithMock.fields) } - .also { log.debug { "Created mock descriptor for ${it.term}" } } + val mock = MockDescriptor(this).also { log.debug { "Created mock descriptor for ${it.term}" } } withMocksInserted.add(this) descriptorToMock[this] = mock descriptorToMock[mock] = mock diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt index 13bd3f421..993c60f33 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt @@ -76,8 +76,7 @@ internal sealed class DescriptorWrapper { ) : DescriptorWrapper() { override fun convert(map: Map, output: MutableMap) { if (id in output) return - val methods = methodReturns.mapTo(mutableSetOf()) { (method, _) -> method } - val instance = descriptor { mock(type as KexClass, methods) }.also { + val instance = descriptor { mock(type as KexClass) }.also { output[id] = it } as MockDescriptor diff --git a/kex-core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt b/kex-core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt index 55178a336..1e4ebbf01 100644 --- a/kex-core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt +++ b/kex-core/src/test/kotlin/org/vorpal/research/kex/serialization/KexSerializerTest.kt @@ -183,7 +183,7 @@ class KexSerializerTest : KexTest("kex-serializer") { return with(DescriptorBuilder()) { val klass = KexClass("java/util/stream/Stream") val toArrayMethod = (klass.getKfgType(cm.type) as ClassType).klass.methods.first { it.name == "toArray" } - val instance = mock(klass, cm.type) + val instance = mock(klass) instance.addReturnValue(toArrayMethod, convertToDescriptor(Array(2) { Any() })) instance["selfReference" to klass] = instance instance["string" to cm.stringClass.kexType] = convertToDescriptor("test") From cec9a3d0651d2252247942533bc3cad127026d59 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 14 Feb 2024 15:29:29 +0100 Subject: [PATCH 075/128] Fix done --- .../research/kex/descriptor/descriptor.kt | 51 +++++++-- .../research/kex/serialization/descriptor.kt | 4 +- .../actionsequence/ActionSequence.kt | 19 +++- .../actionsequence/generator/MockGenerator.kt | 6 +- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 104 ++++++++++++------ .../codegen/javagen/MockUtilsPrinter.kt | 49 ++++++++- 6 files changed, 178 insertions(+), 55 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 87f489d21..700beaec8 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -2,6 +2,7 @@ package org.vorpal.research.kex.descriptor +import kotlinx.serialization.Serializable import org.vorpal.research.kex.asm.manager.instantiationManager import org.vorpal.research.kex.asm.util.AccessModifier import org.vorpal.research.kex.ktype.* @@ -691,6 +692,17 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : } } + +@Serializable +data class MethodId( + val name: String, + val paramTypes: List, + val returnType: KexType +) {} + + +private val Method.id: MethodId get() = MethodId(name, argTypes.map{it.kexType}, returnType.kexType) + class MockDescriptor(term: Term, type: KexClass) : AbstractFieldContainingDescriptor(term, type) { @@ -699,21 +711,30 @@ class MockDescriptor(term: Term, type: KexClass) : fields.putAll(original.fields) } - val methodReturns: MutableMap> = mutableMapOf() + val methodReturns: MutableMap> = mutableMapOf() val allReturns: Iterable get() = methodReturns.values.asSequence().flatMap { it.asSequence() }.asIterable() - val methods: Set + val methods: Set get() = methodReturns.keys - operator fun get(method: Method) = methodReturns[method] + operator fun get(method: Method) = get(method.id) + operator fun get(methodId: MethodId) = methodReturns[methodId] operator fun set(method: Method, values: List) { - methodReturns[method] = values.toMutableList() + methodReturns[method.id] = values.toMutableList() + } + + operator fun set(methodId: MethodId, values: List) { + methodReturns[methodId] = values.toMutableList() } fun addReturnValue(method: Method, value: Descriptor) { - methodReturns.getOrPut(method) { mutableListOf() }.add(value) + addReturnValue(method.id, value) + } + + internal fun addReturnValue(methodId: MethodId, value: Descriptor) { + methodReturns.getOrPut(methodId) { mutableListOf() }.add(value) } override fun concretize( @@ -743,7 +764,7 @@ class MockDescriptor(term: Term, type: KexClass) : visited += this reduceFields(visited) for ((method, list) in methodReturns) { - val type = method.returnType.kexType + val type = method.returnType while (list.isNotEmpty() && list.last() eq descriptor { default(type) }) { list.removeLast() } @@ -832,7 +853,8 @@ class MockDescriptor(term: Term, type: KexClass) : copied[this] = copy for ((method, list) in methodReturns) { - list.forEach { value -> copy.addReturnValue(method, value.deepCopy(copied)) } + copy.methodReturns[method] = + list.mapTo(mutableListOf()) { value -> value.deepCopy(copied) } } for ((field, value) in fields) { copy[field] = value.deepCopy(copied) @@ -937,10 +959,19 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde KexRtManager.Mode.UNMAP -> rtUnmapped } - private val Method.mapped + private val MethodId.mapped get() = when (mode) { - KexRtManager.Mode.MAP -> rtMapped - KexRtManager.Mode.UNMAP -> rtUnmapped + KexRtManager.Mode.MAP -> MethodId( + name.rtMapped, + paramTypes.map { it.rtMapped }, + returnType.rtMapped + ) + + KexRtManager.Mode.UNMAP -> MethodId( + name.rtUnmapped, + paramTypes.map { it.rtUnmapped }, + returnType.rtUnmapped + ) } fun map(descriptor: Descriptor): Descriptor = cache.getOrElse(descriptor) { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt index 993c60f33..48270c970 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt @@ -1,6 +1,5 @@ package org.vorpal.research.kex.serialization -import kotlinx.serialization.Contextual import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException @@ -15,7 +14,6 @@ import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexArray import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexType -import org.vorpal.research.kfg.ir.Method @JvmInline @Serializable @@ -72,7 +70,7 @@ internal sealed class DescriptorWrapper { override val id: Id, override val type: KexType, val fields: MutableList, Id>>, - val methodReturns: MutableList>> + val methodReturns: MutableList>> ) : DescriptorWrapper() { override fun convert(map: Map, output: MutableMap) { if (id in output) return diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt index be38112fd..1d70585d4 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt @@ -2,6 +2,7 @@ package org.vorpal.research.kex.reanimator.actionsequence import org.vorpal.research.kex.descriptor.Descriptor import org.vorpal.research.kex.descriptor.DescriptorRtMapper +import org.vorpal.research.kex.descriptor.MethodId import org.vorpal.research.kex.ktype.KexRtManager import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.KexRtManager.rtUnmapped @@ -233,7 +234,7 @@ data class MockNewInstance(val klass: Class) : MockCall { } data class MockSetupMethod( - val method: Method, val returnValues: List + val method: MethodId, val returnValues: List ) : MockCall { override val parameters: List get() = returnValues @@ -597,12 +598,22 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { KexRtManager.Mode.UNMAP -> rtUnmapped } - private val Method.mapped + private val MethodId.mapped get() = when (mode) { - KexRtManager.Mode.MAP -> rtMapped - KexRtManager.Mode.UNMAP -> rtUnmapped + KexRtManager.Mode.MAP -> MethodId( + name.rtMapped, + paramTypes.map { it.rtMapped }, + returnType.rtMapped + ) + + KexRtManager.Mode.UNMAP -> MethodId( + name.rtUnmapped, + paramTypes.map { it.rtUnmapped }, + returnType.rtUnmapped + ) } + private val Type.mapped get() = when (mode) { KexRtManager.Mode.MAP -> rtMapped diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt index 6475ac6f1..e554c9ca2 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt @@ -18,7 +18,7 @@ class MockGenerator(private val fallback: Generator) : Generator { override fun generate(descriptor: Descriptor, generationDepth: Int): ActionSequence = with(context) { descriptor as? MockDescriptor ?: throw IllegalArgumentException("Expected MockDescriptor. Got: $descriptor") - descriptor as MockDescriptor // TODO remove. Helps autocompletion + descriptor as MockDescriptor // helps autocompletion val name = "${descriptor.term}" val actionSequence = MockSequence(name) @@ -27,10 +27,6 @@ class MockGenerator(private val fallback: Generator) : Generator { actionSequence.mockCalls.add(MockNewInstance(kfgClass)) for ((method, returnValuesDesc) in descriptor.methodReturns) { - if (method !in kfgClass.methods) { - log.warn("Method $method is not found in class $kfgClass") - continue - } val returnValues = returnValuesDesc.map { value -> fallback.generate(value) } actionSequence.mockCalls += MockSetupMethod(method, returnValues) } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index bb6ed127c..175c3b945 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -18,10 +18,14 @@ class ExecutorAS2JavaPrinter( klassName: String, private val setupName: String ) : ActionSequence2JavaPrinter(ctx, packageName, klassName) { - private val surroundInTryCatch = kexConfig.getBooleanValue("testGen", "surroundInTryCatch", true) + private val surroundInTryCatch = + kexConfig.getBooleanValue("testGen", "surroundInTryCatch", true) private val testParams = mutableListOf() private val reflectionUtils = ReflectionUtilsPrinter.reflectionUtils(packageName) - private val mockUtils by lazy { MockUtilsPrinter.mockUtils(packageName) } + private val mockUtils by lazy { + MockUtilsPrinter.mockUtils(packageName) + .also { builder.importStatic("$packageName.MockUtils.*") } + } private val printedDeclarations = hashSetOf() private val printedInsides = hashSetOf() @@ -62,6 +66,11 @@ class ExecutorAS2JavaPrinter( import("java.lang.reflect.Constructor") import("java.lang.reflect.Field") import("java.lang.reflect.Array") + + import("java.util.Arrays") + import("java.util.stream.Stream") + import("java.util.stream.Collectors") + importStatic("${reflectionUtils.klass.pkg}.${reflectionUtils.klass.name}.*") with(klass) { @@ -108,7 +117,8 @@ class ExecutorAS2JavaPrinter( is StringValue -> return@forEach else -> unreachable { log.error("Unexpected call in arg") } } - val fieldType = type.kexType.primitiveName?.let { type(it) } ?: type("Object") + val fieldType = + type.kexType.primitiveName?.let { type(it) } ?: type("Object") if (testParams.all { it.name != arg.name }) { testParams += field(arg.name, fieldType) } @@ -275,7 +285,12 @@ class ExecutorAS2JavaPrinter( for (mockCall in owner.mockCalls) { when (mockCall) { is MockNewInstance -> result += printMockNewInstance(owner, mockCall) - is MockSetupMethod -> mockCall.returnValues.forEach { printDeclarations(it, result) } + is MockSetupMethod -> mockCall.returnValues.forEach { + printDeclarations( + it, + result + ) + } } } for (reflectionCall in owner.reflectionCalls) { @@ -283,7 +298,10 @@ class ExecutorAS2JavaPrinter( } } - private fun printReflectionListDeclarations(owner: ReflectionList, result: MutableList) { + private fun printReflectionListDeclarations( + owner: ReflectionList, + result: MutableList + ) { if (owner.name in printedDeclarations) return printedDeclarations += owner.name @@ -307,13 +325,14 @@ class ExecutorAS2JavaPrinter( } - private fun printInsides(owner: ActionSequence, result: MutableList): Unit = when (owner) { - is ReflectionList -> printReflectionListInsides(owner, result) - is MockSequence -> printMockSequenceInsides(owner, result) - else -> { - owner.printAsJava() + private fun printInsides(owner: ActionSequence, result: MutableList): Unit = + when (owner) { + is ReflectionList -> printReflectionListInsides(owner, result) + is MockSequence -> printMockSequenceInsides(owner, result) + else -> { + owner.printAsJava() + } } - } private fun printMockSequenceInsides( owner: MockSequence, @@ -374,7 +393,10 @@ class ExecutorAS2JavaPrinter( } } - override fun printReflectionNewInstance(owner: ActionSequence, call: ReflectionNewInstance): List { + override fun printReflectionNewInstance( + owner: ActionSequence, + call: ReflectionNewInstance + ): List { val actualType = ASClass(ctx.types.objectType) val kfgClass = (call.type as ClassType).klass return listOf( @@ -407,7 +429,10 @@ class ExecutorAS2JavaPrinter( else -> "Class.forName(\"java.lang.Object\")" } - override fun printReflectionNewArray(owner: ActionSequence, call: ReflectionNewArray): List { + override fun printReflectionNewArray( + owner: ActionSequence, + call: ReflectionNewArray + ): List { val elementType = call.asArray.component val (newArrayCall, actualType) = when { elementType.isPrimitive -> { @@ -453,22 +478,34 @@ class ExecutorAS2JavaPrinter( ) } - override fun printReflectionSetField(owner: ActionSequence, call: ReflectionSetField): List { - val setFieldMethod = call.field.type.kexType.primitiveName?.let { reflectionUtils.setPrimitiveFieldMap[it]!! } - ?: reflectionUtils.setField + override fun printReflectionSetField( + owner: ActionSequence, + call: ReflectionSetField + ): List { + val setFieldMethod = + call.field.type.kexType.primitiveName?.let { reflectionUtils.setPrimitiveFieldMap[it]!! } + ?: reflectionUtils.setField return listOf("${setFieldMethod.name}(${owner.name}, ${owner.name}.getClass(), \"${call.field.name}\", ${call.value.stackName})") } - override fun printReflectionSetStaticField(owner: ActionSequence, call: ReflectionSetStaticField): List { - val setFieldMethod = call.field.type.kexType.primitiveName?.let { reflectionUtils.setPrimitiveFieldMap[it]!! } - ?: reflectionUtils.setField + override fun printReflectionSetStaticField( + owner: ActionSequence, + call: ReflectionSetStaticField + ): List { + val setFieldMethod = + call.field.type.kexType.primitiveName?.let { reflectionUtils.setPrimitiveFieldMap[it]!! } + ?: reflectionUtils.setField return listOf("${setFieldMethod.name}(null, Class.forName(\"${call.field.klass.canonicalDesc}\"), \"${call.field.name}\", ${call.value.stackName})") } - override fun printReflectionArrayWrite(owner: ActionSequence, call: ReflectionArrayWrite): List { + override fun printReflectionArrayWrite( + owner: ActionSequence, + call: ReflectionArrayWrite + ): List { val elementType = call.elementType - val setElementMethod = elementType.kexType.primitiveName?.let { reflectionUtils.setPrimitiveElementMap[it]!! } - ?: reflectionUtils.setElement + val setElementMethod = + elementType.kexType.primitiveName?.let { reflectionUtils.setPrimitiveElementMap[it]!! } + ?: reflectionUtils.setElement return listOf("${setElementMethod.name}(${owner.name}, ${call.index.stackName}, ${call.value.stackName})") } @@ -522,15 +559,20 @@ class ExecutorAS2JavaPrinter( call.returnValues.forEach { it.printAsJava() } val returnValues = call.returnValues - .map { value -> value.cast(call.method.returnType.asType) } - .joinToString(", ") { - it - } - val method = call.method - val anys = call.method.argTypes.joinToString(", ") { type -> mockitoAnyFromType(type) } + .map { it.stackName } + .joinToString(separator = ", ", prefix = "new Object[]{", postfix = "}") { it } + + val anys = call.method.paramTypes + .map { mockitoAnyFromType(it.getKfgType(ctx.types)) } + .joinToString(prefix = "new Object[]{", postfix = "}", separator = ", ") { it } - val type = call.method.klass.asType.asType - // TODO: Mock. Call method using ReflectionUtils, so it can mock package-private methods - return listOf("Mockito.when((${owner.cast(type)}).${method.name}($anys)).thenReturn(${returnValues})") + val paramTypes = call.method.paramTypes + .map { it.getKfgType(ctx.types).klassType } + .joinToString(prefix = "new Class[] {", postfix = "}", separator = ",") { it } + + val methodName = '"' + call.method.name + '"' + return listOf( + "${mockUtils.setupMethod.name}($methodName, $paramTypes, ${owner.stackName}, $anys, $returnValues)" + ) } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt index e564c30f4..43abd3531 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt @@ -11,9 +11,8 @@ class MockUtilsPrinter( ) { private val builder = JavaBuilder(packageName) val klass = builder.run { klass(packageName, MOCK_UTILS_CLASS) } -// val innerClass = builder.run { klass(packageName, MOCK_INIT_TEST_CLASS) } + val setupMethod: JavaBuilder.JavaFunction -// val newPrimitiveArrayMap = mutableMapOf() companion object { const val MOCK_UTILS_CLASS = "MockUtils" @@ -44,9 +43,55 @@ class MockUtilsPrinter( init { with(builder) { import("org.mockito.Mockito") + import ("org.mockito.invocation.InvocationOnMock") + import ("org.mockito.stubbing.Answer") + import ("org.junit.Test") + import ("org.junit.Rule") + import ("org.junit.rules.Timeout") + + import("java.lang.Class") + import("java.lang.reflect.Method") + import("java.lang.reflect.Constructor") + import("java.lang.reflect.Field") + import("java.lang.reflect.Array") + import("java.lang.reflect.Modifier") + import("sun.misc.Unsafe") + + import ("java.lang.Throwable") + import ("java.lang.IllegalStateException") + import ("java.util.concurrent.TimeUnit") + import ("org.junit.Before") + with(klass) { + setupMethod = method("setupMethod") { + arguments += arg("name", type("String")) + arguments += arg("argTypes", type("Class[]")) + arguments += arg("instance", type("Object")) + arguments += arg("anys", type("Object[]")) + arguments += arg("returns", type("Object[]")) + + visibility = Visibility.PUBLIC + modifiers += "static" + exceptions += "Throwable" + returnType = type("void") + + +"Class klass = instance.getClass()" + +"Method method = klass.getDeclaredMethod(name, argTypes)" + +"method.setAccessible(true)" + +"""Mockito.when(method.invoke(instance, anys)).thenAnswer(new Answer() { + int cur = 0; + @Override + public Object answer(InvocationOnMock invocation) { + if (cur == returns.length - 1){ + return returns[cur]; + } + return returns[cur++]; + } + })""" + } + staticClass(MOCK_INIT_TEST_CLASS) { field("globalTimeout", type("Timeout")) { visibility = Visibility.PUBLIC From 8ca1e71804ccc9525443b671aac98c2e7b3da79e Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 14 Feb 2024 15:39:58 +0100 Subject: [PATCH 076/128] Refactoring. Regression test --- .../research/kex/descriptor/descriptor.kt | 33 ++++++++---------- .../research/kex/serialization/descriptor.kt | 2 +- .../actionsequence/ActionSequence.kt | 10 +++--- .../codegen/javagen/MockUtilsPrinter.kt | 7 ++-- .../kex/concolic/MockConcolicLongTest.kt | 6 ++++ .../concolic/mock/MockInheritanceTests.java | 34 +++++++++++++++++++ 6 files changed, 63 insertions(+), 29 deletions(-) create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockInheritanceTests.java diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 700beaec8..34ae192a8 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -694,14 +694,14 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : @Serializable -data class MethodId( +data class MockedMethod( val name: String, val paramTypes: List, val returnType: KexType -) {} +) -private val Method.id: MethodId get() = MethodId(name, argTypes.map{it.kexType}, returnType.kexType) +private val Method.mocked: MockedMethod get() = MockedMethod(name, argTypes.map{it.kexType}, returnType.kexType) class MockDescriptor(term: Term, type: KexClass) : AbstractFieldContainingDescriptor(term, type) { @@ -711,30 +711,25 @@ class MockDescriptor(term: Term, type: KexClass) : fields.putAll(original.fields) } - val methodReturns: MutableMap> = mutableMapOf() + val methodReturns: MutableMap> = mutableMapOf() val allReturns: Iterable get() = methodReturns.values.asSequence().flatMap { it.asSequence() }.asIterable() - val methods: Set + val methods: Set get() = methodReturns.keys - operator fun get(method: Method) = get(method.id) - operator fun get(methodId: MethodId) = methodReturns[methodId] - operator fun set(method: Method, values: List) { - methodReturns[method.id] = values.toMutableList() - } - - operator fun set(methodId: MethodId, values: List) { - methodReturns[methodId] = values.toMutableList() + operator fun get(mockedMethod: MockedMethod) = methodReturns[mockedMethod] + operator fun set(mockedMethod: MockedMethod, values: List) { + methodReturns[mockedMethod] = values.toMutableList() } fun addReturnValue(method: Method, value: Descriptor) { - addReturnValue(method.id, value) + addReturnValue(method.mocked, value) } - internal fun addReturnValue(methodId: MethodId, value: Descriptor) { - methodReturns.getOrPut(methodId) { mutableListOf() }.add(value) + internal fun addReturnValue(mockedMethod: MockedMethod, value: Descriptor) { + methodReturns.getOrPut(mockedMethod) { mutableListOf() }.add(value) } override fun concretize( @@ -959,15 +954,15 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde KexRtManager.Mode.UNMAP -> rtUnmapped } - private val MethodId.mapped + private val MockedMethod.mapped get() = when (mode) { - KexRtManager.Mode.MAP -> MethodId( + KexRtManager.Mode.MAP -> MockedMethod( name.rtMapped, paramTypes.map { it.rtMapped }, returnType.rtMapped ) - KexRtManager.Mode.UNMAP -> MethodId( + KexRtManager.Mode.UNMAP -> MockedMethod( name.rtUnmapped, paramTypes.map { it.rtUnmapped }, returnType.rtUnmapped diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt index 48270c970..377cdf9b3 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt @@ -70,7 +70,7 @@ internal sealed class DescriptorWrapper { override val id: Id, override val type: KexType, val fields: MutableList, Id>>, - val methodReturns: MutableList>> + val methodReturns: MutableList>> ) : DescriptorWrapper() { override fun convert(map: Map, output: MutableMap) { if (id in output) return diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt index 1d70585d4..bff43e684 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt @@ -2,7 +2,7 @@ package org.vorpal.research.kex.reanimator.actionsequence import org.vorpal.research.kex.descriptor.Descriptor import org.vorpal.research.kex.descriptor.DescriptorRtMapper -import org.vorpal.research.kex.descriptor.MethodId +import org.vorpal.research.kex.descriptor.MockedMethod import org.vorpal.research.kex.ktype.KexRtManager import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.KexRtManager.rtUnmapped @@ -234,7 +234,7 @@ data class MockNewInstance(val klass: Class) : MockCall { } data class MockSetupMethod( - val method: MethodId, val returnValues: List + val method: MockedMethod, val returnValues: List ) : MockCall { override val parameters: List get() = returnValues @@ -598,15 +598,15 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { KexRtManager.Mode.UNMAP -> rtUnmapped } - private val MethodId.mapped + private val MockedMethod.mapped get() = when (mode) { - KexRtManager.Mode.MAP -> MethodId( + KexRtManager.Mode.MAP -> MockedMethod( name.rtMapped, paramTypes.map { it.rtMapped }, returnType.rtMapped ) - KexRtManager.Mode.UNMAP -> MethodId( + KexRtManager.Mode.UNMAP -> MockedMethod( name.rtUnmapped, paramTypes.map { it.rtUnmapped }, returnType.rtUnmapped diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt index 43abd3531..e4cc9eb7f 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt @@ -75,7 +75,7 @@ class MockUtilsPrinter( visibility = Visibility.PUBLIC modifiers += "static" exceptions += "Throwable" - returnType = type("void") + returnType = void +"Class klass = instance.getClass()" +"Method method = klass.getDeclaredMethod(name, argTypes)" @@ -99,11 +99,10 @@ class MockUtilsPrinter( annotations += "Rule" } method("mockitoInitTest") { + returnType = void + annotations += "Test" +"Object mock = Mockito.mock(Object.class)" +"assert (mock.hashCode() == 0 || mock.hashCode() != 0)" - }.apply { - returnType = void; - annotations += "Test" } } } diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index 00bb7a7b4..cbf89885a 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -59,4 +59,10 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { val eps = 0.5 assertCoverage(cm[prefix + "MockSetTests"], 1.0, eps) } + + @Test + fun mockInheritanceTests(){ + // TODO: force mocking mode "full" + assertCoverage(cm[prefix + "MockInheritanceTests"], 1.0) + } } diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockInheritanceTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockInheritanceTests.java new file mode 100644 index 000000000..2794533d3 --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockInheritanceTests.java @@ -0,0 +1,34 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + + +@SuppressWarnings("ALL") +public class MockInheritanceTests { + + static class Base { + + int foo(boolean b) { + return 248; + } + } + + static class Derived extends Base { + + @Override + int foo(boolean b) { + return 777; + } + } + + public static void testInheritance(Base a) { + if (a.foo(false) == 222) { + Derived d = (Derived) a; + if (d.foo(true) == 888) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + } +} From 30f65cde2869dcb02c4566b4290da6bac90decb0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 14 Feb 2024 15:47:06 +0100 Subject: [PATCH 077/128] Fixes --- .../org/vorpal/research/kex/descriptor/descriptor.kt | 2 +- .../vorpal/research/kex/concolic/MockConcolicLongTest.kt | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 34ae192a8..9179dbe7b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -728,7 +728,7 @@ class MockDescriptor(term: Term, type: KexClass) : addReturnValue(method.mocked, value) } - internal fun addReturnValue(mockedMethod: MockedMethod, value: Descriptor) { + fun addReturnValue(mockedMethod: MockedMethod, value: Descriptor) { methodReturns.getOrPut(mockedMethod) { mutableListOf() }.add(value) } diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index cbf89885a..51a20f01b 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -4,6 +4,9 @@ import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import org.junit.Test +import org.vorpal.research.kex.config.RuntimeConfig +import org.vorpal.research.kex.config.kexConfig +import org.vorpal.research.kex.util.mockingMode import kotlin.time.ExperimentalTime @ExperimentalTime @@ -62,7 +65,9 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockInheritanceTests(){ - // TODO: force mocking mode "full" + val oldMockingMode = kexConfig.mockingMode + RuntimeConfig.setValue("mock", "mode", "full"); assertCoverage(cm[prefix + "MockInheritanceTests"], 1.0) + RuntimeConfig.setValue("mock", "mode", oldMockingMode.toString()); } } From 1040d3f1ff077e0c3a77aefc7e934b8f5685fac9 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 14 Feb 2024 16:08:09 +0100 Subject: [PATCH 078/128] MockUtils refactoring --- .../kex/reanimator/codegen/javagen/MockUtilsPrinter.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt index e4cc9eb7f..3310768a5 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/MockUtilsPrinter.kt @@ -16,7 +16,7 @@ class MockUtilsPrinter( companion object { const val MOCK_UTILS_CLASS = "MockUtils" - const val MOCK_INIT_TEST_CLASS = "InitTest" +// const val MOCK_INIT_TEST_CLASS = "InitTest" private val mockUtilsInstances = mutableMapOf, MockUtilsPrinter>() fun mockUtils(packageName: String): MockUtilsPrinter { @@ -84,14 +84,13 @@ class MockUtilsPrinter( int cur = 0; @Override public Object answer(InvocationOnMock invocation) { - if (cur == returns.length - 1){ - return returns[cur]; - } + if (cur == returns.length - 1) return returns[cur]; return returns[cur++]; } })""" } +/* staticClass(MOCK_INIT_TEST_CLASS) { field("globalTimeout", type("Timeout")) { visibility = Visibility.PUBLIC @@ -105,6 +104,7 @@ class MockUtilsPrinter( +"assert (mock.hashCode() == 0 || mock.hashCode() != 0)" } } +*/ } } } From 79f737a4ce3bbfa87e7f2004088c2c9849600fe3 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 16 Feb 2024 19:14:09 +0100 Subject: [PATCH 079/128] Hack to fix mockito on java 8 --- .../kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 69e93eee1..b96b267fa 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -1,5 +1,6 @@ package org.vorpal.research.kex.util +import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.ConcreteClass import org.vorpal.research.kfg.util.toByteArray @@ -20,6 +21,13 @@ class KfgClassLoader( private val cache = hashMapOf>() val fallback = PathClassLoader(paths) + init { + if (kexConfig.isMockingEnabled) tryOrNull { + // hack to fix mockito with java 8 + definePackage("org.mockito.codegen", "", "", "", "", "", "", null) + } + } + companion object { private val INCLUDES = setOf( "package org.vorpal.research.kex.test.*", From d07d03b494a1fca6e54c0d52e14f35c14173581f Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 17 Feb 2024 23:28:39 +0100 Subject: [PATCH 080/128] Removed unnecessary hack in TestExecutor --- .../research/kex/worker/TestExecutor.kt | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt index c92ec8992..9b0c500fb 100644 --- a/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt +++ b/kex-executor/src/main/kotlin/org/vorpal/research/kex/worker/TestExecutor.kt @@ -4,26 +4,15 @@ import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.descriptor.convertToDescriptor import org.vorpal.research.kex.trace.symbolic.TraceCollectorProxy import org.vorpal.research.kex.trace.symbolic.protocol.* -import org.vorpal.research.kex.util.asArray import org.vorpal.research.kfg.ir.value.NameMapperContext -import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log class TestExecutor( val ctx: ExecutionContext ) { - private var isFirstRun: Boolean = true fun executeTest(request: TestExecutionRequest): ExecutionResult { val javaClass = ctx.loader.loadClass(request.klass) - if (isFirstRun) { - log.debug { "First run with JUnit" } - executeTestFromJUnit(javaClass) - isFirstRun = false - } else { - log.debug { "No JUnit run" } - } - val instance = javaClass.getConstructor().newInstance() log.debug("Loaded a test class and created an instance") @@ -60,15 +49,27 @@ class TestExecutor( else -> SuccessResult(collector.instructionTrace, collector.symbolicState) } } - - private fun executeTestFromJUnit(testClass: Class<*>?): Unit = try { - val jcClass = ctx.loader.loadClass("org.junit.runner.JUnitCore") - val jc = jcClass.getConstructor().newInstance() - log.debug { "Created JUnitCoreInstance" } - jcClass.getMethod("run", Class::class.java.asArray()) - .invoke(jc, arrayOf(testClass)) - log.debug { "JUnit successfully executed" } - } catch (e: Throwable) { - log.error("JUnit execution failed with an exception", e) - } +// +// private fun fixMockito(javaClass: Class<*>?) { +// if (!kexConfig.isMockingEnabled) return +// +// if (isFirstRun) { +// log.debug { "First run with JUnit" } +// executeTestFromJUnit(javaClass) +// isFirstRun = false +// } else { +// log.debug { "No JUnit run" } +// } +// } +// +// private fun executeTestFromJUnit(testClass: Class<*>?): Unit = try { +// val jcClass = ctx.loader.loadClass("org.junit.runner.JUnitCore") +// val jc = jcClass.getConstructor().newInstance() +// log.debug { "Created JUnitCoreInstance" } +// jcClass.getMethod("run", Class::class.java.asArray()) +// .invoke(jc, arrayOf(testClass)) +// log.debug { "JUnit successfully executed" } +// } catch (e: Throwable) { +// log.error("JUnit execution failed with an exception", e) +// } } From e0a45828f2b628fcacbaad6b11c9a4ffabaca5cc Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 17 Feb 2024 23:57:38 +0100 Subject: [PATCH 081/128] Config entry to disable workaround in KfgClassLoader --- .../research/kex/util/KfgClassLoader.kt | 21 ++++++++++++++++--- .../kotlin/org/vorpal/research/kex/util/rt.kt | 3 +++ kex-test.ini | 1 + kex.ini | 5 +++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index b96b267fa..859d863e6 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -4,6 +4,9 @@ import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.ConcreteClass import org.vorpal.research.kfg.util.toByteArray +import org.vorpal.research.kthelper.logging.info +import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.logging.warn import org.vorpal.research.kthelper.tryOrNull import java.nio.file.Path import java.util.jar.JarFile @@ -22,12 +25,24 @@ class KfgClassLoader( val fallback = PathClassLoader(paths) init { - if (kexConfig.isMockingEnabled) tryOrNull { - // hack to fix mockito with java 8 - definePackage("org.mockito.codegen", "", "", "", "", "", "", null) + val isJava8 = tryOrNull { System.getProperty("java.version") }?.startsWith("1.8") != false + if (kexConfig.isMockingEnabled && kexConfig.isMockitoJava8WorkaroundEnabled && isJava8) { + log.info { "Applying workaround for mockito on java 8" } + if (applyJava8Workaround()) { + log.info { "Workaround successfully applied" } + } else { + log.warn { "Workaround failed" } + } } } + + private fun applyJava8Workaround(): Boolean { + return tryOrNull { + definePackage("org.mockito.codegen", "", "", "", "", "", "", null) + } != null + } + companion object { private val INCLUDES = setOf( "package org.vorpal.research.kex.test.*", diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index b8b3325c9..b1fed3545 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -63,6 +63,9 @@ val Config.mockingMode: MockingMode? val Config.isMockitoClassesWorkaroundEnabled: Boolean get() = getBooleanValue("mock", "mockitoClassesWorkaround", true) +val Config.isMockitoJava8WorkaroundEnabled: Boolean + get() = getBooleanValue("mock", "java8WorkaroundEnabled", false) + val Config.logTypeFix: Boolean get() = getBooleanValue("mock", "logTypeFix", false) diff --git a/kex-test.ini b/kex-test.ini index 1f5383a0f..5ae51666c 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -34,6 +34,7 @@ mode = basic ; full to mock as much as possible mockitoClassesWorkaround = true +java8WorkaroundEnabled = true mockPessimizationEnabled = true diff --git a/kex.ini b/kex.ini index f5865dfab..b203a2144 100644 --- a/kex.ini +++ b/kex.ini @@ -51,10 +51,15 @@ mode = basic test = true ; normally should be missing or false +mockPessimizationEnabled = true + +; workarounds mockitoClassesWorkaround = true +java8WorkaroundEnabled = true mockPessimizationEnabled = true +; logging logTypeFix = false logStackTraceTypeFix = false From f95baa7f99fbf15fa13608ed1409d6b935d12b3d Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 18 Feb 2024 00:24:44 +0100 Subject: [PATCH 082/128] Test of lambda mocking --- .../research/kex/util/KfgClassLoader.kt | 2 +- .../kex/concolic/MockConcolicLongTest.kt | 12 ++++-- .../test/concolic/mock/MockLambdaTests.java | 38 +++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 859d863e6..05f8b7c8e 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -36,13 +36,13 @@ class KfgClassLoader( } } - private fun applyJava8Workaround(): Boolean { return tryOrNull { definePackage("org.mockito.codegen", "", "", "", "", "", "", null) } != null } + companion object { private val INCLUDES = setOf( "package org.vorpal.research.kex.test.*", diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index 51a20f01b..f92ebcc70 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -20,6 +20,7 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { fun mockTest() { assertCoverage(cm[prefix + "MockTests"], 1.0) } + @Test fun mockReturnsMockTest() { assertCoverage(cm[prefix + "MockReturnsMockTests"], 1.0) @@ -53,21 +54,26 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { } @Test - fun mockGenericsTests(){ + fun mockGenericsTests() { assertCoverage(cm[prefix + "MockGenericsTests"], 1.0) } @Test - fun mockSetTests(){ + fun mockSetTests() { val eps = 0.5 assertCoverage(cm[prefix + "MockSetTests"], 1.0, eps) } @Test - fun mockInheritanceTests(){ + fun mockInheritanceTests() { val oldMockingMode = kexConfig.mockingMode RuntimeConfig.setValue("mock", "mode", "full"); assertCoverage(cm[prefix + "MockInheritanceTests"], 1.0) RuntimeConfig.setValue("mock", "mode", oldMockingMode.toString()); } + + @Test + fun mockLambdaTests() { + assertCoverage(cm[prefix + "MockLambdaTests"], 1.0) + } } diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java new file mode 100644 index 000000000..0c2bcccf6 --- /dev/null +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java @@ -0,0 +1,38 @@ +package org.vorpal.research.kex.test.concolic.mock; + +import java.util.function.Function; +import java.util.function.IntSupplier; +import java.util.function.Supplier; +import org.vorpal.research.kex.intrinsics.AssertIntrinsics; + + +@SuppressWarnings("ALL") +public class MockLambdaTests { + + public void testIntSupplier(IntSupplier a) { + if (a.getAsInt() == 42) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + +/* + public void testBoxedIntSupplier(Supplier a) { + if (a.get() == 333) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } + + public void testFunction(Function a) { + if (a.apply(null) == 909L) { + AssertIntrinsics.kexAssert(true); + } else { + AssertIntrinsics.kexAssert(true); + } + } +*/ +} + From fe54ba4c7c128a753a7d738a53d364aff3020d5b Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 19 Feb 2024 22:28:54 +0100 Subject: [PATCH 083/128] Fixed PathClassLoader like KfgClassLoader --- .../research/kex/util/PathClassLoader.kt | 20 +++++++++++++ .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 29 +++++++++---------- kex-test.ini | 10 ++++--- .../test/concolic/mock/MockLambdaTests.java | 2 -- kex.ini | 4 +-- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt index f785c8b52..96cfaf023 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt @@ -1,5 +1,9 @@ package org.vorpal.research.kex.util +import org.vorpal.research.kex.config.kexConfig +import org.vorpal.research.kthelper.logging.info +import org.vorpal.research.kthelper.logging.warn +import org.vorpal.research.kthelper.tryOrNull import java.nio.file.Path import java.util.jar.JarFile import kotlin.io.path.exists @@ -12,6 +16,22 @@ class PathClassLoader( ) : ClassLoader(parent) { private val cache = hashMapOf>() + init { + val isJava8 = tryOrNull { System.getProperty("java.version") }?.startsWith("1.8") != false + if (kexConfig.isMockingEnabled && kexConfig.isMockitoJava8WorkaroundEnabled && isJava8) { + org.vorpal.research.kthelper.logging.log.info { "Applying workaround for mockito on java 8" } + if (applyJava8Workaround()) { + org.vorpal.research.kthelper.logging.log.info { "Workaround successfully applied" } + } else { + org.vorpal.research.kthelper.logging.log.warn { "Workaround failed" } + } + } + } + + private fun applyJava8Workaround(): Boolean = tryOrNull { + definePackage("org.mockito.codegen", "", "", "", "", "", "", null) + } != null + private fun readClassFromJar(name: String, path: Path): ByteArray? { val fileName = name.asmString + ".class" val jarFile = JarFile(path.toFile()) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 175c3b945..dee6251e8 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -5,6 +5,7 @@ import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.ktype.* import org.vorpal.research.kex.parameters.Parameters import org.vorpal.research.kex.reanimator.actionsequence.* +import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.type.* import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.error @@ -509,8 +510,11 @@ class ExecutorAS2JavaPrinter( return listOf("${setElementMethod.name}(${owner.name}, ${call.index.stackName}, ${call.value.stackName})") } + // FIXME: remove that map + private val mockType: MutableMap = mutableMapOf() override fun printMockNewInstance(owner: ActionSequence, call: MockNewInstance): List { builder.import("org.mockito.Mockito") + mockType[owner] = call.klass val actualType = ASClass(ctx.types.objectType) val kfgClass = call.klass return listOf( @@ -556,23 +560,18 @@ class ExecutorAS2JavaPrinter( if (call.returnValues.isEmpty()) { return emptyList() } - call.returnValues.forEach { it.printAsJava() } - - val returnValues = call.returnValues - .map { it.stackName } - .joinToString(separator = ", ", prefix = "new Object[]{", postfix = "}") { it } - val anys = call.method.paramTypes - .map { mockitoAnyFromType(it.getKfgType(ctx.types)) } - .joinToString(prefix = "new Object[]{", postfix = "}", separator = ", ") { it } + call.returnValues.forEach { it.printAsJava() } + val returns = call.returnValues.joinToString(separator = ", ") { value -> + value.forceCastIfNull(call.method.returnType.getKfgType(ctx.types).asType) + } - val paramTypes = call.method.paramTypes - .map { it.getKfgType(ctx.types).klassType } - .joinToString(prefix = "new Class[] {", postfix = "}", separator = ",") { it } + val anys = call.method.paramTypes.joinToString(separator = ", ") { type -> + mockitoAnyFromType(type.getKfgType(ctx.types)) + } - val methodName = '"' + call.method.name + '"' - return listOf( - "${mockUtils.setupMethod.name}($methodName, $paramTypes, ${owner.stackName}, $anys, $returnValues)" - ) + val methodName = call.method.name + val instance = "(${owner.cast(mockType[owner]!!.asType.asType)})" + return listOf("Mockito.when($instance.$methodName($anys)).thenReturn($returns)") } } diff --git a/kex-test.ini b/kex-test.ini index 5ae51666c..175553803 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -33,14 +33,16 @@ mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible -mockitoClassesWorkaround = true -java8WorkaroundEnabled = true - mockPessimizationEnabled = true -test = true ; normally should be missing or false +test = false + +; workarounds +mockitoClassesWorkaround = true +java8WorkaroundEnabled = true +; extra logging logTypeFix = false logStackTraceTypeFix = false diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java index 0c2bcccf6..d71f9a42c 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java @@ -17,7 +17,6 @@ public void testIntSupplier(IntSupplier a) { } } -/* public void testBoxedIntSupplier(Supplier a) { if (a.get() == 333) { AssertIntrinsics.kexAssert(true); @@ -33,6 +32,5 @@ public void testFunction(Function a) { AssertIntrinsics.kexAssert(true); } } -*/ } diff --git a/kex.ini b/kex.ini index b203a2144..f4f5eb4ea 100644 --- a/kex.ini +++ b/kex.ini @@ -48,8 +48,8 @@ mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible -test = true ; normally should be missing or false +test = false mockPessimizationEnabled = true @@ -57,8 +57,6 @@ mockPessimizationEnabled = true mockitoClassesWorkaround = true java8WorkaroundEnabled = true -mockPessimizationEnabled = true - ; logging logTypeFix = false logStackTraceTypeFix = false From 60716588c10858dd7fbb90158be94d70d2c21144 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 23 Feb 2024 14:56:07 +0100 Subject: [PATCH 084/128] broken --- .../asm/manager/ClassInstantiationManager.kt | 2 +- .../research/kex/descriptor/descriptor.kt | 24 ++++-- .../kex/descriptor/functionalExtensions.kt | 58 ++++++++++++++ .../research/kex/parameters/Parameters.kt | 76 +++++++++++++++++-- .../research/kex/util/HackedClassLoader.kt | 29 +++++++ .../research/kex/util/KfgClassLoader.kt | 25 +----- .../research/kex/util/PathClassLoader.kt | 22 +----- .../kotlin/org/vorpal/research/kex/util/rt.kt | 4 +- .../kex/asm/analysis/util/extensions.kt | 2 +- .../actionsequence/ActionSequence.kt | 3 + 10 files changed, 184 insertions(+), 61 deletions(-) create mode 100644 kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt create mode 100644 kex-core/src/main/kotlin/org/vorpal/research/kex/util/HackedClassLoader.kt diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt index 808a4ef04..3a7023f5b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt @@ -308,7 +308,7 @@ class ClassInstantiationDetector( override fun visit(klass: Class) { if (ignoredInstantiations.any { it.matches(klass) }) { - return + return // todo maybe super.visit(klass) ??? } if (StringClassInstantiationManagerImpl.isDirectlyInstantiable(klass, baseAccessLevel)) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 9179dbe7b..97fb98489 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -18,6 +18,7 @@ import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.log +import ru.spbstu.wheels.hashCombine import ru.spbstu.wheels.joinToString import kotlin.random.Random @@ -476,7 +477,7 @@ sealed class FieldContainingDescriptor>( } class ObjectDescriptor(klass: KexClass) : - FieldContainingDescriptor(term { generate(klass) }, klass) { + FieldContainingDescriptor(term{ generate(klass) }, klass) { override fun deepCopy(copied: MutableMap): Descriptor { if (this in copied) return copied[this]!! val copy = ObjectDescriptor(klass) @@ -695,13 +696,24 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : @Serializable data class MockedMethod( + val klass: KexClass, // excluded from hashCode and equals val name: String, val paramTypes: List, val returnType: KexType -) +){ + override fun equals(other: Any?): Boolean { + if (other !is MockedMethod) return false + return name == other.name && paramTypes == other.paramTypes && + returnType == other.returnType + } + + override fun hashCode(): Int { + return hashCombine(name, paramTypes, returnType) + } +} -private val Method.mocked: MockedMethod get() = MockedMethod(name, argTypes.map{it.kexType}, returnType.kexType) +private val Method.mocked: MockedMethod get() = MockedMethod(klass.kexType, name, argTypes.map{it.kexType}, returnType.kexType) class MockDescriptor(term: Term, type: KexClass) : AbstractFieldContainingDescriptor(term, type) { @@ -713,8 +725,8 @@ class MockDescriptor(term: Term, type: KexClass) : val methodReturns: MutableMap> = mutableMapOf() - val allReturns: Iterable - get() = methodReturns.values.asSequence().flatMap { it.asSequence() }.asIterable() + val allReturns: Sequence + get() = methodReturns.values.asSequence().flatMap { it.asSequence() } val methods: Set get() = methodReturns.keys @@ -957,12 +969,14 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde private val MockedMethod.mapped get() = when (mode) { KexRtManager.Mode.MAP -> MockedMethod( + klass.rtMapped as KexClass, name.rtMapped, paramTypes.map { it.rtMapped }, returnType.rtMapped ) KexRtManager.Mode.UNMAP -> MockedMethod( + klass.rtUnmapped as KexClass, name.rtUnmapped, paramTypes.map { it.rtUnmapped }, returnType.rtUnmapped diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt new file mode 100644 index 000000000..27744b7c3 --- /dev/null +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt @@ -0,0 +1,58 @@ +package org.vorpal.research.kex.descriptor + + +fun Descriptor.map( + map: MutableMap, + mapper: (Descriptor) -> Descriptor +): Descriptor { + if (map[this] != null) return map[this]!! + + val newDescriptor = mapper(this) + map[this] = newDescriptor + map[newDescriptor] = newDescriptor + + when (newDescriptor) { + is ConstantDescriptor -> Unit + is ClassDescriptor -> newDescriptor.fields.replaceAll { _, desc -> desc.map(map, mapper) } + is ObjectDescriptor -> newDescriptor.fields.replaceAll { _, desc -> desc.map(map, mapper) } + is MockDescriptor -> { + newDescriptor.fields.replaceAll { _, desc -> desc.map(map, mapper) } + newDescriptor.methodReturns.values.forEach { values -> + values.replaceAll { d -> d.map(map, mapper) } + } + } + + is ArrayDescriptor -> newDescriptor.elements.replaceAll { _, u -> u.map(map, mapper) } + } + + return newDescriptor +} + + +fun Descriptor.asSequence(): Sequence = asSequence(mutableSetOf()) +fun Descriptor.asSequence(visited: MutableSet): Sequence { + if (this in visited) return emptySequence() + + return sequenceOf(this) + when (this) { + is ConstantDescriptor -> emptySequence() + is ClassDescriptor -> fields.values.asSequence().flatMap { it.asSequence(visited) } + is ObjectDescriptor -> fields.values.asSequence().flatMap { it.asSequence(visited) } + is MockDescriptor -> + fields.values.asSequence().flatMap { it.asSequence(visited) } + allReturns + + is ArrayDescriptor -> elements.values.asSequence() + } +} + +fun Descriptor.forEach( + visited: MutableSet = mutableSetOf(), + block: (Descriptor) -> Unit +) { + this.asSequence(visited).forEach(block) +} + +fun Descriptor.any( + visited: MutableSet = mutableSetOf(), + predicate: (Descriptor) -> Boolean +): Boolean = asSequence(visited).any(predicate) + diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index f6dc6e2a2..b0630394c 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -1,6 +1,7 @@ package org.vorpal.research.kex.parameters import kotlinx.serialization.Serializable +import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.manager.instantiationManager import org.vorpal.research.kex.asm.util.AccessModifier import org.vorpal.research.kex.config.kexConfig @@ -8,13 +9,16 @@ import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager.isKexRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped +import org.vorpal.research.kex.ktype.kexType import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.util.KfgTargetFilter import org.vorpal.research.kex.util.MockingMode +import org.vorpal.research.kex.util.loadClass import org.vorpal.research.kex.util.mockingMode import org.vorpal.research.kfg.ClassManager +import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory import org.vorpal.research.kthelper.logging.debug @@ -109,11 +113,10 @@ fun Parameters.filterIgnoredStatic(): Parameters { fun createDescriptorToMock( allDescriptors: Collection, types: TypeFactory -): Map { - val descriptorToMock = mutableMapOf() - val visited = mutableSetOf() - allDescriptors.map { it.replaceWithMock(types, descriptorToMock, visited) } - return descriptorToMock +): Map { + val map = mutableMapOf() + allDescriptors.map { it.replaceWithMock(types, map) } + return map } private fun Descriptor.insertMocks( @@ -159,6 +162,10 @@ fun Descriptor.isMockable(types: TypeFactory): Boolean { } } +fun Descriptor.reqMocks2( + types: TypeFactory, visited: MutableSet = mutableSetOf() +): Boolean = any(visited) { descriptor -> descriptor.isMockable(types) } + fun Descriptor.requireMocks(types: TypeFactory, visited: MutableSet): Boolean { if (this in visited) return false if (this.isMockable(types)) return true @@ -173,6 +180,58 @@ fun Descriptor.requireMocks(types: TypeFactory, visited: MutableSet) } } + +fun Descriptor.replaceWithMock( + types: TypeFactory, + map: MutableMap +): Descriptor { + return this.map(map) { + if (!this.isMockable(types)) { + return@map this + } + this as ObjectDescriptor + val klass = (this.type.getKfgType(types) as? ClassType)?.klass + klass ?: return@map this.also { log.error { "Got null class to mock. Descriptor: $this" } } + + MockDescriptor(this).also { log.debug { "Created mock descriptor for ${it.term}" } } + } +} + + +fun Class.getFunctionalInterfaces( + ctx: ExecutionContext, + acc: MutableSet = mutableSetOf() +): Set { + if (this.isInterface && + ctx.loader.loadClass(this).getAnnotation(FunctionalInterface::class.java) != null + ) { + acc.add(this) + return acc + } + allAncestors.forEach { it.getFunctionalInterfaces(ctx, acc) } + return acc +} + +fun Descriptor.filterConcreteLambdas( + ctx: ExecutionContext, + visited: MutableMap = mutableMapOf() +): Descriptor { + return this + return this.map(visited) { + if (this !is ObjectDescriptor) { + return@map this + } + val clazz = this.type as KexClass + val kfgClass = clazz.kfgClass(ctx.types) + val functionalInterfaces = kfgClass.getFunctionalInterfaces(ctx) + when (functionalInterfaces.size) { + 0 -> this + 1 -> descriptor { `object`(functionalInterfaces.first().kexType) } + else -> this + } + } +} + private fun Descriptor.replaceWithMock( types: TypeFactory, descriptorToMock: MutableMap, @@ -199,12 +258,15 @@ private fun Descriptor.replaceWithMock( fun setupMocks( methodCalls: List, termToDescriptor: Map, - descriptorToMock: Map + descriptorToMock: Map ) { for (callPredicate in methodCalls) { + if (!callPredicate.hasLhv) continue val call = callPredicate.call as CallTerm + if (call.method.name == "getClass") continue - val mock = termToDescriptor[call.owner]?.let { descriptorToMock[it] ?: it } + val mock = + termToDescriptor[call.owner]?.let { descriptorToMock[it] ?: it } as? MockDescriptor val value = termToDescriptor[callPredicate.lhvUnsafe]?.let { descriptorToMock[it] ?: it } mock ?: log.warn { "No mock for $call" } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/HackedClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/HackedClassLoader.kt new file mode 100644 index 000000000..ecbb7ac23 --- /dev/null +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/HackedClassLoader.kt @@ -0,0 +1,29 @@ +package org.vorpal.research.kex.util + +import org.vorpal.research.kex.config.kexConfig +import org.vorpal.research.kthelper.logging.info +import org.vorpal.research.kthelper.logging.warn +import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.tryOrNull + +sealed class HackedClassLoader(parent: ClassLoader = getSystemClassLoader()) : + ClassLoader(parent) { + + init { + val isJava8 = tryOrNull { System.getProperty("java.version") }?.startsWith("1.8") == true + if (kexConfig.isMockingEnabled && kexConfig.isMockitoJava8WorkaroundEnabled && isJava8) { + log.info { "Applying workaround for mockito on java 8" } + if (applyJava8Workaround()) { + log.info { "Workaround successfully applied" } + } else { + log.warn { "Workaround failed" } + } + } + } + + private fun applyJava8Workaround(): Boolean { + return tryOrNull { + definePackage("org.mockito.codegen", "", "", "", "", "", "", null) + } != null + } +} \ No newline at end of file diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 05f8b7c8e..79f444944 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -1,12 +1,8 @@ package org.vorpal.research.kex.util -import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.ConcreteClass import org.vorpal.research.kfg.util.toByteArray -import org.vorpal.research.kthelper.logging.info -import org.vorpal.research.kthelper.logging.log -import org.vorpal.research.kthelper.logging.warn import org.vorpal.research.kthelper.tryOrNull import java.nio.file.Path import java.util.jar.JarFile @@ -20,29 +16,10 @@ class KfgClassLoader( private val includes: Set = INCLUDES, private val excludes: Set = EXCLUDES, val transformation: (ConcreteClass) -> Unit = {} -) : ClassLoader() { +) : HackedClassLoader() { private val cache = hashMapOf>() val fallback = PathClassLoader(paths) - init { - val isJava8 = tryOrNull { System.getProperty("java.version") }?.startsWith("1.8") != false - if (kexConfig.isMockingEnabled && kexConfig.isMockitoJava8WorkaroundEnabled && isJava8) { - log.info { "Applying workaround for mockito on java 8" } - if (applyJava8Workaround()) { - log.info { "Workaround successfully applied" } - } else { - log.warn { "Workaround failed" } - } - } - } - - private fun applyJava8Workaround(): Boolean { - return tryOrNull { - definePackage("org.mockito.codegen", "", "", "", "", "", "", null) - } != null - } - - companion object { private val INCLUDES = setOf( "package org.vorpal.research.kex.test.*", diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt index 96cfaf023..e7baba928 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt @@ -1,9 +1,5 @@ package org.vorpal.research.kex.util -import org.vorpal.research.kex.config.kexConfig -import org.vorpal.research.kthelper.logging.info -import org.vorpal.research.kthelper.logging.warn -import org.vorpal.research.kthelper.tryOrNull import java.nio.file.Path import java.util.jar.JarFile import kotlin.io.path.exists @@ -13,25 +9,9 @@ import kotlin.io.path.readBytes class PathClassLoader( val paths: List, parent: ClassLoader = PathClassLoader::class.java.classLoader -) : ClassLoader(parent) { +) : HackedClassLoader(parent) { private val cache = hashMapOf>() - init { - val isJava8 = tryOrNull { System.getProperty("java.version") }?.startsWith("1.8") != false - if (kexConfig.isMockingEnabled && kexConfig.isMockitoJava8WorkaroundEnabled && isJava8) { - org.vorpal.research.kthelper.logging.log.info { "Applying workaround for mockito on java 8" } - if (applyJava8Workaround()) { - org.vorpal.research.kthelper.logging.log.info { "Workaround successfully applied" } - } else { - org.vorpal.research.kthelper.logging.log.warn { "Workaround failed" } - } - } - } - - private fun applyJava8Workaround(): Boolean = tryOrNull { - definePackage("org.mockito.codegen", "", "", "", "", "", "", null) - } != null - private fun readClassFromJar(name: String, path: Path): ByteArray? { val fileName = name.asmString + ".class" val jarFile = JarFile(path.toFile()) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index 63b81fca9..254881b83 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -79,8 +79,8 @@ val Config.logTypeFix: Boolean val Config.logStackTraceTypeFix: Boolean get() = getBooleanValue("mock", "logStackTraceTypeFix", false) -val Config.isMockPessimizationEnabled: Boolean - get() = getBooleanValue("mock", "mockPessimizationEnabled", true) +val Config.isExpectMocks: Boolean + get() = getBooleanValue("mock", "expectMocks", false) val Config.mockito: Container? get() { diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index b538a81ea..6470429aa 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -103,7 +103,7 @@ private fun Parameters.finalizeDescriptors( fun Collection.removeInstance() = this.filterNot { it == instance } val visited = mutableSetOf() - if (kexConfig.isMockPessimizationEnabled) { + if (!kexConfig.isExpectMocks) { if (this.asList.removeInstance().none { it.requireMocks(ctx.types, visited) }) { return this } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt index bff43e684..ed5d3113f 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt @@ -3,6 +3,7 @@ package org.vorpal.research.kex.reanimator.actionsequence import org.vorpal.research.kex.descriptor.Descriptor import org.vorpal.research.kex.descriptor.DescriptorRtMapper import org.vorpal.research.kex.descriptor.MockedMethod +import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.KexRtManager.rtUnmapped @@ -601,12 +602,14 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { private val MockedMethod.mapped get() = when (mode) { KexRtManager.Mode.MAP -> MockedMethod( + klass.rtMapped as KexClass, name.rtMapped, paramTypes.map { it.rtMapped }, returnType.rtMapped ) KexRtManager.Mode.UNMAP -> MockedMethod( + klass.rtUnmapped as KexClass, name.rtUnmapped, paramTypes.map { it.rtUnmapped }, returnType.rtUnmapped From 915e5b56bd53e10954220df0ded2f74a4c424ba8 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 23 Feb 2024 19:53:50 +0100 Subject: [PATCH 085/128] Fix functionalExtensions --- .../research/kex/descriptor/descriptor.kt | 4 +- .../kex/descriptor/functionalExtensions.kt | 42 ++++--- .../research/kex/parameters/Parameters.kt | 106 +++--------------- .../kex/asm/analysis/util/extensions.kt | 3 +- 4 files changed, 43 insertions(+), 112 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 97fb98489..f81f0967b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -719,9 +719,7 @@ class MockDescriptor(term: Term, type: KexClass) : AbstractFieldContainingDescriptor(term, type) { constructor(type: KexClass) : this(term { generate(type) }, type) - constructor(original: ObjectDescriptor) : this(original.term, original.type as KexClass) { - fields.putAll(original.fields) - } + constructor(original: ObjectDescriptor) : this(original.term, original.type as KexClass) val methodReturns: MutableMap> = mutableMapOf() diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt index 27744b7c3..840f421b8 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt @@ -2,36 +2,44 @@ package org.vorpal.research.kex.descriptor fun Descriptor.map( - map: MutableMap, - mapper: (Descriptor) -> Descriptor + mapped: MutableMap, + transform: (Descriptor) -> Descriptor ): Descriptor { - if (map[this] != null) return map[this]!! + if (mapped[this] != null) return mapped[this]!! - val newDescriptor = mapper(this) - map[this] = newDescriptor - map[newDescriptor] = newDescriptor + val newDescriptor = transform(this) + mapped[this] = newDescriptor + mapped[newDescriptor] = newDescriptor when (newDescriptor) { is ConstantDescriptor -> Unit - is ClassDescriptor -> newDescriptor.fields.replaceAll { _, desc -> desc.map(map, mapper) } - is ObjectDescriptor -> newDescriptor.fields.replaceAll { _, desc -> desc.map(map, mapper) } + is ClassDescriptor -> newDescriptor.fields.replaceAll { _, desc -> + desc.map(mapped, transform) + } + + is ObjectDescriptor -> newDescriptor.fields.replaceAll { _, desc -> + desc.map( + mapped, + transform + ) + } + is MockDescriptor -> { - newDescriptor.fields.replaceAll { _, desc -> desc.map(map, mapper) } + newDescriptor.fields.replaceAll { _, desc -> desc.map(mapped, transform) } newDescriptor.methodReturns.values.forEach { values -> - values.replaceAll { d -> d.map(map, mapper) } + values.replaceAll { d -> d.map(mapped, transform) } } } - is ArrayDescriptor -> newDescriptor.elements.replaceAll { _, u -> u.map(map, mapper) } + is ArrayDescriptor -> newDescriptor.elements.replaceAll { _, u -> u.map(mapped, transform) } } return newDescriptor } -fun Descriptor.asSequence(): Sequence = asSequence(mutableSetOf()) -fun Descriptor.asSequence(visited: MutableSet): Sequence { - if (this in visited) return emptySequence() +fun Descriptor.asSequence(visited: MutableSet = mutableSetOf()): Sequence { + if (!visited.add(this)) return emptySequence() return sequenceOf(this) + when (this) { is ConstantDescriptor -> emptySequence() @@ -40,16 +48,14 @@ fun Descriptor.asSequence(visited: MutableSet): Sequence is MockDescriptor -> fields.values.asSequence().flatMap { it.asSequence(visited) } + allReturns - is ArrayDescriptor -> elements.values.asSequence() + is ArrayDescriptor -> elements.values.asSequence().flatMap { it.asSequence(visited) } } } fun Descriptor.forEach( visited: MutableSet = mutableSetOf(), block: (Descriptor) -> Unit -) { - this.asSequence(visited).forEach(block) -} +) = asSequence(visited).forEach(block) fun Descriptor.any( visited: MutableSet = mutableSetOf(), diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index b0630394c..3afff01f8 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -22,7 +22,6 @@ import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory import org.vorpal.research.kthelper.logging.debug -import org.vorpal.research.kthelper.logging.error import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn import kotlin.random.Random @@ -114,42 +113,9 @@ fun createDescriptorToMock( allDescriptors: Collection, types: TypeFactory ): Map { - val map = mutableMapOf() - allDescriptors.map { it.replaceWithMock(types, map) } - return map -} - -private fun Descriptor.insertMocks( - types: TypeFactory, - descriptorToMock: MutableMap, - withMocksInserted: MutableSet -) { - fun Descriptor.replaceWithMock() = replaceWithMock(types, descriptorToMock, withMocksInserted) - - if (this in withMocksInserted) return - withMocksInserted.add(this) - - when (this) { - is ConstantDescriptor -> {} - is ClassDescriptor -> { - fields.mapValuesTo(fields) { (_, value) -> value.replaceWithMock() } - } - - is ObjectDescriptor -> { - fields.mapValuesTo(fields) { (_, value) -> value.replaceWithMock() } - } - - is MockDescriptor -> { - fields.mapValuesTo(fields) { (_, value) -> value.replaceWithMock() } - for ((_, returns) in methodReturns) { - returns.mapTo(returns) { value -> value.replaceWithMock() } - } - } - - is ArrayDescriptor -> { - elements.mapValuesTo(elements) { (_, value) -> value.replaceWithMock() } - } - } + val mapped = mutableMapOf() + allDescriptors.forEach { it.map(mapped) { it.replaceWithMock(types) } } + return mapped } fun Descriptor.isMockable(types: TypeFactory): Boolean { @@ -162,38 +128,19 @@ fun Descriptor.isMockable(types: TypeFactory): Boolean { } } -fun Descriptor.reqMocks2( +fun Descriptor.requireMocks( types: TypeFactory, visited: MutableSet = mutableSetOf() ): Boolean = any(visited) { descriptor -> descriptor.isMockable(types) } -fun Descriptor.requireMocks(types: TypeFactory, visited: MutableSet): Boolean { - if (this in visited) return false - if (this.isMockable(types)) return true - visited.add(this) - fun Descriptor.requireMocks() = requireMocks(types, visited) - return when (this) { - is ConstantDescriptor -> false - is ClassDescriptor -> fields.values.any { it.requireMocks() } - is ObjectDescriptor -> fields.values.any { it.requireMocks() } - is MockDescriptor -> (fields.values + allReturns).any { it.requireMocks() } - is ArrayDescriptor -> elements.values.any { it.requireMocks() } - } -} - -fun Descriptor.replaceWithMock( - types: TypeFactory, - map: MutableMap -): Descriptor { - return this.map(map) { - if (!this.isMockable(types)) { - return@map this - } - this as ObjectDescriptor - val klass = (this.type.getKfgType(types) as? ClassType)?.klass - klass ?: return@map this.also { log.error { "Got null class to mock. Descriptor: $this" } } - - MockDescriptor(this).also { log.debug { "Created mock descriptor for ${it.term}" } } +// TODO, predicate instead of TypeFactory +fun Descriptor.replaceWithMock(types: TypeFactory): Descriptor { + if (!this.isMockable(types)) { + return this + } + return MockDescriptor(this as ObjectDescriptor).apply { + fields.putAll(this@replaceWithMock.fields) + log.debug { "Created mock descriptor for $term" } } } @@ -216,7 +163,6 @@ fun Descriptor.filterConcreteLambdas( ctx: ExecutionContext, visited: MutableMap = mutableMapOf() ): Descriptor { - return this return this.map(visited) { if (this !is ObjectDescriptor) { return@map this @@ -227,34 +173,14 @@ fun Descriptor.filterConcreteLambdas( when (functionalInterfaces.size) { 0 -> this 1 -> descriptor { `object`(functionalInterfaces.first().kexType) } - else -> this + // TODO create mock with all interfaces + else -> descriptor { `object`(functionalInterfaces.random().kexType) }.also { + log.warn { "FIXME: Multiple functional interfaces, so chose random:)" } + } } } } -private fun Descriptor.replaceWithMock( - types: TypeFactory, - descriptorToMock: MutableMap, - withMocksInserted: MutableSet -): Descriptor { - if (descriptorToMock[this] != null) return descriptorToMock[this]!! - if (!this.isMockable(types)) { - this.insertMocks(types, descriptorToMock, withMocksInserted) - return this - } - - this as ObjectDescriptor - val klass = (this.type.getKfgType(types) as? ClassType)?.klass - klass ?: return this.also { log.error { "Got null class to mock. Descriptor: $this" } } - - val mock = MockDescriptor(this).also { log.debug { "Created mock descriptor for ${it.term}" } } - withMocksInserted.add(this) - descriptorToMock[this] = mock - descriptorToMock[mock] = mock - mock.insertMocks(types, descriptorToMock, withMocksInserted) - return mock -} - fun setupMocks( methodCalls: List, termToDescriptor: Map, diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 6470429aa..93f7a4467 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -101,14 +101,15 @@ private fun Parameters.finalizeDescriptors( return this } fun Collection.removeInstance() = this.filterNot { it == instance } - val visited = mutableSetOf() if (!kexConfig.isExpectMocks) { + val visited = mutableSetOf() if (this.asList.removeInstance().none { it.requireMocks(ctx.types, visited) }) { return this } } generator.generateAll() + val visited = mutableSetOf() if (generator.allValues.removeInstance().none { it.requireMocks(ctx.types, visited) }) { return this } From 33bd4aeadea8878beb61ebb76e99ae42f15e10ff Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 23 Feb 2024 22:37:31 +0100 Subject: [PATCH 086/128] Exclude lambdas from easy-random. config entry --- .../research/kex/random/easyrandom/easyRandom.kt | 11 ++++++----- .../kotlin/org/vorpal/research/kex/util/rt.kt | 15 ++++++++++++--- kex-test.ini | 3 +++ kex.ini | 3 +++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt index 3d0d4f8c3..517795365 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt @@ -15,11 +15,7 @@ import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.random.GenerationException import org.vorpal.research.kex.random.Randomizer import org.vorpal.research.kex.random.UnknownTypeException -import org.vorpal.research.kex.util.KfgTargetFilter -import org.vorpal.research.kex.util.asmString -import org.vorpal.research.kex.util.isAbstract -import org.vorpal.research.kex.util.isPublic -import org.vorpal.research.kex.util.isStatic +import org.vorpal.research.kex.util.* import org.vorpal.research.kthelper.assert.ktassert import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.log @@ -79,6 +75,11 @@ class EasyRandomDriver( private val Class<*>.shouldBeExcluded: Boolean get() { + if (kexConfig.isEasyRandomExcludeLambdas) { + if (isInterface && this.annotations.contains(FunctionalInterface::class.java)) { + return true + } + } return config.excludes.any { it.matches(name.asmString) } } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index 254881b83..a206287b2 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -82,6 +82,9 @@ val Config.logStackTraceTypeFix: Boolean val Config.isExpectMocks: Boolean get() = getBooleanValue("mock", "expectMocks", false) +val Config.isEasyRandomExcludeLambdas: Boolean + get() = getBooleanValue("mock", "easyRandomExcludeLambdas", false) + val Config.mockito: Container? get() { val libPath = libPath ?: return null @@ -92,7 +95,7 @@ val Config.mockito: Container? // debug purposes, normally should be false val Config.isMockTest: Boolean - get() = getBooleanValue("mock", "test", false).also{if (it) println("Test feature invoked!")} + get() = getBooleanValue("mock", "test", false).also { if (it) println("Test feature invoked!") } fun getRuntime(): Container? { @@ -105,7 +108,10 @@ fun getRuntime(): Container? { fun getIntrinsics(): Container? { val libPath = kexConfig.libPath ?: return null val intrinsicsVersion = kexConfig.getStringValue("kex", "intrinsicsVersion") ?: return null - return JarContainer(libPath.resolve("kex-intrinsics-${intrinsicsVersion}.jar"), Package.defaultPackage) + return JarContainer( + libPath.resolve("kex-intrinsics-${intrinsicsVersion}.jar"), + Package.defaultPackage + ) } fun getPathSeparator(): String = File.pathSeparator @@ -113,7 +119,10 @@ fun getPathSeparator(): String = File.pathSeparator fun getJunit(): Container? { val libPath = kexConfig.libPath ?: return null val junitVersion = kexConfig.getStringValue("kex", "junitVersion") ?: return null - return JarContainer(libPath.resolve("junit-$junitVersion.jar").toAbsolutePath(), Package.defaultPackage) + return JarContainer( + libPath.resolve("junit-$junitVersion.jar").toAbsolutePath(), + Package.defaultPackage + ) } fun getKexRuntime(): Container? { diff --git a/kex-test.ini b/kex-test.ini index 175553803..2278c3859 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -38,6 +38,9 @@ mockPessimizationEnabled = true ; normally should be missing or false test = false +;exclusions +easyRandomExcludeLambdas = false + ; workarounds mockitoClassesWorkaround = true java8WorkaroundEnabled = true diff --git a/kex.ini b/kex.ini index c45bd33f7..00b4356ae 100644 --- a/kex.ini +++ b/kex.ini @@ -53,6 +53,9 @@ test = false mockPessimizationEnabled = true +;exclusions +easyRandomExcludeLambdas = false + ; workarounds mockitoClassesWorkaround = true java8WorkaroundEnabled = true From e0678a4952774e0f728cc039e7e5ec59f9489919 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 24 Feb 2024 23:07:36 +0100 Subject: [PATCH 087/128] Exclude lambdas from easy-random. config entry --- .../src/main/kotlin/org/vorpal/research/kex/util/rt.kt | 3 +++ kex-test.ini | 9 +++++---- kex.ini | 8 ++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index a206287b2..d070b7be1 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -82,6 +82,9 @@ val Config.logStackTraceTypeFix: Boolean val Config.isExpectMocks: Boolean get() = getBooleanValue("mock", "expectMocks", false) +val Config.isFixConcreteLambdas: Boolean + get() = getBooleanValue("mock", "concreteLambdasPassEnabled", false) + val Config.isEasyRandomExcludeLambdas: Boolean get() = getBooleanValue("mock", "easyRandomExcludeLambdas", false) diff --git a/kex-test.ini b/kex-test.ini index 2278c3859..3fc5d4eb7 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -33,12 +33,13 @@ mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible -mockPessimizationEnabled = true +; true -- mocks are usually needed (hot-path) +expectMocks = true -; normally should be missing or false -test = false +; additional passes over descriptors +concreteLambdasPassEnabled = true -;exclusions +;easy-random tweaks easyRandomExcludeLambdas = false ; workarounds diff --git a/kex.ini b/kex.ini index 00b4356ae..03ed8525f 100644 --- a/kex.ini +++ b/kex.ini @@ -51,9 +51,13 @@ mode = basic ; normally should be missing or false test = false -mockPessimizationEnabled = true +; true -- mocks are usually needed (hot-path) +expectMocks = true -;exclusions +; additional passes over descriptors +concreteLambdasPassEnabled = true + +;easy-random easyRandomExcludeLambdas = false ; workarounds From 95ec4cdf56a72a961421b5cd39c71e1aa1cf0236 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 24 Feb 2024 23:08:01 +0100 Subject: [PATCH 088/128] Descriptor.map renamed to Descriptor. transform --- .../kex/descriptor/functionalExtensions.kt | 27 ++++++++++++++----- .../research/kex/parameters/Parameters.kt | 13 ++++----- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt index 840f421b8..58108d8d4 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt @@ -1,7 +1,7 @@ package org.vorpal.research.kex.descriptor -fun Descriptor.map( +fun Descriptor.transform( mapped: MutableMap, transform: (Descriptor) -> Descriptor ): Descriptor { @@ -14,29 +14,44 @@ fun Descriptor.map( when (newDescriptor) { is ConstantDescriptor -> Unit is ClassDescriptor -> newDescriptor.fields.replaceAll { _, desc -> - desc.map(mapped, transform) + desc.transform(mapped, transform) } is ObjectDescriptor -> newDescriptor.fields.replaceAll { _, desc -> - desc.map( + desc.transform( mapped, transform ) } is MockDescriptor -> { - newDescriptor.fields.replaceAll { _, desc -> desc.map(mapped, transform) } + newDescriptor.fields.replaceAll { _, desc -> desc.transform(mapped, transform) } newDescriptor.methodReturns.values.forEach { values -> - values.replaceAll { d -> d.map(mapped, transform) } + values.replaceAll { d -> d.transform(mapped, transform) } } } - is ArrayDescriptor -> newDescriptor.elements.replaceAll { _, u -> u.map(mapped, transform) } + is ArrayDescriptor -> newDescriptor.elements.replaceAll { _, u -> + u.transform( + mapped, + transform + ) + } } return newDescriptor } +private fun Descriptor.mapImpl( + mapped: MutableMap, + mappedToSelf: MutableSet, + transform: (Descriptor) -> Descriptor, +): Descriptor { + if (mapped[this] != null) return mapped[this]!! + if (this in mappedToSelf) return this + TODO() +} + fun Descriptor.asSequence(visited: MutableSet = mutableSetOf()): Sequence { if (!visited.add(this)) return emptySequence() diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 3afff01f8..89f5a0ca6 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -114,7 +114,7 @@ fun createDescriptorToMock( types: TypeFactory ): Map { val mapped = mutableMapOf() - allDescriptors.forEach { it.map(mapped) { it.replaceWithMock(types) } } + allDescriptors.forEach { it.transform(mapped) { it.replaceWithMock(types) } } return mapped } @@ -133,7 +133,7 @@ fun Descriptor.requireMocks( ): Boolean = any(visited) { descriptor -> descriptor.isMockable(types) } -// TODO, predicate instead of TypeFactory +// TODO, pass predicate in argument instead of TypeFactory fun Descriptor.replaceWithMock(types: TypeFactory): Descriptor { if (!this.isMockable(types)) { return this @@ -159,13 +159,14 @@ fun Class.getFunctionalInterfaces( return acc } -fun Descriptor.filterConcreteLambdas( + +fun Descriptor.fixConcreteLambdas( ctx: ExecutionContext, visited: MutableMap = mutableMapOf() ): Descriptor { - return this.map(visited) { + return this.transform(visited) { if (this !is ObjectDescriptor) { - return@map this + return@transform this } val clazz = this.type as KexClass val kfgClass = clazz.kfgClass(ctx.types) @@ -175,7 +176,7 @@ fun Descriptor.filterConcreteLambdas( 1 -> descriptor { `object`(functionalInterfaces.first().kexType) } // TODO create mock with all interfaces else -> descriptor { `object`(functionalInterfaces.random().kexType) }.also { - log.warn { "FIXME: Multiple functional interfaces, so chose random:)" } + log.warn { "FIXME: Multiple functional interfaces, so chosen random one :D" } } } } From 2874946b593eb7935f5afec84d364d1874192956 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sat, 24 Feb 2024 23:48:40 +0100 Subject: [PATCH 089/128] Refactoring --- .../kex/descriptor/functionalExtensions.kt | 64 +++++++++---------- .../research/kex/parameters/Parameters.kt | 10 +-- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt index 58108d8d4..2c36c212d 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt @@ -1,55 +1,55 @@ package org.vorpal.research.kex.descriptor -fun Descriptor.transform( - mapped: MutableMap, - transform: (Descriptor) -> Descriptor +fun Descriptor.transform( + mapped: MutableMap, + failureMappings: MutableSet = mutableSetOf(), + transformOrNull: (Descriptor) -> T? ): Descriptor { if (mapped[this] != null) return mapped[this]!! + if (this in failureMappings) return this + + val newDescriptor = transformOrNull(this) ?: return this.also { + failureMappings.add(this) + this.transformChildren(mapped, failureMappings, transformOrNull) + } - val newDescriptor = transform(this) mapped[this] = newDescriptor mapped[newDescriptor] = newDescriptor + newDescriptor.transformChildren(mapped, failureMappings, transformOrNull) + return newDescriptor +} - when (newDescriptor) { +private fun Descriptor.transformChildren( + mapped: MutableMap, + failureMappings: MutableSet, + transformOrNull: (Descriptor) -> T? +) { + when (this) { is ConstantDescriptor -> Unit - is ClassDescriptor -> newDescriptor.fields.replaceAll { _, desc -> - desc.transform(mapped, transform) + is ClassDescriptor -> fields.replaceAll { _, descriptor -> + descriptor.transform(mapped, failureMappings, transformOrNull) } - is ObjectDescriptor -> newDescriptor.fields.replaceAll { _, desc -> - desc.transform( - mapped, - transform - ) + is ObjectDescriptor -> fields.replaceAll { _, descriptor -> + descriptor.transform(mapped, failureMappings, transformOrNull) } is MockDescriptor -> { - newDescriptor.fields.replaceAll { _, desc -> desc.transform(mapped, transform) } - newDescriptor.methodReturns.values.forEach { values -> - values.replaceAll { d -> d.transform(mapped, transform) } + fields.replaceAll { _, descriptor -> + descriptor.transform(mapped, failureMappings, transformOrNull) + } + methodReturns.values.forEach { values -> + values.replaceAll { descriptor -> + descriptor.transform(mapped, failureMappings, transformOrNull) + } } } - is ArrayDescriptor -> newDescriptor.elements.replaceAll { _, u -> - u.transform( - mapped, - transform - ) + is ArrayDescriptor -> elements.replaceAll { _, descriptor -> + descriptor.transform(mapped, failureMappings, transformOrNull) } } - - return newDescriptor -} - -private fun Descriptor.mapImpl( - mapped: MutableMap, - mappedToSelf: MutableSet, - transform: (Descriptor) -> Descriptor, -): Descriptor { - if (mapped[this] != null) return mapped[this]!! - if (this in mappedToSelf) return this - TODO() } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 89f5a0ca6..860a55d79 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -112,8 +112,8 @@ fun Parameters.filterIgnoredStatic(): Parameters { fun createDescriptorToMock( allDescriptors: Collection, types: TypeFactory -): Map { - val mapped = mutableMapOf() +): Map { + val mapped = mutableMapOf() allDescriptors.forEach { it.transform(mapped) { it.replaceWithMock(types) } } return mapped } @@ -134,9 +134,9 @@ fun Descriptor.requireMocks( // TODO, pass predicate in argument instead of TypeFactory -fun Descriptor.replaceWithMock(types: TypeFactory): Descriptor { +fun Descriptor.replaceWithMock(types: TypeFactory): MockDescriptor? { if (!this.isMockable(types)) { - return this + return null } return MockDescriptor(this as ObjectDescriptor).apply { fields.putAll(this@replaceWithMock.fields) @@ -185,7 +185,7 @@ fun Descriptor.fixConcreteLambdas( fun setupMocks( methodCalls: List, termToDescriptor: Map, - descriptorToMock: Map + descriptorToMock: Map ) { for (callPredicate in methodCalls) { if (!callPredicate.hasLhv) continue From a6de94707c2415271a15d0e61359945bd389cbc8 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 25 Feb 2024 02:04:22 +0100 Subject: [PATCH 090/128] Fix config usage --- .../org/vorpal/research/kex/random/easyrandom/easyRandom.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt index 517795365..3b61e1aa8 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt @@ -75,7 +75,7 @@ class EasyRandomDriver( private val Class<*>.shouldBeExcluded: Boolean get() { - if (kexConfig.isEasyRandomExcludeLambdas) { + if (kexConfig.isMockingEnabled && kexConfig.isEasyRandomExcludeLambdas) { if (isInterface && this.annotations.contains(FunctionalInterface::class.java)) { return true } From 14b0dba7c8792eb371cb97e878898b229be5544d Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 25 Feb 2024 02:05:17 +0100 Subject: [PATCH 091/128] New MockDescriptor constructor --- .../org/vorpal/research/kex/descriptor/descriptor.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index f81f0967b..9a12eb73b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -477,7 +477,7 @@ sealed class FieldContainingDescriptor>( } class ObjectDescriptor(klass: KexClass) : - FieldContainingDescriptor(term{ generate(klass) }, klass) { + FieldContainingDescriptor(term { generate(klass) }, klass) { override fun deepCopy(copied: MutableMap): Descriptor { if (this in copied) return copied[this]!! val copy = ObjectDescriptor(klass) @@ -700,7 +700,7 @@ data class MockedMethod( val name: String, val paramTypes: List, val returnType: KexType -){ +) { override fun equals(other: Any?): Boolean { if (other !is MockedMethod) return false return name == other.name && paramTypes == other.paramTypes && @@ -720,6 +720,7 @@ class MockDescriptor(term: Term, type: KexClass) : constructor(type: KexClass) : this(term { generate(type) }, type) constructor(original: ObjectDescriptor) : this(original.term, original.type as KexClass) + constructor(original: ObjectDescriptor, type: KexClass) : this(original.term, type) val methodReturns: MutableMap> = mutableMapOf() @@ -906,6 +907,8 @@ open class DescriptorBuilder : StringInfoContext() { ArrayDescriptor(elementType, length) fun mock(type: KexClass) = MockDescriptor(type) + fun mock(original: ObjectDescriptor) = MockDescriptor(original) + fun mock(original: ObjectDescriptor, type: KexClass) = MockDescriptor(original, type) fun default(type: KexType, nullable: Boolean): Descriptor = descriptor { when (type) { @@ -955,6 +958,9 @@ private object DescriptorBuilderImpl : DescriptorBuilder() fun descriptor(body: DescriptorBuilder.() -> Descriptor): Descriptor = DescriptorBuilderImpl.body() +//fun descriptor(body: DescriptorBuilder.() -> T): T = +// DescriptorBuilderImpl.body() + class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilder() { private val cache = mutableMapOf() From f6c18adea3e608e19164452d2fd9b7c9c8229b72 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 25 Feb 2024 02:06:27 +0100 Subject: [PATCH 092/128] Mocking performs through `MockMaker`s --- .../vorpal/research/kex/mocking/MockMaker.kt | 120 ++++++++++++++++++ .../research/kex/parameters/Parameters.kt | 76 ++--------- .../kex/asm/analysis/util/extensions.kt | 8 +- kex-test.ini | 5 + kex.ini | 5 + 5 files changed, 147 insertions(+), 67 deletions(-) create mode 100644 kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt new file mode 100644 index 000000000..8ed8c2255 --- /dev/null +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -0,0 +1,120 @@ +package org.vorpal.research.kex.mocking + +import org.vorpal.research.kex.ExecutionContext +import org.vorpal.research.kex.asm.manager.instantiationManager +import org.vorpal.research.kex.config.Config +import org.vorpal.research.kex.descriptor.Descriptor +import org.vorpal.research.kex.descriptor.MockDescriptor +import org.vorpal.research.kex.descriptor.ObjectDescriptor +import org.vorpal.research.kex.ktype.KexRtManager.isKexRt +import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.util.loadClass +import org.vorpal.research.kfg.ir.Class +import org.vorpal.research.kfg.type.ClassType +import org.vorpal.research.kfg.type.TypeFactory +import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.logging.warn + +sealed class MockMaker(protected val ctx: ExecutionContext) { + protected val types: TypeFactory = ctx.types + + abstract fun canMock(descriptor: Descriptor): Boolean + abstract fun mockOrNull(descriptor: Descriptor): MockDescriptor? + + protected fun necessaryConditions(descriptor: Descriptor): Boolean { + val klass = getKlass(descriptor) ?: return false + return !klass.isFinal && !descriptor.type.isKexRt && descriptor is ObjectDescriptor + } + + protected fun getKlass(descriptor: Descriptor): Class? = + (descriptor.type.getKfgType(types) as? ClassType)?.klass +} + +private class AllMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { + override fun canMock(descriptor: Descriptor): Boolean { + return necessaryConditions(descriptor) + } + + override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { + if (!canMock(descriptor)) return null + descriptor as ObjectDescriptor + return MockDescriptor(descriptor).also { it.fields.putAll(descriptor.fields) } + } +} + +private class UnimplementedMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { + override fun canMock(descriptor: Descriptor): Boolean { + val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass ?: return false + return necessaryConditions(descriptor) && !instantiationManager.isInstantiable(klass) + } + + override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { + if (!canMock(descriptor)) return null + descriptor as ObjectDescriptor + return MockDescriptor(descriptor).also { it.fields.putAll(descriptor.fields) } + } + +} + +private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { + + private fun Class.getFunctionalInterfaces( + interfaces: MutableSet = mutableSetOf() + ): Set { + if (this.isInterface && + ctx.loader.loadClass(this).getAnnotation(FunctionalInterface::class.java) != null + ) { + interfaces.add(this) + return interfaces + } + allAncestors.forEach { it.getFunctionalInterfaces(interfaces) } + return interfaces + } + + override fun canMock(descriptor: Descriptor): Boolean { + val klass = getKlass(descriptor) ?: return false + return necessaryConditions(descriptor) && klass.getFunctionalInterfaces().isNotEmpty() + } + + override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { + if (!canMock(descriptor)) return null + descriptor as ObjectDescriptor + val klass = getKlass(descriptor) ?: return null + val functionalInterfaces = klass.getFunctionalInterfaces() + return when (functionalInterfaces.size) { + 0 -> null + 1 -> org.vorpal.research.kex.descriptor.descriptor { + mock( + descriptor, + functionalInterfaces.first().kexType + ) + } + // TODO create mock with all interfaces + else -> org.vorpal.research.kex.descriptor.descriptor { + mock( + descriptor, + functionalInterfaces.random().kexType + ) + }.also { + log.warn { "FIXME: Multiple functional interfaces, so chosen the random one :D" } + } + } as? MockDescriptor + } +} + +enum class MockingRule { + // Order is important! First rule applies first + LAMBDA, ANY, UNIMPLEMENTED +} + +fun createMocker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { + MockingRule.LAMBDA -> LambdaMockMaker(ctx) + MockingRule.ANY -> AllMockMaker(ctx) + MockingRule.UNIMPLEMENTED -> UnimplementedMockMaker(ctx) +} + +fun Config.getMockMakers(ctx: ExecutionContext): List = + getMultipleStringValue("mock", "rule") + .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } + .sortedBy { rule -> rule.ordinal } + .map { rule -> createMocker(rule, ctx) } \ No newline at end of file diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 860a55d79..06d2382e3 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -10,6 +10,7 @@ import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager.isKexRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.mocking.MockMaker import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term @@ -111,76 +112,23 @@ fun Parameters.filterIgnoredStatic(): Parameters { fun createDescriptorToMock( allDescriptors: Collection, - types: TypeFactory + mockMakers: List ): Map { - val mapped = mutableMapOf() - allDescriptors.forEach { it.transform(mapped) { it.replaceWithMock(types) } } - return mapped -} - -fun Descriptor.isMockable(types: TypeFactory): Boolean { - val klass = (type.getKfgType(types) as? ClassType)?.klass ?: return false - val necessaryConditions = !klass.isFinal && !type.isKexRt && this is ObjectDescriptor - return necessaryConditions && when (kexConfig.mockingMode) { - MockingMode.FULL -> true - MockingMode.BASIC -> !instantiationManager.isInstantiable(klass) - null -> false - } -} - -fun Descriptor.requireMocks( - types: TypeFactory, visited: MutableSet = mutableSetOf() -): Boolean = any(visited) { descriptor -> descriptor.isMockable(types) } - - -// TODO, pass predicate in argument instead of TypeFactory -fun Descriptor.replaceWithMock(types: TypeFactory): MockDescriptor? { - if (!this.isMockable(types)) { - return null - } - return MockDescriptor(this as ObjectDescriptor).apply { - fields.putAll(this@replaceWithMock.fields) - log.debug { "Created mock descriptor for $term" } + val descriptorToMock = mutableMapOf() + allDescriptors.forEach { + it.transform(descriptorToMock) { descriptor -> + mockMakers.firstNotNullOfOrNull { mockMaker -> mockMaker.mockOrNull(descriptor) } + } } + return descriptorToMock } -fun Class.getFunctionalInterfaces( - ctx: ExecutionContext, - acc: MutableSet = mutableSetOf() -): Set { - if (this.isInterface && - ctx.loader.loadClass(this).getAnnotation(FunctionalInterface::class.java) != null - ) { - acc.add(this) - return acc - } - allAncestors.forEach { it.getFunctionalInterfaces(ctx, acc) } - return acc -} - +fun Descriptor.requireMocks( + mockMakers: List, visited: MutableSet = mutableSetOf() +): Boolean = + any(visited) { descriptor -> mockMakers.any { mockMaker -> mockMaker.canMock(descriptor) } } -fun Descriptor.fixConcreteLambdas( - ctx: ExecutionContext, - visited: MutableMap = mutableMapOf() -): Descriptor { - return this.transform(visited) { - if (this !is ObjectDescriptor) { - return@transform this - } - val clazz = this.type as KexClass - val kfgClass = clazz.kfgClass(ctx.types) - val functionalInterfaces = kfgClass.getFunctionalInterfaces(ctx) - when (functionalInterfaces.size) { - 0 -> this - 1 -> descriptor { `object`(functionalInterfaces.first().kexType) } - // TODO create mock with all interfaces - else -> descriptor { `object`(functionalInterfaces.random().kexType) }.also { - log.warn { "FIXME: Multiple functional interfaces, so chosen random one :D" } - } - } - } -} fun setupMocks( methodCalls: List, diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 93f7a4467..cbe2f818e 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -11,6 +11,7 @@ import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.mocking.getMockMakers import org.vorpal.research.kex.parameters.* import org.vorpal.research.kex.smt.AsyncChecker import org.vorpal.research.kex.smt.AsyncIncrementalChecker @@ -102,19 +103,20 @@ private fun Parameters.finalizeDescriptors( } fun Collection.removeInstance() = this.filterNot { it == instance } + val mockMakers = kexConfig.getMockMakers(ctx) if (!kexConfig.isExpectMocks) { val visited = mutableSetOf() - if (this.asList.removeInstance().none { it.requireMocks(ctx.types, visited) }) { + if (this.asList.removeInstance().none { it.requireMocks(mockMakers, visited) }) { return this } } generator.generateAll() val visited = mutableSetOf() - if (generator.allValues.removeInstance().none { it.requireMocks(ctx.types, visited) }) { + if (generator.allValues.removeInstance().none { it.requireMocks(mockMakers, visited) }) { return this } - val descriptorToMock = createDescriptorToMock(generator.allValues.removeInstance(), ctx.types) + val descriptorToMock = createDescriptorToMock(generator.allValues.removeInstance(), mockMakers) val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = state.methodCalls() setupMocks(methodCalls, generator.memory, descriptorToMock) diff --git a/kex-test.ini b/kex-test.ini index 3fc5d4eb7..499cb3955 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -33,6 +33,11 @@ mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible +; mocking rules +rule = unimplemented +;rule = lambda +rule = any + ; true -- mocks are usually needed (hot-path) expectMocks = true diff --git a/kex.ini b/kex.ini index 03ed8525f..ca1bcc207 100644 --- a/kex.ini +++ b/kex.ini @@ -48,6 +48,11 @@ mode = basic ; basic to mock only when can't create otherwise ; full to mock as much as possible +; mocking rules +rule = unimplemented +;rule = lambda +rule = any + ; normally should be missing or false test = false From 49a3bd15184b11613e757e59e6c5c640bbfda330 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 25 Feb 2024 02:07:13 +0100 Subject: [PATCH 093/128] Imports optimized --- .../org/vorpal/research/kex/parameters/Parameters.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 06d2382e3..9076f66d0 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -1,28 +1,17 @@ package org.vorpal.research.kex.parameters import kotlinx.serialization.Serializable -import org.vorpal.research.kex.ExecutionContext -import org.vorpal.research.kex.asm.manager.instantiationManager import org.vorpal.research.kex.asm.util.AccessModifier import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexClass -import org.vorpal.research.kex.ktype.KexRtManager.isKexRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped -import org.vorpal.research.kex.ktype.kexType import org.vorpal.research.kex.mocking.MockMaker import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.util.KfgTargetFilter -import org.vorpal.research.kex.util.MockingMode -import org.vorpal.research.kex.util.loadClass -import org.vorpal.research.kex.util.mockingMode import org.vorpal.research.kfg.ClassManager -import org.vorpal.research.kfg.ir.Class -import org.vorpal.research.kfg.type.ClassType -import org.vorpal.research.kfg.type.TypeFactory -import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn import kotlin.random.Random From 1e40c331ab5fd38ffd46e42eeee3610806618bc0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 25 Feb 2024 02:37:27 +0100 Subject: [PATCH 094/128] Refactoring --- .../vorpal/research/kex/mocking/MockMaker.kt | 39 +++++-------------- .../vorpal/research/kex/mocking/mockUtils.kt | 15 +++++++ 2 files changed, 24 insertions(+), 30 deletions(-) create mode 100644 kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index 8ed8c2255..b35de19fa 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -2,10 +2,10 @@ package org.vorpal.research.kex.mocking import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.manager.instantiationManager -import org.vorpal.research.kex.config.Config import org.vorpal.research.kex.descriptor.Descriptor import org.vorpal.research.kex.descriptor.MockDescriptor import org.vorpal.research.kex.descriptor.ObjectDescriptor +import org.vorpal.research.kex.descriptor.descriptor import org.vorpal.research.kex.ktype.KexRtManager.isKexRt import org.vorpal.research.kex.ktype.kexType import org.vorpal.research.kex.util.loadClass @@ -21,7 +21,7 @@ sealed class MockMaker(protected val ctx: ExecutionContext) { abstract fun canMock(descriptor: Descriptor): Boolean abstract fun mockOrNull(descriptor: Descriptor): MockDescriptor? - protected fun necessaryConditions(descriptor: Descriptor): Boolean { + protected fun satisfiesNecessaryConditions(descriptor: Descriptor): Boolean { val klass = getKlass(descriptor) ?: return false return !klass.isFinal && !descriptor.type.isKexRt && descriptor is ObjectDescriptor } @@ -32,7 +32,7 @@ sealed class MockMaker(protected val ctx: ExecutionContext) { private class AllMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { override fun canMock(descriptor: Descriptor): Boolean { - return necessaryConditions(descriptor) + return satisfiesNecessaryConditions(descriptor) } override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { @@ -45,7 +45,8 @@ private class AllMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { private class UnimplementedMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { override fun canMock(descriptor: Descriptor): Boolean { val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass ?: return false - return necessaryConditions(descriptor) && !instantiationManager.isInstantiable(klass) + return satisfiesNecessaryConditions(descriptor) && + !instantiationManager.isInstantiable(klass) } override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { @@ -53,7 +54,6 @@ private class UnimplementedMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { descriptor as ObjectDescriptor return MockDescriptor(descriptor).also { it.fields.putAll(descriptor.fields) } } - } private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { @@ -72,8 +72,8 @@ private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { } override fun canMock(descriptor: Descriptor): Boolean { - val klass = getKlass(descriptor) ?: return false - return necessaryConditions(descriptor) && klass.getFunctionalInterfaces().isNotEmpty() + val functionalInterfaces = getKlass(descriptor)?.getFunctionalInterfaces() ?: emptySet() + return satisfiesNecessaryConditions(descriptor) && functionalInterfaces.isNotEmpty() } override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { @@ -83,38 +83,17 @@ private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { val functionalInterfaces = klass.getFunctionalInterfaces() return when (functionalInterfaces.size) { 0 -> null - 1 -> org.vorpal.research.kex.descriptor.descriptor { - mock( - descriptor, - functionalInterfaces.first().kexType - ) - } + 1 -> descriptor { mock(descriptor, functionalInterfaces.first().kexType) } // TODO create mock with all interfaces - else -> org.vorpal.research.kex.descriptor.descriptor { - mock( - descriptor, - functionalInterfaces.random().kexType - ) - }.also { + else -> descriptor { mock(descriptor, functionalInterfaces.random().kexType) }.also { log.warn { "FIXME: Multiple functional interfaces, so chosen the random one :D" } } } as? MockDescriptor } } -enum class MockingRule { - // Order is important! First rule applies first - LAMBDA, ANY, UNIMPLEMENTED -} - fun createMocker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { MockingRule.LAMBDA -> LambdaMockMaker(ctx) MockingRule.ANY -> AllMockMaker(ctx) MockingRule.UNIMPLEMENTED -> UnimplementedMockMaker(ctx) } - -fun Config.getMockMakers(ctx: ExecutionContext): List = - getMultipleStringValue("mock", "rule") - .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } - .sortedBy { rule -> rule.ordinal } - .map { rule -> createMocker(rule, ctx) } \ No newline at end of file diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt new file mode 100644 index 000000000..f403b542a --- /dev/null +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -0,0 +1,15 @@ +package org.vorpal.research.kex.mocking + +import org.vorpal.research.kex.ExecutionContext +import org.vorpal.research.kex.config.Config + +enum class MockingRule { + // Order is important! First rule applies first + LAMBDA, ANY, UNIMPLEMENTED +} + +fun Config.getMockMakers(ctx: ExecutionContext): List = + getMultipleStringValue("mock", "rule") + .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } + .sortedBy { rule -> rule.ordinal } + .map { rule -> createMocker(rule, ctx) } \ No newline at end of file From eebc99d645c4447ed6b5497b8848e0a60f8bd727 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 25 Feb 2024 17:43:26 +0100 Subject: [PATCH 095/128] Method.general(). Will fix issues with inheritance --- .../research/kex/descriptor/descriptor.kt | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 9a12eb73b..42435041b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -15,9 +15,13 @@ import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.util.StringInfoContext import org.vorpal.research.kfg.ClassManager +import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.assert.unreachable +import org.vorpal.research.kthelper.collection.queueOf import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.logging.warn +import org.vorpal.research.kthelper.tryOrNull import ru.spbstu.wheels.hashCombine import ru.spbstu.wheels.joinToString import kotlin.random.Random @@ -713,7 +717,45 @@ data class MockedMethod( } -private val Method.mocked: MockedMethod get() = MockedMethod(klass.kexType, name, argTypes.map{it.kexType}, returnType.kexType) +private val Method.mocked: MockedMethod + get() = MockedMethod( + klass.kexType, + name, + argTypes.map { it.kexType }, + returnType.kexType + ) + +fun Class.isOverride(method: Method): Boolean = + tryOrNull { this.getMethod(method.name, method.desc) } != null + +fun Class.hasAncestorOverrides(method: Method): Boolean = + allAncestors.any { klass -> klass.isOverride(method) } + +fun Method.general(): Method { + var ancestors = this.klass.allAncestors.filter { it.isOverride(this) } + val possibleGeneralizations = mutableSetOf() + while (ancestors.isNotEmpty()) { + possibleGeneralizations.addAll(ancestors + .filterNot { it.hasAncestorOverrides(this) } + .map { it.getMethod(this.name, this.desc) }) + + ancestors = ancestors.flatMap { it.allAncestors }.filter { it.isOverride(this) } + } + return when (possibleGeneralizations.size) { + 0 -> this + 1 -> possibleGeneralizations.first() + + else -> { + log.warn { + "Got multiple general version of method $this. Versions:\n ${ + possibleGeneralizations.joinToString(separator = "\n") + }\nPlease check your code." + } + possibleGeneralizations.first() + } + } +} + class MockDescriptor(term: Term, type: KexClass) : AbstractFieldContainingDescriptor(term, type) { From 3b3bef2a93c6c3e2b64742da7776aa0bc75d7285 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 25 Feb 2024 17:52:29 +0100 Subject: [PATCH 096/128] Use Method (from kfg) instead of MockMethod in MockDescriptor. Fix inheritance issue for that --- .../research/kex/descriptor/descriptor.kt | 67 +++---------------- .../research/kex/serialization/descriptor.kt | 48 +++++++++---- .../kex/asm/analysis/util/extensions.kt | 6 ++ .../actionsequence/ActionSequence.kt | 21 ++---- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 6 +- 5 files changed, 59 insertions(+), 89 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 42435041b..f4e065c42 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -2,7 +2,6 @@ package org.vorpal.research.kex.descriptor -import kotlinx.serialization.Serializable import org.vorpal.research.kex.asm.manager.instantiationManager import org.vorpal.research.kex.asm.util.AccessModifier import org.vorpal.research.kex.ktype.* @@ -18,11 +17,9 @@ import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.assert.unreachable -import org.vorpal.research.kthelper.collection.queueOf import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn import org.vorpal.research.kthelper.tryOrNull -import ru.spbstu.wheels.hashCombine import ru.spbstu.wheels.joinToString import kotlin.random.Random @@ -698,33 +695,6 @@ class ArrayDescriptor(val elementType: KexType, val length: Int) : } -@Serializable -data class MockedMethod( - val klass: KexClass, // excluded from hashCode and equals - val name: String, - val paramTypes: List, - val returnType: KexType -) { - override fun equals(other: Any?): Boolean { - if (other !is MockedMethod) return false - return name == other.name && paramTypes == other.paramTypes && - returnType == other.returnType - } - - override fun hashCode(): Int { - return hashCombine(name, paramTypes, returnType) - } -} - - -private val Method.mocked: MockedMethod - get() = MockedMethod( - klass.kexType, - name, - argTypes.map { it.kexType }, - returnType.kexType - ) - fun Class.isOverride(method: Method): Boolean = tryOrNull { this.getMethod(method.name, method.desc) } != null @@ -764,25 +734,21 @@ class MockDescriptor(term: Term, type: KexClass) : constructor(original: ObjectDescriptor) : this(original.term, original.type as KexClass) constructor(original: ObjectDescriptor, type: KexClass) : this(original.term, type) - val methodReturns: MutableMap> = mutableMapOf() + val methodReturns: MutableMap> = mutableMapOf() val allReturns: Sequence get() = methodReturns.values.asSequence().flatMap { it.asSequence() } - val methods: Set + val methods: Set get() = methodReturns.keys - operator fun get(mockedMethod: MockedMethod) = methodReturns[mockedMethod] - operator fun set(mockedMethod: MockedMethod, values: List) { - methodReturns[mockedMethod] = values.toMutableList() + operator fun get(mockedMethod: Method) = methodReturns[mockedMethod.general()] + operator fun set(mockedMethod: Method, values: List) { + methodReturns[mockedMethod.general()] = values.toMutableList() } - fun addReturnValue(method: Method, value: Descriptor) { - addReturnValue(method.mocked, value) - } - - fun addReturnValue(mockedMethod: MockedMethod, value: Descriptor) { - methodReturns.getOrPut(mockedMethod) { mutableListOf() }.add(value) + fun addReturnValue(mockedMethod: Method, value: Descriptor) { + methodReturns.getOrPut(mockedMethod.general()) { mutableListOf() }.add(value) } override fun concretize( @@ -813,7 +779,7 @@ class MockDescriptor(term: Term, type: KexClass) : reduceFields(visited) for ((method, list) in methodReturns) { val type = method.returnType - while (list.isNotEmpty() && list.last() eq descriptor { default(type) }) { + while (list.isNotEmpty() && list.last() eq descriptor { default(type.kexType) }) { list.removeLast() } } @@ -1012,21 +978,10 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde KexRtManager.Mode.UNMAP -> rtUnmapped } - private val MockedMethod.mapped + private val Method.mapped get() = when (mode) { - KexRtManager.Mode.MAP -> MockedMethod( - klass.rtMapped as KexClass, - name.rtMapped, - paramTypes.map { it.rtMapped }, - returnType.rtMapped - ) - - KexRtManager.Mode.UNMAP -> MockedMethod( - klass.rtUnmapped as KexClass, - name.rtUnmapped, - paramTypes.map { it.rtUnmapped }, - returnType.rtUnmapped - ) + KexRtManager.Mode.MAP -> rtMapped + KexRtManager.Mode.UNMAP -> rtUnmapped } fun map(descriptor: Descriptor): Descriptor = cache.getOrElse(descriptor) { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt index 377cdf9b3..4bc637190 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt @@ -1,5 +1,6 @@ package org.vorpal.research.kex.serialization +import kotlinx.serialization.Contextual import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException @@ -14,6 +15,7 @@ import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexArray import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexType +import org.vorpal.research.kfg.ir.Method @JvmInline @Serializable @@ -33,7 +35,10 @@ internal sealed class DescriptorWrapper { return context.getValue(id) } - protected abstract fun convert(map: Map, output: MutableMap) + protected abstract fun convert( + map: Map, + output: MutableMap + ) @Serializable class Constant( @@ -70,7 +75,7 @@ internal sealed class DescriptorWrapper { override val id: Id, override val type: KexType, val fields: MutableList, Id>>, - val methodReturns: MutableList>> + val methodReturns: MutableList>> ) : DescriptorWrapper() { override fun convert(map: Map, output: MutableMap) { if (id in output) return @@ -146,14 +151,30 @@ private fun Descriptor.toWrapper(visited: MutableMap) { if (id in visited) return when (this) { ConstantDescriptor.Null -> visited[id] = DescriptorWrapper.Constant(id, this.type, "null") - is ConstantDescriptor.Bool -> visited[id] = DescriptorWrapper.Constant(id, this.type, "${this.value}") - is ConstantDescriptor.Byte -> visited[id] = DescriptorWrapper.Constant(id, this.type, "${this.value}") - is ConstantDescriptor.Char -> visited[id] = DescriptorWrapper.Constant(id, this.type, "${this.value}") - is ConstantDescriptor.Short -> visited[id] = DescriptorWrapper.Constant(id, this.type, "${this.value}") - is ConstantDescriptor.Int -> visited[id] = DescriptorWrapper.Constant(id, this.type, "${this.value}") - is ConstantDescriptor.Long -> visited[id] = DescriptorWrapper.Constant(id, this.type, "${this.value}") - is ConstantDescriptor.Float -> visited[id] = DescriptorWrapper.Constant(id, this.type, "${this.value}") - is ConstantDescriptor.Double -> visited[id] = DescriptorWrapper.Constant(id, this.type, "${this.value}") + is ConstantDescriptor.Bool -> visited[id] = + DescriptorWrapper.Constant(id, this.type, "${this.value}") + + is ConstantDescriptor.Byte -> visited[id] = + DescriptorWrapper.Constant(id, this.type, "${this.value}") + + is ConstantDescriptor.Char -> visited[id] = + DescriptorWrapper.Constant(id, this.type, "${this.value}") + + is ConstantDescriptor.Short -> visited[id] = + DescriptorWrapper.Constant(id, this.type, "${this.value}") + + is ConstantDescriptor.Int -> visited[id] = + DescriptorWrapper.Constant(id, this.type, "${this.value}") + + is ConstantDescriptor.Long -> visited[id] = + DescriptorWrapper.Constant(id, this.type, "${this.value}") + + is ConstantDescriptor.Float -> visited[id] = + DescriptorWrapper.Constant(id, this.type, "${this.value}") + + is ConstantDescriptor.Double -> visited[id] = + DescriptorWrapper.Constant(id, this.type, "${this.value}") + is ArrayDescriptor -> { val array = DescriptorWrapper.Array(id, this.type, this.length, mutableMapOf()).also { visited[id] = it @@ -191,9 +212,10 @@ private fun Descriptor.toWrapper(visited: MutableMap) { } is MockDescriptor -> { - val instance = DescriptorWrapper.Mock(id, this.type, mutableListOf(), mutableListOf()).also { - visited[id] = it - } + val instance = + DescriptorWrapper.Mock(id, this.type, mutableListOf(), mutableListOf()).also { + visited[id] = it + } for ((field, value) in this.fields) { instance.fields += field to value.id } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index cbe2f818e..21cd43693 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -19,6 +19,7 @@ import org.vorpal.research.kex.smt.Result import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery import org.vorpal.research.kex.state.predicate.CallPredicate +import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState @@ -119,6 +120,11 @@ private fun Parameters.finalizeDescriptors( val descriptorToMock = createDescriptorToMock(generator.allValues.removeInstance(), mockMakers) val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = state.methodCalls() + val methods = methodCalls.map { (it.call as CallTerm).method } + val mapped = methods.map{it.general() } + if (methods != mapped){ + 42 + } setupMocks(methodCalls, generator.memory, descriptorToMock) return withMocks } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt index ed5d3113f..57c1fa9e0 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt @@ -2,8 +2,6 @@ package org.vorpal.research.kex.reanimator.actionsequence import org.vorpal.research.kex.descriptor.Descriptor import org.vorpal.research.kex.descriptor.DescriptorRtMapper -import org.vorpal.research.kex.descriptor.MockedMethod -import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.KexRtManager.rtUnmapped @@ -235,7 +233,7 @@ data class MockNewInstance(val klass: Class) : MockCall { } data class MockSetupMethod( - val method: MockedMethod, val returnValues: List + val method: Method, val returnValues: List ) : MockCall { override val parameters: List get() = returnValues @@ -599,21 +597,10 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { KexRtManager.Mode.UNMAP -> rtUnmapped } - private val MockedMethod.mapped + private val Method.mapped get() = when (mode) { - KexRtManager.Mode.MAP -> MockedMethod( - klass.rtMapped as KexClass, - name.rtMapped, - paramTypes.map { it.rtMapped }, - returnType.rtMapped - ) - - KexRtManager.Mode.UNMAP -> MockedMethod( - klass.rtUnmapped as KexClass, - name.rtUnmapped, - paramTypes.map { it.rtUnmapped }, - returnType.rtUnmapped - ) + KexRtManager.Mode.MAP -> rtMapped + KexRtManager.Mode.UNMAP -> rtUnmapped } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index dee6251e8..8c07816bc 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -563,11 +563,11 @@ class ExecutorAS2JavaPrinter( call.returnValues.forEach { it.printAsJava() } val returns = call.returnValues.joinToString(separator = ", ") { value -> - value.forceCastIfNull(call.method.returnType.getKfgType(ctx.types).asType) + value.forceCastIfNull(call.method.returnType.asType) } - val anys = call.method.paramTypes.joinToString(separator = ", ") { type -> - mockitoAnyFromType(type.getKfgType(ctx.types)) + val anys = call.method.argTypes.joinToString(separator = ", ") { type -> + mockitoAnyFromType(type) } val methodName = call.method.name From d870178745eb44e74f845b254377de7ea7b30904 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 25 Feb 2024 17:56:14 +0100 Subject: [PATCH 097/128] Use Method.klass to perform cast --- .../kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 8c07816bc..425bcf677 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -510,11 +510,8 @@ class ExecutorAS2JavaPrinter( return listOf("${setElementMethod.name}(${owner.name}, ${call.index.stackName}, ${call.value.stackName})") } - // FIXME: remove that map - private val mockType: MutableMap = mutableMapOf() override fun printMockNewInstance(owner: ActionSequence, call: MockNewInstance): List { builder.import("org.mockito.Mockito") - mockType[owner] = call.klass val actualType = ASClass(ctx.types.objectType) val kfgClass = call.klass return listOf( @@ -571,7 +568,7 @@ class ExecutorAS2JavaPrinter( } val methodName = call.method.name - val instance = "(${owner.cast(mockType[owner]!!.asType.asType)})" + val instance = "(${owner.cast(call.method.klass.asType.asType)})" return listOf("Mockito.when($instance.$methodName($anys)).thenReturn($returns)") } } From 10a8f311d1d38f66a4ed123694cb10f045e0fa39 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Sun, 25 Feb 2024 17:56:59 +0100 Subject: [PATCH 098/128] Fixes, removed testing code --- .../org/vorpal/research/kex/asm/analysis/util/extensions.kt | 5 ----- .../kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt | 1 - 2 files changed, 6 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 21cd43693..5647c77b5 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -120,11 +120,6 @@ private fun Parameters.finalizeDescriptors( val descriptorToMock = createDescriptorToMock(generator.allValues.removeInstance(), mockMakers) val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = state.methodCalls() - val methods = methodCalls.map { (it.call as CallTerm).method } - val mapped = methods.map{it.general() } - if (methods != mapped){ - 42 - } setupMocks(methodCalls, generator.memory, descriptorToMock) return withMocks } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index 425bcf677..c581d99cc 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -5,7 +5,6 @@ import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.ktype.* import org.vorpal.research.kex.parameters.Parameters import org.vorpal.research.kex.reanimator.actionsequence.* -import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.type.* import org.vorpal.research.kthelper.assert.unreachable import org.vorpal.research.kthelper.logging.error From f87fecd9137067d5b672573021626fe6ddd72d56 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 26 Feb 2024 02:30:49 +0100 Subject: [PATCH 099/128] Select multiple mocking rules instead of one mockingMode. Can generate mocks with multiple interfaces (useful for lambdas). When trying to mock lambda, when class implements multiple functional interfaces just creating mock with all of them. --- .../research/kex/descriptor/descriptor.kt | 11 ++++- .../vorpal/research/kex/mocking/MockMaker.kt | 17 +++---- .../research/kex/serialization/descriptor.kt | 8 +++- .../kotlin/org/vorpal/research/kex/util/rt.kt | 7 --- .../kex/asm/analysis/util/extensions.kt | 3 +- .../actionsequence/ActionSequence.kt | 4 +- .../actionsequence/generator/MockGenerator.kt | 44 ++++++++++++------- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 7 ++- .../kex/concolic/MockConcolicLongTest.kt | 9 ++-- kex-test.ini | 13 ++---- kex.ini | 13 ++---- 11 files changed, 70 insertions(+), 66 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index f4e065c42..442594b55 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -727,14 +727,18 @@ fun Method.general(): Method { } -class MockDescriptor(term: Term, type: KexClass) : +class MockDescriptor(term: Term, type: KexClass, additionalInterfaces: Set = setOf()) : AbstractFieldContainingDescriptor(term, type) { constructor(type: KexClass) : this(term { generate(type) }, type) constructor(original: ObjectDescriptor) : this(original.term, original.type as KexClass) constructor(original: ObjectDescriptor, type: KexClass) : this(original.term, type) + constructor( + original: ObjectDescriptor, type: KexClass, additionalInterfaces: Set + ) : this(original.term, type, additionalInterfaces) val methodReturns: MutableMap> = mutableMapOf() + val extraInterfaces: MutableSet = additionalInterfaces.toMutableSet() val allReturns: Sequence get() = methodReturns.values.asSequence().flatMap { it.asSequence() } @@ -917,6 +921,8 @@ open class DescriptorBuilder : StringInfoContext() { fun mock(type: KexClass) = MockDescriptor(type) fun mock(original: ObjectDescriptor) = MockDescriptor(original) fun mock(original: ObjectDescriptor, type: KexClass) = MockDescriptor(original, type) + fun mock(original: ObjectDescriptor, type: KexClass, additionalInterfaces: Set) = + MockDescriptor(original, type, additionalInterfaces) fun default(type: KexType, nullable: Boolean): Descriptor = descriptor { when (type) { @@ -1018,7 +1024,8 @@ class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilde val mockMapped = MockDescriptor( descriptor.term, - descriptor.type.mapped as KexClass + descriptor.type.mapped as KexClass, + descriptor.extraInterfaces.map { it.mapped as KexClass }.toSet() ) cache[descriptor] = mockMapped for ((field, value) in descriptor.fields) { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index b35de19fa..3cabb238f 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -6,14 +6,14 @@ import org.vorpal.research.kex.descriptor.Descriptor import org.vorpal.research.kex.descriptor.MockDescriptor import org.vorpal.research.kex.descriptor.ObjectDescriptor import org.vorpal.research.kex.descriptor.descriptor +import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager.isKexRt import org.vorpal.research.kex.ktype.kexType import org.vorpal.research.kex.util.loadClass import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory -import org.vorpal.research.kthelper.logging.log -import org.vorpal.research.kthelper.logging.warn +import org.vorpal.research.kfg.type.objectType sealed class MockMaker(protected val ctx: ExecutionContext) { protected val types: TypeFactory = ctx.types @@ -57,7 +57,6 @@ private class UnimplementedMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { } private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { - private fun Class.getFunctionalInterfaces( interfaces: MutableSet = mutableSetOf() ): Set { @@ -65,7 +64,6 @@ private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { ctx.loader.loadClass(this).getAnnotation(FunctionalInterface::class.java) != null ) { interfaces.add(this) - return interfaces } allAncestors.forEach { it.getFunctionalInterfaces(interfaces) } return interfaces @@ -73,7 +71,7 @@ private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { override fun canMock(descriptor: Descriptor): Boolean { val functionalInterfaces = getKlass(descriptor)?.getFunctionalInterfaces() ?: emptySet() - return satisfiesNecessaryConditions(descriptor) && functionalInterfaces.isNotEmpty() + return !descriptor.type.isKexRt && descriptor is ObjectDescriptor && functionalInterfaces.isNotEmpty() } override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { @@ -84,9 +82,12 @@ private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { return when (functionalInterfaces.size) { 0 -> null 1 -> descriptor { mock(descriptor, functionalInterfaces.first().kexType) } - // TODO create mock with all interfaces - else -> descriptor { mock(descriptor, functionalInterfaces.random().kexType) }.also { - log.warn { "FIXME: Multiple functional interfaces, so chosen the random one :D" } + else -> descriptor { + mock( + descriptor, + ctx.types.objectType.kexType as KexClass, + functionalInterfaces.map { it.kexType }.toSet() + ) } } as? MockDescriptor } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt index 4bc637190..156819b62 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/serialization/descriptor.kt @@ -75,13 +75,15 @@ internal sealed class DescriptorWrapper { override val id: Id, override val type: KexType, val fields: MutableList, Id>>, - val methodReturns: MutableList>> + val methodReturns: MutableList>>, + val extraInterfaces: MutableSet ) : DescriptorWrapper() { override fun convert(map: Map, output: MutableMap) { if (id in output) return val instance = descriptor { mock(type as KexClass) }.also { output[id] = it } as MockDescriptor + instance.extraInterfaces.addAll(extraInterfaces) for ((field, fieldId) in fields) { map.getValue(fieldId).toDescriptor(map, output) @@ -213,7 +215,9 @@ private fun Descriptor.toWrapper(visited: MutableMap) { is MockDescriptor -> { val instance = - DescriptorWrapper.Mock(id, this.type, mutableListOf(), mutableListOf()).also { + DescriptorWrapper.Mock( + id, this.type, mutableListOf(), mutableListOf(), this.extraInterfaces + ).also { visited[id] = it } for ((field, value) in this.fields) { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index d070b7be1..01bc002d4 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -60,13 +60,6 @@ fun getJavaPath(): Path = Paths.get(System.getProperty("java.home"), "bin", "jav val Config.isMockingEnabled: Boolean get() = getBooleanValue("mock", "enabled", false) -enum class MockingMode { - BASIC, FULL -} - -val Config.mockingMode: MockingMode? - get() = getEnumValue("mock", "mode", ignoreCase = true) - val Config.isMockitoClassesWorkaroundEnabled: Boolean get() = getBooleanValue("mock", "mockitoClassesWorkaround", true) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 5647c77b5..d05e84d47 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -19,7 +19,6 @@ import org.vorpal.research.kex.smt.Result import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery import org.vorpal.research.kex.state.predicate.CallPredicate -import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState @@ -99,7 +98,7 @@ private fun Parameters.finalizeDescriptors( generator: DescriptorGenerator, state: SymbolicState ): Parameters { - if (!kexConfig.isMockingEnabled || kexConfig.mockingMode == null) { + if (!kexConfig.isMockingEnabled) { return this } fun Collection.removeInstance() = this.filterNot { it == instance } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt index 57c1fa9e0..b0af25f37 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt @@ -221,7 +221,7 @@ sealed interface MockCall { fun print(owner: ActionSequence, builder: StringBuilder, visited: MutableSet) } -data class MockNewInstance(val klass: Class) : MockCall { +data class MockNewInstance(val klass: Class, val extraInterfaces: Set) : MockCall { override val parameters: List get() = listOf() @@ -727,7 +727,7 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { } fun map(api: MockCall): MockCall = when (api) { - is MockNewInstance -> MockNewInstance(api.klass.mapped) + is MockNewInstance -> MockNewInstance(api.klass.mapped, api.extraInterfaces.map{it.mapped}.toSet()) is MockSetupMethod -> MockSetupMethod(api.method.mapped, api.returnValues.map { value -> map(value) }) } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt index e554c9ca2..fba798baf 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt @@ -16,25 +16,35 @@ class MockGenerator(private val fallback: Generator) : Generator { override fun supports(descriptor: Descriptor): Boolean = descriptor is MockDescriptor - override fun generate(descriptor: Descriptor, generationDepth: Int): ActionSequence = with(context) { - descriptor as? MockDescriptor ?: throw IllegalArgumentException("Expected MockDescriptor. Got: $descriptor") - descriptor as MockDescriptor // helps autocompletion - - val name = "${descriptor.term}" - val actionSequence = MockSequence(name) - saveToCache(descriptor, actionSequence) - val kfgClass = (descriptor.type.getKfgType(types) as ClassType).klass - actionSequence.mockCalls.add(MockNewInstance(kfgClass)) - - for ((method, returnValuesDesc) in descriptor.methodReturns) { - val returnValues = returnValuesDesc.map { value -> fallback.generate(value) } - actionSequence.mockCalls += MockSetupMethod(method, returnValues) - } + override fun generate(descriptor: Descriptor, generationDepth: Int): ActionSequence = + with(context) { + descriptor as? MockDescriptor + ?: throw IllegalArgumentException("Expected MockDescriptor. Got: $descriptor") + descriptor as MockDescriptor // helps autocompletion - actionSequence.reflectionCalls.addSetupFieldsCalls(descriptor.fields, kfgClass, types, fallback) + val name = "${descriptor.term}" + val actionSequence = MockSequence(name) + saveToCache(descriptor, actionSequence) + val kfgClass = (descriptor.type.getKfgType(types) as ClassType).klass + val extraInterfaces = descriptor.extraInterfaces + .map { (it.getKfgType(types) as ClassType).klass } + .toSet() + actionSequence.mockCalls.add(MockNewInstance(kfgClass, extraInterfaces)) - getFromCache(descriptor)!! - } + for ((method, returnValuesDesc) in descriptor.methodReturns) { + val returnValues = returnValuesDesc.map { value -> fallback.generate(value) } + actionSequence.mockCalls += MockSetupMethod(method, returnValues) + } + + actionSequence.reflectionCalls.addSetupFieldsCalls( + descriptor.fields, + kfgClass, + types, + fallback + ) + + getFromCache(descriptor)!! + } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index c581d99cc..0b27d289d 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -513,6 +513,9 @@ class ExecutorAS2JavaPrinter( builder.import("org.mockito.Mockito") val actualType = ASClass(ctx.types.objectType) val kfgClass = call.klass + val extraInterfaces = if (call.extraInterfaces.isEmpty()) "" else call.extraInterfaces.joinToString(prefix=", Mockito.withSettings().extraInterfaces(", postfix=")", separator = ", "){ + "Class.forName(\"${it.canonicalDesc}\")" + } return listOf( if (resolvedTypes[owner] != null) { val rest = resolvedTypes[owner]!! @@ -523,7 +526,7 @@ class ExecutorAS2JavaPrinter( owner.name, type ) - } = Mockito.mock(Class.forName(\"${kfgClass.canonicalDesc}\"))" + } = Mockito.mock(Class.forName(\"${kfgClass.canonicalDesc}\")$extraInterfaces)" } else { actualTypes[owner] = actualType "${ @@ -531,7 +534,7 @@ class ExecutorAS2JavaPrinter( owner.name, actualType ) - } = Mockito.mock(Class.forName(\"${kfgClass.canonicalDesc}\"))" + } = Mockito.mock(Class.forName(\"${kfgClass.canonicalDesc}\")$extraInterfaces)" } ) } diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index f92ebcc70..3a27c6c64 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -6,7 +6,6 @@ import kotlinx.serialization.InternalSerializationApi import org.junit.Test import org.vorpal.research.kex.config.RuntimeConfig import org.vorpal.research.kex.config.kexConfig -import org.vorpal.research.kex.util.mockingMode import kotlin.time.ExperimentalTime @ExperimentalTime @@ -66,14 +65,16 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockInheritanceTests() { - val oldMockingMode = kexConfig.mockingMode - RuntimeConfig.setValue("mock", "mode", "full"); + // todo: enable mocking rule "any" +// val oldMockingMode = kexConfig.mockingMode +// RuntimeConfig.setValue("mock", "mode", "full"); assertCoverage(cm[prefix + "MockInheritanceTests"], 1.0) - RuntimeConfig.setValue("mock", "mode", oldMockingMode.toString()); +// RuntimeConfig.setValue("mock", "mode", oldMockingMode.toString()); } @Test fun mockLambdaTests() { + // todo: enable mocking rule "lambda" assertCoverage(cm[prefix + "MockLambdaTests"], 1.0) } } diff --git a/kex-test.ini b/kex-test.ini index 499cb3955..d4972193c 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -29,21 +29,14 @@ ignoreInstantiation = package org.mockito.* [mock] enabled = true mockitoVersion = 4.11.0 -mode = basic -; basic to mock only when can't create otherwise -; full to mock as much as possible -; mocking rules -rule = unimplemented -;rule = lambda -rule = any +rule = lambda +;rule = any +;rule = unimplemented ; true -- mocks are usually needed (hot-path) expectMocks = true -; additional passes over descriptors -concreteLambdasPassEnabled = true - ;easy-random tweaks easyRandomExcludeLambdas = false diff --git a/kex.ini b/kex.ini index ca1bcc207..3622067dd 100644 --- a/kex.ini +++ b/kex.ini @@ -44,14 +44,10 @@ maxArrayLength = 1000 [mock] enabled = true mockitoVersion = 4.11.0 -mode = basic -; basic to mock only when can't create otherwise -; full to mock as much as possible -; mocking rules -rule = unimplemented -;rule = lambda -rule = any +rule = lambda +;rule = any +;rule = unimplemented ; normally should be missing or false test = false @@ -59,9 +55,6 @@ test = false ; true -- mocks are usually needed (hot-path) expectMocks = true -; additional passes over descriptors -concreteLambdasPassEnabled = true - ;easy-random easyRandomExcludeLambdas = false From b3804d522cea682cbad16c4c5a7df708fa3b8add Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 26 Feb 2024 02:38:53 +0100 Subject: [PATCH 100/128] MockDescriptor constructor/builder refactoring --- .../org/vorpal/research/kex/descriptor/descriptor.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 442594b55..d7217cb4b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -731,10 +731,10 @@ class MockDescriptor(term: Term, type: KexClass, additionalInterfaces: Set + original: ObjectDescriptor, + type: KexClass = original.type as KexClass, + additionalInterfaces: Set = emptySet() ) : this(original.term, type, additionalInterfaces) val methodReturns: MutableMap> = mutableMapOf() @@ -919,9 +919,7 @@ open class DescriptorBuilder : StringInfoContext() { ArrayDescriptor(elementType, length) fun mock(type: KexClass) = MockDescriptor(type) - fun mock(original: ObjectDescriptor) = MockDescriptor(original) - fun mock(original: ObjectDescriptor, type: KexClass) = MockDescriptor(original, type) - fun mock(original: ObjectDescriptor, type: KexClass, additionalInterfaces: Set) = + fun mock(original: ObjectDescriptor, type: KexClass = original.type as KexClass, additionalInterfaces: Set = emptySet()) = MockDescriptor(original, type, additionalInterfaces) fun default(type: KexType, nullable: Boolean): Descriptor = descriptor { From 73f74d2d3e7aeceec4166ab2b9b8448d405ebff5 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 26 Feb 2024 03:04:35 +0100 Subject: [PATCH 101/128] log.warn -> log.error --- .../kotlin/org/vorpal/research/kex/descriptor/descriptor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index d7217cb4b..f132d867b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -17,8 +17,8 @@ import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.assert.unreachable +import org.vorpal.research.kthelper.logging.error import org.vorpal.research.kthelper.logging.log -import org.vorpal.research.kthelper.logging.warn import org.vorpal.research.kthelper.tryOrNull import ru.spbstu.wheels.joinToString import kotlin.random.Random @@ -716,7 +716,7 @@ fun Method.general(): Method { 1 -> possibleGeneralizations.first() else -> { - log.warn { + log.error { "Got multiple general version of method $this. Versions:\n ${ possibleGeneralizations.joinToString(separator = "\n") }\nPlease check your code." From 67deb27b7d9d263771c9a8770deded1c4607afc7 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Mon, 4 Mar 2024 00:07:31 +0100 Subject: [PATCH 102/128] DescriptorBuilder improvement --- .../kotlin/org/vorpal/research/kex/descriptor/descriptor.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index f132d867b..c9152652a 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -967,12 +967,9 @@ open class DescriptorBuilder : StringInfoContext() { private object DescriptorBuilderImpl : DescriptorBuilder() -fun descriptor(body: DescriptorBuilder.() -> Descriptor): Descriptor = +fun descriptor(body: DescriptorBuilder.() -> T): T = DescriptorBuilderImpl.body() -//fun descriptor(body: DescriptorBuilder.() -> T): T = -// DescriptorBuilderImpl.body() - class DescriptorRtMapper(private val mode: KexRtManager.Mode) : DescriptorBuilder() { private val cache = mutableMapOf() From 075260b608de72db83d4b17168b63a56cfbea059 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 5 Mar 2024 11:58:47 +0100 Subject: [PATCH 103/128] kfg 0.4.15 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 22ead42e2..0a5a930e4 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 3.5.1 0.1.14 - 0.4.14 + 0.4.15 1.2.11 1.7.36 From 0c423d3488dfb315ba095bf8621a518d09905b5d Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 5 Mar 2024 12:48:22 +0100 Subject: [PATCH 104/128] mockito id (core/inline) as property --- kex-runner/pom.xml | 2 +- kex-test/pom.xml | 4 +++- pom.xml | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/kex-runner/pom.xml b/kex-runner/pom.xml index ff2b65eb5..f296d18fb 100644 --- a/kex-runner/pom.xml +++ b/kex-runner/pom.xml @@ -112,7 +112,7 @@ org.mockito - mockito-core + mockito-${mockito.id} ${mockito.version} diff --git a/kex-test/pom.xml b/kex-test/pom.xml index 4b4b5eb57..ce4001776 100644 --- a/kex-test/pom.xml +++ b/kex-test/pom.xml @@ -18,7 +18,9 @@ 1.6.21 3.5.1 0.1.0 + 4.11.0 + core @@ -40,7 +42,7 @@ org.mockito - mockito-core + mockito-${mockito.id} ${mockito.version} diff --git a/pom.xml b/pom.xml index 0a5a930e4..2e203cc16 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,7 @@ 1.5.0 4.11.0 + core -XX:+IgnoreUnrecognizedVMOptions From 78f93cd8b10fe97c39a58fd83cd65d91892433d0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 5 Mar 2024 14:01:34 +0100 Subject: [PATCH 105/128] Removed mockito from kex-test deps --- .../vorpal/research/kex/compile/CompilerHelper.kt | 9 ++++++++- kex-test/pom.xml | 13 ------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/compile/CompilerHelper.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/compile/CompilerHelper.kt index 739262140..3dc04a38e 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/compile/CompilerHelper.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/compile/CompilerHelper.kt @@ -4,11 +4,13 @@ import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.util.compiledCodeDirectory import org.vorpal.research.kex.util.getJunit +import org.vorpal.research.kex.util.mockito import org.vorpal.research.kex.util.testcaseDirectory import java.nio.file.Path class CompilerHelper(val ctx: ExecutionContext) { private val junitJar = getJunit()!! + private val mockitoJar = kexConfig.mockito private val enabled: Boolean = kexConfig.getBooleanValue("compile", "enabled", true) private val compileDir: Path = kexConfig.compiledCodeDirectory.also { it.toFile().mkdirs() @@ -19,7 +21,12 @@ class CompilerHelper(val ctx: ExecutionContext) { if (!enabled) return val compilerDriver = JavaCompilerDriver( - listOf(*ctx.classPath.toTypedArray(), junitJar.path, testDirectory), compileDir + listOfNotNull( + *ctx.classPath.toTypedArray(), + mockitoJar?.path, + junitJar.path, + testDirectory + ), compileDir ) compilerDriver.compile(listOf(file)) } diff --git a/kex-test/pom.xml b/kex-test/pom.xml index ce4001776..262c1ef2e 100644 --- a/kex-test/pom.xml +++ b/kex-test/pom.xml @@ -18,9 +18,6 @@ 1.6.21 3.5.1 0.1.0 - - 4.11.0 - core @@ -40,16 +37,6 @@ kex-intrinsics ${kex-intrinsics.version} - - org.mockito - mockito-${mockito.id} - ${mockito.version} - - - - - - From f2050fbef2ce742c9e44fa6d8d4618387d8bc191 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 5 Mar 2024 14:06:12 +0100 Subject: [PATCH 106/128] Removed mockito from kex-executor deps --- kex-executor/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/kex-executor/pom.xml b/kex-executor/pom.xml index 70e1cc51e..5df7d2ea8 100644 --- a/kex-executor/pom.xml +++ b/kex-executor/pom.xml @@ -52,11 +52,6 @@ kotlinx-coroutines-core ${coroutines.version} - - org.mockito - mockito-core - ${mockito.version} - From 05eabe91d9c9320dcfc8f7f0bd9c67ca6026a594 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 12 Mar 2024 11:42:41 +0100 Subject: [PATCH 107/128] Lambda mocking improved --- .../vorpal/research/kex/mocking/MockMaker.kt | 31 +++++++++---------- .../kex/random/easyrandom/easyRandom.kt | 10 +++--- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index 3cabb238f..82f652f60 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -6,14 +6,11 @@ import org.vorpal.research.kex.descriptor.Descriptor import org.vorpal.research.kex.descriptor.MockDescriptor import org.vorpal.research.kex.descriptor.ObjectDescriptor import org.vorpal.research.kex.descriptor.descriptor -import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager.isKexRt import org.vorpal.research.kex.ktype.kexType -import org.vorpal.research.kex.util.loadClass import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory -import org.vorpal.research.kfg.type.objectType sealed class MockMaker(protected val ctx: ExecutionContext) { protected val types: TypeFactory = ctx.types @@ -56,13 +53,15 @@ private class UnimplementedMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { } } +private const val FUNCTIONAL_INTERFACE_CLASS_NAME = "java/lang/FunctionalInterface" +private val Class.isLambda: Boolean + get() = isInterface && annotations.any { it.type.name == FUNCTIONAL_INTERFACE_CLASS_NAME } + private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { private fun Class.getFunctionalInterfaces( interfaces: MutableSet = mutableSetOf() ): Set { - if (this.isInterface && - ctx.loader.loadClass(this).getAnnotation(FunctionalInterface::class.java) != null - ) { + if (isLambda) { interfaces.add(this) } allAncestors.forEach { it.getFunctionalInterfaces(interfaces) } @@ -78,18 +77,16 @@ private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { if (!canMock(descriptor)) return null descriptor as ObjectDescriptor val klass = getKlass(descriptor) ?: return null + if (klass.isLambda) { + return descriptor { mock(klass.kexType) } + } + val mockKlass = (if (klass.isFinal) klass.superClass else klass) ?: return null val functionalInterfaces = klass.getFunctionalInterfaces() - return when (functionalInterfaces.size) { - 0 -> null - 1 -> descriptor { mock(descriptor, functionalInterfaces.first().kexType) } - else -> descriptor { - mock( - descriptor, - ctx.types.objectType.kexType as KexClass, - functionalInterfaces.map { it.kexType }.toSet() - ) - } - } as? MockDescriptor + + if (functionalInterfaces.isEmpty()) return null + return descriptor { + mock(descriptor, mockKlass.kexType, functionalInterfaces.map { it.kexType }.toSet()) + } } } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt index 3b61e1aa8..1909d84ec 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt @@ -75,11 +75,6 @@ class EasyRandomDriver( private val Class<*>.shouldBeExcluded: Boolean get() { - if (kexConfig.isMockingEnabled && kexConfig.isEasyRandomExcludeLambdas) { - if (isInterface && this.annotations.contains(FunctionalInterface::class.java)) { - return true - } - } return config.excludes.any { it.matches(name.asmString) } } @@ -287,6 +282,11 @@ class EasyRandomDriver( log.warn("Reached maximum depth of generation $depth") return null } + if (kexConfig.isMockingEnabled && kexConfig.isEasyRandomExcludeLambdas && type is Class<*>) { + if (type.isInterface && type.getAnnotation(FunctionalInterface::class.java) != null) { + return null + } + } repeat(config.attempts) { tryOrNull { return generateType(type, depth) From 0da43277e81f882d83e8a68246dd1c2a2dc91da0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 13 Mar 2024 15:09:59 +0100 Subject: [PATCH 108/128] Updated tests and config. Fixed(?) ClassInstantiationManager --- .../kex/asm/manager/ClassInstantiationManager.kt | 3 ++- kex-test.ini | 2 +- .../test/concolic/mock/MockInheritanceTests.java | 4 ++-- .../kex/test/concolic/mock/MockLambdaTests.java | 16 ++++++++++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt index 3a7023f5b..996b0c796 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/asm/manager/ClassInstantiationManager.kt @@ -308,7 +308,8 @@ class ClassInstantiationDetector( override fun visit(klass: Class) { if (ignoredInstantiations.any { it.matches(klass) }) { - return // todo maybe super.visit(klass) ??? + super.visit(klass) + return } if (StringClassInstantiationManagerImpl.isDirectlyInstantiable(klass, baseAccessLevel)) diff --git a/kex-test.ini b/kex-test.ini index d4972193c..fce356fe7 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -38,7 +38,7 @@ rule = lambda expectMocks = true ;easy-random tweaks -easyRandomExcludeLambdas = false +easyRandomExcludeLambdas = true ; workarounds mockitoClassesWorkaround = true diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockInheritanceTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockInheritanceTests.java index 2794533d3..772618f92 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockInheritanceTests.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockInheritanceTests.java @@ -6,14 +6,14 @@ @SuppressWarnings("ALL") public class MockInheritanceTests { - static class Base { + static abstract class Base { int foo(boolean b) { return 248; } } - static class Derived extends Base { + static abstract class Derived extends Base { @Override int foo(boolean b) { diff --git a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java index d71f9a42c..008decf46 100644 --- a/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java +++ b/kex-test/src/main/kotlin/org/vorpal/research/kex/test/concolic/mock/MockLambdaTests.java @@ -25,6 +25,7 @@ public void testBoxedIntSupplier(Supplier a) { } } + public void testFunction(Function a) { if (a.apply(null) == 909L) { AssertIntrinsics.kexAssert(true); @@ -32,5 +33,20 @@ public void testFunction(Function a) { AssertIntrinsics.kexAssert(true); } } + + @FunctionalInterface + interface CustomLambda { + + Object whatever(); + } + + public void testCustomLambda(CustomLambda a) { + Object b = a.whatever(); + if (b instanceof CustomLambda) { + if (b != a) { + AssertIntrinsics.kexAssert(true); + } + } + } } From 9cacfabf58b8bfac4e56ad8d814f6119a3d15d91 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 19 Mar 2024 15:44:06 +0100 Subject: [PATCH 109/128] Refactoring --- .../{MockMaker.kt => AbstractMockMaker.kt} | 20 +++++++++---------- .../vorpal/research/kex/mocking/mockUtils.kt | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) rename kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/{MockMaker.kt => AbstractMockMaker.kt} (86%) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt similarity index 86% rename from kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt rename to kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt index 82f652f60..aea3d9429 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt @@ -12,22 +12,22 @@ import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory -sealed class MockMaker(protected val ctx: ExecutionContext) { - protected val types: TypeFactory = ctx.types - - abstract fun canMock(descriptor: Descriptor): Boolean - abstract fun mockOrNull(descriptor: Descriptor): MockDescriptor? +interface MockMaker { + fun canMock(descriptor: Descriptor): Boolean + fun mockOrNull(descriptor: Descriptor): MockDescriptor? +} +sealed class AbstractMockMaker(protected val ctx: ExecutionContext) : MockMaker { + protected val types: TypeFactory = ctx.types protected fun satisfiesNecessaryConditions(descriptor: Descriptor): Boolean { val klass = getKlass(descriptor) ?: return false return !klass.isFinal && !descriptor.type.isKexRt && descriptor is ObjectDescriptor } - protected fun getKlass(descriptor: Descriptor): Class? = (descriptor.type.getKfgType(types) as? ClassType)?.klass } -private class AllMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { +private class AllMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { override fun canMock(descriptor: Descriptor): Boolean { return satisfiesNecessaryConditions(descriptor) } @@ -39,7 +39,7 @@ private class AllMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { } } -private class UnimplementedMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { +private class UnimplementedMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { override fun canMock(descriptor: Descriptor): Boolean { val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass ?: return false return satisfiesNecessaryConditions(descriptor) && @@ -57,7 +57,7 @@ private const val FUNCTIONAL_INTERFACE_CLASS_NAME = "java/lang/FunctionalInterfa private val Class.isLambda: Boolean get() = isInterface && annotations.any { it.type.name == FUNCTIONAL_INTERFACE_CLASS_NAME } -private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { +private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { private fun Class.getFunctionalInterfaces( interfaces: MutableSet = mutableSetOf() ): Set { @@ -90,7 +90,7 @@ private class LambdaMockMaker(ctx: ExecutionContext) : MockMaker(ctx) { } } -fun createMocker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { +fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { MockingRule.LAMBDA -> LambdaMockMaker(ctx) MockingRule.ANY -> AllMockMaker(ctx) MockingRule.UNIMPLEMENTED -> UnimplementedMockMaker(ctx) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt index f403b542a..4c646c4b6 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -8,8 +8,8 @@ enum class MockingRule { LAMBDA, ANY, UNIMPLEMENTED } -fun Config.getMockMakers(ctx: ExecutionContext): List = +fun Config.getMockMakers(ctx: ExecutionContext): List = getMultipleStringValue("mock", "rule") .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } .sortedBy { rule -> rule.ordinal } - .map { rule -> createMocker(rule, ctx) } \ No newline at end of file + .map { rule -> createMockMaker(rule, ctx) } \ No newline at end of file From 249a4ade181f21aaec1fa396dcd255e956224083 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 19 Mar 2024 15:44:41 +0100 Subject: [PATCH 110/128] Fix --- .../main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt index 4c646c4b6..d315e887e 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -8,7 +8,7 @@ enum class MockingRule { LAMBDA, ANY, UNIMPLEMENTED } -fun Config.getMockMakers(ctx: ExecutionContext): List = +fun Config.getMockMakers(ctx: ExecutionContext): List = getMultipleStringValue("mock", "rule") .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } .sortedBy { rule -> rule.ordinal } From 289ae99bd11cf280a892be94aa2cca9d814cbdb5 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 19 Mar 2024 16:12:07 +0100 Subject: [PATCH 111/128] Renaming. Prepairing for BreakingMockMaker --- .../research/kex/mocking/AbstractMockMaker.kt | 60 +++++++++++++------ .../vorpal/research/kex/mocking/mockUtils.kt | 2 +- .../research/kex/parameters/Parameters.kt | 6 +- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt index aea3d9429..e84039f40 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt @@ -12,17 +12,25 @@ import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory -interface MockMaker { +// mock is subtype of original +interface SafeMockMaker { fun canMock(descriptor: Descriptor): Boolean - fun mockOrNull(descriptor: Descriptor): MockDescriptor? + fun mockOrNull(original: Descriptor): MockDescriptor? } -sealed class AbstractMockMaker(protected val ctx: ExecutionContext) : MockMaker { +// mock is NOT subtype of original +interface BreakingMockMaker { + fun canMockBreaking(expectedClass: Class, descriptor: Descriptor): Boolean + fun mockBreakingOrNull(expectedClass: Class, original: Descriptor): MockDescriptor? +} + +sealed class AbstractMockMaker(protected val ctx: ExecutionContext) : SafeMockMaker { protected val types: TypeFactory = ctx.types protected fun satisfiesNecessaryConditions(descriptor: Descriptor): Boolean { val klass = getKlass(descriptor) ?: return false return !klass.isFinal && !descriptor.type.isKexRt && descriptor is ObjectDescriptor } + protected fun getKlass(descriptor: Descriptor): Class? = (descriptor.type.getKfgType(types) as? ClassType)?.klass } @@ -32,10 +40,10 @@ private class AllMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { return satisfiesNecessaryConditions(descriptor) } - override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { - if (!canMock(descriptor)) return null - descriptor as ObjectDescriptor - return MockDescriptor(descriptor).also { it.fields.putAll(descriptor.fields) } + override fun mockOrNull(original: Descriptor): MockDescriptor? { + if (!canMock(original)) return null + original as ObjectDescriptor + return MockDescriptor(original).also { it.fields.putAll(original.fields) } } } @@ -46,10 +54,10 @@ private class UnimplementedMockMaker(ctx: ExecutionContext) : AbstractMockMaker( !instantiationManager.isInstantiable(klass) } - override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { - if (!canMock(descriptor)) return null - descriptor as ObjectDescriptor - return MockDescriptor(descriptor).also { it.fields.putAll(descriptor.fields) } + override fun mockOrNull(original: Descriptor): MockDescriptor? { + if (!canMock(original)) return null + original as ObjectDescriptor + return MockDescriptor(original).also { it.fields.putAll(original.fields) } } } @@ -57,7 +65,7 @@ private const val FUNCTIONAL_INTERFACE_CLASS_NAME = "java/lang/FunctionalInterfa private val Class.isLambda: Boolean get() = isInterface && annotations.any { it.type.name == FUNCTIONAL_INTERFACE_CLASS_NAME } -private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { +private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx), BreakingMockMaker { private fun Class.getFunctionalInterfaces( interfaces: MutableSet = mutableSetOf() ): Set { @@ -70,13 +78,13 @@ private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { override fun canMock(descriptor: Descriptor): Boolean { val functionalInterfaces = getKlass(descriptor)?.getFunctionalInterfaces() ?: emptySet() - return !descriptor.type.isKexRt && descriptor is ObjectDescriptor && functionalInterfaces.isNotEmpty() + return getKlass(descriptor)?.isFinal == false && !descriptor.type.isKexRt && descriptor is ObjectDescriptor && functionalInterfaces.isNotEmpty() } - override fun mockOrNull(descriptor: Descriptor): MockDescriptor? { - if (!canMock(descriptor)) return null - descriptor as ObjectDescriptor - val klass = getKlass(descriptor) ?: return null + override fun mockOrNull(original: Descriptor): MockDescriptor? { + if (!canMock(original)) return null + original as ObjectDescriptor + val klass = getKlass(original) ?: return null if (klass.isLambda) { return descriptor { mock(klass.kexType) } } @@ -85,12 +93,26 @@ private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { if (functionalInterfaces.isEmpty()) return null return descriptor { - mock(descriptor, mockKlass.kexType, functionalInterfaces.map { it.kexType }.toSet()) + mock(original, mockKlass.kexType, functionalInterfaces.map { it.kexType }.toSet()) + } + } + + override fun canMockBreaking(expectedClass: Class, descriptor: Descriptor): Boolean { + return canMock(descriptor) || expectedClass.isLambda + } + + override fun mockBreakingOrNull(expectedClass: Class, original: Descriptor): MockDescriptor? { + return when { + canMock(original) -> mockOrNull(original) + canMockBreaking(expectedClass, original) -> + descriptor { mock(original as ObjectDescriptor, expectedClass.kexType) } + + else -> null } } } -fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { +fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): SafeMockMaker = when (rule) { MockingRule.LAMBDA -> LambdaMockMaker(ctx) MockingRule.ANY -> AllMockMaker(ctx) MockingRule.UNIMPLEMENTED -> UnimplementedMockMaker(ctx) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt index d315e887e..e45abd142 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -8,7 +8,7 @@ enum class MockingRule { LAMBDA, ANY, UNIMPLEMENTED } -fun Config.getMockMakers(ctx: ExecutionContext): List = +fun Config.getMockMakers(ctx: ExecutionContext): List = getMultipleStringValue("mock", "rule") .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } .sortedBy { rule -> rule.ordinal } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 9076f66d0..5290f46c5 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -6,7 +6,7 @@ import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager.rtMapped -import org.vorpal.research.kex.mocking.MockMaker +import org.vorpal.research.kex.mocking.SafeMockMaker import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term @@ -101,7 +101,7 @@ fun Parameters.filterIgnoredStatic(): Parameters { fun createDescriptorToMock( allDescriptors: Collection, - mockMakers: List + mockMakers: List ): Map { val descriptorToMock = mutableMapOf() allDescriptors.forEach { @@ -114,7 +114,7 @@ fun createDescriptorToMock( fun Descriptor.requireMocks( - mockMakers: List, visited: MutableSet = mutableSetOf() + mockMakers: List, visited: MutableSet = mutableSetOf() ): Boolean = any(visited) { descriptor -> mockMakers.any { mockMaker -> mockMaker.canMock(descriptor) } } From e76249605a91c1ae9d3e2556cafdabebc1b76679 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 19 Mar 2024 19:33:51 +0100 Subject: [PATCH 112/128] Refactoring, preparing to better Lambda mocking --- .../research/kex/descriptor/descriptor.kt | 18 +-- .../research/kex/mocking/AbstractMockMaker.kt | 119 ------------------ .../vorpal/research/kex/mocking/MockMaker.kt | 118 +++++++++++++++++ .../vorpal/research/kex/mocking/mockUtils.kt | 11 +- .../research/kex/parameters/Parameters.kt | 14 +-- .../kex/asm/analysis/util/extensions.kt | 20 +-- 6 files changed, 154 insertions(+), 146 deletions(-) delete mode 100644 kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt create mode 100644 kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index c9152652a..83f993b0f 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -715,30 +715,29 @@ fun Method.general(): Method { 0 -> this 1 -> possibleGeneralizations.first() - else -> { + else -> possibleGeneralizations.first().also { log.error { "Got multiple general version of method $this. Versions:\n ${ possibleGeneralizations.joinToString(separator = "\n") }\nPlease check your code." } - possibleGeneralizations.first() } } } -class MockDescriptor(term: Term, type: KexClass, additionalInterfaces: Set = setOf()) : +class MockDescriptor(term: Term, type: KexClass, extraInterfaces: Set = setOf()) : AbstractFieldContainingDescriptor(term, type) { constructor(type: KexClass) : this(term { generate(type) }, type) constructor( original: ObjectDescriptor, type: KexClass = original.type as KexClass, - additionalInterfaces: Set = emptySet() - ) : this(original.term, type, additionalInterfaces) + extraInterfaces: Set = emptySet() + ) : this(original.term, type, extraInterfaces) val methodReturns: MutableMap> = mutableMapOf() - val extraInterfaces: MutableSet = additionalInterfaces.toMutableSet() + val extraInterfaces: MutableSet = extraInterfaces.toMutableSet() val allReturns: Sequence get() = methodReturns.values.asSequence().flatMap { it.asSequence() } @@ -919,8 +918,11 @@ open class DescriptorBuilder : StringInfoContext() { ArrayDescriptor(elementType, length) fun mock(type: KexClass) = MockDescriptor(type) - fun mock(original: ObjectDescriptor, type: KexClass = original.type as KexClass, additionalInterfaces: Set = emptySet()) = - MockDescriptor(original, type, additionalInterfaces) + fun mock( + original: ObjectDescriptor, + type: KexClass = original.type as KexClass, + extraInterfaces: Set = emptySet() + ) = MockDescriptor(original, type, extraInterfaces) fun default(type: KexType, nullable: Boolean): Descriptor = descriptor { when (type) { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt deleted file mode 100644 index e84039f40..000000000 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/AbstractMockMaker.kt +++ /dev/null @@ -1,119 +0,0 @@ -package org.vorpal.research.kex.mocking - -import org.vorpal.research.kex.ExecutionContext -import org.vorpal.research.kex.asm.manager.instantiationManager -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.MockDescriptor -import org.vorpal.research.kex.descriptor.ObjectDescriptor -import org.vorpal.research.kex.descriptor.descriptor -import org.vorpal.research.kex.ktype.KexRtManager.isKexRt -import org.vorpal.research.kex.ktype.kexType -import org.vorpal.research.kfg.ir.Class -import org.vorpal.research.kfg.type.ClassType -import org.vorpal.research.kfg.type.TypeFactory - -// mock is subtype of original -interface SafeMockMaker { - fun canMock(descriptor: Descriptor): Boolean - fun mockOrNull(original: Descriptor): MockDescriptor? -} - -// mock is NOT subtype of original -interface BreakingMockMaker { - fun canMockBreaking(expectedClass: Class, descriptor: Descriptor): Boolean - fun mockBreakingOrNull(expectedClass: Class, original: Descriptor): MockDescriptor? -} - -sealed class AbstractMockMaker(protected val ctx: ExecutionContext) : SafeMockMaker { - protected val types: TypeFactory = ctx.types - protected fun satisfiesNecessaryConditions(descriptor: Descriptor): Boolean { - val klass = getKlass(descriptor) ?: return false - return !klass.isFinal && !descriptor.type.isKexRt && descriptor is ObjectDescriptor - } - - protected fun getKlass(descriptor: Descriptor): Class? = - (descriptor.type.getKfgType(types) as? ClassType)?.klass -} - -private class AllMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { - override fun canMock(descriptor: Descriptor): Boolean { - return satisfiesNecessaryConditions(descriptor) - } - - override fun mockOrNull(original: Descriptor): MockDescriptor? { - if (!canMock(original)) return null - original as ObjectDescriptor - return MockDescriptor(original).also { it.fields.putAll(original.fields) } - } -} - -private class UnimplementedMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { - override fun canMock(descriptor: Descriptor): Boolean { - val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass ?: return false - return satisfiesNecessaryConditions(descriptor) && - !instantiationManager.isInstantiable(klass) - } - - override fun mockOrNull(original: Descriptor): MockDescriptor? { - if (!canMock(original)) return null - original as ObjectDescriptor - return MockDescriptor(original).also { it.fields.putAll(original.fields) } - } -} - -private const val FUNCTIONAL_INTERFACE_CLASS_NAME = "java/lang/FunctionalInterface" -private val Class.isLambda: Boolean - get() = isInterface && annotations.any { it.type.name == FUNCTIONAL_INTERFACE_CLASS_NAME } - -private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx), BreakingMockMaker { - private fun Class.getFunctionalInterfaces( - interfaces: MutableSet = mutableSetOf() - ): Set { - if (isLambda) { - interfaces.add(this) - } - allAncestors.forEach { it.getFunctionalInterfaces(interfaces) } - return interfaces - } - - override fun canMock(descriptor: Descriptor): Boolean { - val functionalInterfaces = getKlass(descriptor)?.getFunctionalInterfaces() ?: emptySet() - return getKlass(descriptor)?.isFinal == false && !descriptor.type.isKexRt && descriptor is ObjectDescriptor && functionalInterfaces.isNotEmpty() - } - - override fun mockOrNull(original: Descriptor): MockDescriptor? { - if (!canMock(original)) return null - original as ObjectDescriptor - val klass = getKlass(original) ?: return null - if (klass.isLambda) { - return descriptor { mock(klass.kexType) } - } - val mockKlass = (if (klass.isFinal) klass.superClass else klass) ?: return null - val functionalInterfaces = klass.getFunctionalInterfaces() - - if (functionalInterfaces.isEmpty()) return null - return descriptor { - mock(original, mockKlass.kexType, functionalInterfaces.map { it.kexType }.toSet()) - } - } - - override fun canMockBreaking(expectedClass: Class, descriptor: Descriptor): Boolean { - return canMock(descriptor) || expectedClass.isLambda - } - - override fun mockBreakingOrNull(expectedClass: Class, original: Descriptor): MockDescriptor? { - return when { - canMock(original) -> mockOrNull(original) - canMockBreaking(expectedClass, original) -> - descriptor { mock(original as ObjectDescriptor, expectedClass.kexType) } - - else -> null - } - } -} - -fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): SafeMockMaker = when (rule) { - MockingRule.LAMBDA -> LambdaMockMaker(ctx) - MockingRule.ANY -> AllMockMaker(ctx) - MockingRule.UNIMPLEMENTED -> UnimplementedMockMaker(ctx) -} diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt new file mode 100644 index 000000000..4d618936c --- /dev/null +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -0,0 +1,118 @@ +package org.vorpal.research.kex.mocking + +import org.vorpal.research.kex.ExecutionContext +import org.vorpal.research.kex.asm.manager.instantiationManager +import org.vorpal.research.kex.descriptor.Descriptor +import org.vorpal.research.kex.descriptor.MockDescriptor +import org.vorpal.research.kex.descriptor.ObjectDescriptor +import org.vorpal.research.kex.descriptor.descriptor +import org.vorpal.research.kex.ktype.KexRtManager.isKexRt +import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kfg.ir.Class +import org.vorpal.research.kfg.type.ClassType +import org.vorpal.research.kfg.type.TypeFactory + +// mock is subtype of original +interface MockMaker { + fun canMock(descriptor: Descriptor, expectedClass: Class? = null): Boolean + fun mockOrNull(original: Descriptor, expectedClass: Class? = null): MockDescriptor? +} + +private sealed class AbstractMockMaker(protected val ctx: ExecutionContext) : MockMaker { + protected val types: TypeFactory get() = ctx.types + protected fun satisfiesNecessaryConditions(descriptor: Descriptor): Boolean { + val klass = descriptor.kfgClass ?: return false + return !klass.isFinal && !descriptor.type.isKexRt && descriptor is ObjectDescriptor + } + + protected val Descriptor.kfgClass: Class? + get() = (type.getKfgType(types) as? ClassType)?.klass +} + +private class AllMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { + override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { + return satisfiesNecessaryConditions(descriptor) + } + + override fun mockOrNull(original: Descriptor, expectedClass: Class?): MockDescriptor? { + if (!canMock(original)) return null + original as ObjectDescriptor + return MockDescriptor(original).also { it.fields.putAll(original.fields) } + } +} + +private class UnimplementedMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { + override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { + val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass ?: return false + return satisfiesNecessaryConditions(descriptor) && + !instantiationManager.isInstantiable(klass) + } + + override fun mockOrNull(original: Descriptor, expectedClass: Class?): MockDescriptor? { + if (!canMock(original)) return null + original as ObjectDescriptor + return MockDescriptor(original).also { it.fields.putAll(original.fields) } + } +} + +private const val FUNCTIONAL_INTERFACE_CLASS_NAME = "java/lang/FunctionalInterface" +private val Class.isLambda: Boolean + get() = isInterface && annotations.any { it.type.name == FUNCTIONAL_INTERFACE_CLASS_NAME } + +private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { + private fun Class.getFunctionalInterfaces( + interfaces: MutableSet = mutableSetOf() + ): Set { + if (isLambda) { + interfaces.add(this) + } + allAncestors.forEach { it.getFunctionalInterfaces(interfaces) } + return interfaces + } + + override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { + return expectedClass?.isLambda == true && descriptor is ObjectDescriptor + } + + override fun mockOrNull(original: Descriptor, expectedClass: Class?): MockDescriptor? { + if (!canMock(original, expectedClass)) return null + + return descriptor { mock(original as ObjectDescriptor, expectedClass!!.kexType) } + } +} + + +internal class CompositeMockMaker(private val mockMakers: List) : MockMaker { + override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { + return mockMakers.any { mockMaker -> mockMaker.canMock(descriptor, expectedClass) } + } + + override fun mockOrNull(original: Descriptor, expectedClass: Class?): MockDescriptor? { + return mockMakers.firstNotNullOfOrNull { mockMaker -> + mockMaker.mockOrNull(original, expectedClass) + } + } +} + +private class ExcludingMockMaker( + private val mockMaker: MockMaker, + private val exclude: (Descriptor) -> Boolean +) : MockMaker { + override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { + return !exclude(descriptor) && mockMaker.canMock(descriptor, expectedClass) + } + + override fun mockOrNull(original: Descriptor, expectedClass: Class?): MockDescriptor? { + if (exclude(original)) return null + return mockMaker.mockOrNull(original, expectedClass) + } +} + +fun MockMaker.excluding(excludePredicate: (Descriptor) -> Boolean): MockMaker = + ExcludingMockMaker(this, excludePredicate) + +fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { + MockingRule.LAMBDA -> LambdaMockMaker(ctx) + MockingRule.ANY -> AllMockMaker(ctx) + MockingRule.UNIMPLEMENTED -> UnimplementedMockMaker(ctx) +} diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt index e45abd142..7bfe8d236 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -2,14 +2,19 @@ package org.vorpal.research.kex.mocking import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.config.Config +import org.vorpal.research.kex.descriptor.Descriptor +import org.vorpal.research.kex.descriptor.MockDescriptor +import org.vorpal.research.kfg.ir.Class enum class MockingRule { // Order is important! First rule applies first - LAMBDA, ANY, UNIMPLEMENTED + ANY, UNIMPLEMENTED, LAMBDA } -fun Config.getMockMakers(ctx: ExecutionContext): List = + +fun Config.getMockMaker(ctx: ExecutionContext): MockMaker = getMultipleStringValue("mock", "rule") .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } .sortedBy { rule -> rule.ordinal } - .map { rule -> createMockMaker(rule, ctx) } \ No newline at end of file + .map { rule -> createMockMaker(rule, ctx) } + .let { mockMakers -> CompositeMockMaker(mockMakers) } \ No newline at end of file diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 5290f46c5..3caa2bc62 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -6,12 +6,13 @@ import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager.rtMapped -import org.vorpal.research.kex.mocking.SafeMockMaker +import org.vorpal.research.kex.mocking.MockMaker import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.util.KfgTargetFilter import org.vorpal.research.kfg.ClassManager +import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn import kotlin.random.Random @@ -101,22 +102,21 @@ fun Parameters.filterIgnoredStatic(): Parameters { fun createDescriptorToMock( allDescriptors: Collection, - mockMakers: List + mockMaker: MockMaker, + expectedType: Map ): Map { val descriptorToMock = mutableMapOf() allDescriptors.forEach { - it.transform(descriptorToMock) { descriptor -> - mockMakers.firstNotNullOfOrNull { mockMaker -> mockMaker.mockOrNull(descriptor) } - } + it.transform(descriptorToMock) { descriptor -> mockMaker.mockOrNull(descriptor) } } return descriptorToMock } fun Descriptor.requireMocks( - mockMakers: List, visited: MutableSet = mutableSetOf() + mockMaker: MockMaker, visited: MutableSet = mutableSetOf() ): Boolean = - any(visited) { descriptor -> mockMakers.any { mockMaker -> mockMaker.canMock(descriptor) } } + any(visited) { descriptor -> mockMaker.canMock(descriptor) } fun setupMocks( diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index d05e84d47..0b729e42c 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -11,7 +11,8 @@ import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType -import org.vorpal.research.kex.mocking.getMockMakers +import org.vorpal.research.kex.mocking.excluding +import org.vorpal.research.kex.mocking.getMockMaker import org.vorpal.research.kex.parameters.* import org.vorpal.research.kex.smt.AsyncChecker import org.vorpal.research.kex.smt.AsyncIncrementalChecker @@ -79,7 +80,7 @@ suspend fun Method.checkAsync( checker.state ) - val finalDescriptors = initialDescriptors.finalizeDescriptors(ctx, generator, state) + val finalDescriptors = initialDescriptors.finalizeDescriptors(ctx, generator, state, this) finalDescriptors .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { @@ -93,30 +94,31 @@ suspend fun Method.checkAsync( } } + private fun Parameters.finalizeDescriptors( ctx: ExecutionContext, generator: DescriptorGenerator, - state: SymbolicState + state: SymbolicState, + method: Method ): Parameters { if (!kexConfig.isMockingEnabled) { return this } - fun Collection.removeInstance() = this.filterNot { it == instance } - val mockMakers = kexConfig.getMockMakers(ctx) + val mockMaker = kexConfig.getMockMaker(ctx).excluding { desc -> desc == instance } if (!kexConfig.isExpectMocks) { val visited = mutableSetOf() - if (this.asList.removeInstance().none { it.requireMocks(mockMakers, visited) }) { + if (this.asList.none { it.requireMocks(mockMaker, visited) }) { return this } } generator.generateAll() val visited = mutableSetOf() - if (generator.allValues.removeInstance().none { it.requireMocks(mockMakers, visited) }) { + if (generator.allValues.none { it.requireMocks(mockMaker, visited) }) { return this } - val descriptorToMock = createDescriptorToMock(generator.allValues.removeInstance(), mockMakers) + val descriptorToMock = createDescriptorToMock(generator.allValues, mockMaker, emptyMap()) val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = state.methodCalls() setupMocks(methodCalls, generator.memory, descriptorToMock) @@ -204,7 +206,7 @@ suspend fun Method.checkAsyncIncremental( val fullPS = checker.state + checker.queries[index].hardConstraints generateInitialDescriptors(this, ctx, result.model, fullPS) .let { (descriptors, generator) -> - descriptors.finalizeDescriptors(ctx, generator, state) + descriptors.finalizeDescriptors(ctx, generator, state, this) } .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } From 662be30364331c69b7d6d3af6fcb068316bd72a9 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 19 Mar 2024 20:57:27 +0100 Subject: [PATCH 113/128] Should work --- .../vorpal/research/kex/descriptor/descriptor.kt | 2 +- .../org/vorpal/research/kex/mocking/mockUtils.kt | 3 --- .../vorpal/research/kex/parameters/Parameters.kt | 12 ++++++++---- .../research/kex/asm/analysis/util/extensions.kt | 14 +++++++++++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 83f993b0f..147d5e4b3 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -35,7 +35,7 @@ sealed class Descriptor(term: Term, type: KexType) { var klassDescriptor: ObjectDescriptor get() { if (innerKlassDescriptor == null) { - innerKlassDescriptor = descriptor { klass(type) } as ObjectDescriptor + innerKlassDescriptor = descriptor { klass(type) } } return innerKlassDescriptor!! } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt index 7bfe8d236..c13f69917 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -2,9 +2,6 @@ package org.vorpal.research.kex.mocking import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.config.Config -import org.vorpal.research.kex.descriptor.Descriptor -import org.vorpal.research.kex.descriptor.MockDescriptor -import org.vorpal.research.kfg.ir.Class enum class MockingRule { // Order is important! First rule applies first diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 3caa2bc62..2ce4b3bb3 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -103,20 +103,24 @@ fun Parameters.filterIgnoredStatic(): Parameters { fun createDescriptorToMock( allDescriptors: Collection, mockMaker: MockMaker, - expectedType: Map + expectedClasses: Map ): Map { val descriptorToMock = mutableMapOf() allDescriptors.forEach { - it.transform(descriptorToMock) { descriptor -> mockMaker.mockOrNull(descriptor) } + it.transform(descriptorToMock) { descriptor -> + mockMaker.mockOrNull(descriptor, expectedClasses[descriptor]) + } } return descriptorToMock } fun Descriptor.requireMocks( - mockMaker: MockMaker, visited: MutableSet = mutableSetOf() + mockMaker: MockMaker, + expectedClass: Map, + visited: MutableSet = mutableSetOf() ): Boolean = - any(visited) { descriptor -> mockMaker.canMock(descriptor) } + any(visited) { descriptor -> mockMaker.canMock(descriptor, expectedClass[descriptor]) } fun setupMocks( diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 0b729e42c..b9da92244 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -25,6 +25,7 @@ import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.ir.Method +import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn @@ -105,20 +106,27 @@ private fun Parameters.finalizeDescriptors( return this } + val expectedClasses = method.argTypes + .map { (it as? ClassType)?.klass } + .zip(arguments) + .filter { (klass, _) -> klass != null } + .associate { (klass, descriptor) -> descriptor to klass!! } + val mockMaker = kexConfig.getMockMaker(ctx).excluding { desc -> desc == instance } if (!kexConfig.isExpectMocks) { val visited = mutableSetOf() - if (this.asList.none { it.requireMocks(mockMaker, visited) }) { + if (this.asList.none { it.requireMocks(mockMaker, expectedClasses, visited) }) { return this } } generator.generateAll() val visited = mutableSetOf() - if (generator.allValues.none { it.requireMocks(mockMaker, visited) }) { + if (generator.allValues.none { it.requireMocks(mockMaker, expectedClasses, visited) }) { return this } - val descriptorToMock = createDescriptorToMock(generator.allValues, mockMaker, emptyMap()) + + val descriptorToMock = createDescriptorToMock(generator.allValues, mockMaker, expectedClasses) val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = state.methodCalls() setupMocks(methodCalls, generator.memory, descriptorToMock) From 9680c9b076825b2abb63d9421723e5336af09de4 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 19 Mar 2024 23:43:26 +0100 Subject: [PATCH 114/128] Finished better lambda mocking --- .../vorpal/research/kex/mocking/MockMaker.kt | 35 ++++++++++++------- .../vorpal/research/kex/mocking/mockUtils.kt | 2 +- .../kex/asm/analysis/util/extensions.kt | 4 +-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index 4d618936c..bac8ee055 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -12,7 +12,6 @@ import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kfg.type.TypeFactory -// mock is subtype of original interface MockMaker { fun canMock(descriptor: Descriptor, expectedClass: Class? = null): Boolean fun mockOrNull(original: Descriptor, expectedClass: Class? = null): MockDescriptor? @@ -59,17 +58,17 @@ private const val FUNCTIONAL_INTERFACE_CLASS_NAME = "java/lang/FunctionalInterfa private val Class.isLambda: Boolean get() = isInterface && annotations.any { it.type.name == FUNCTIONAL_INTERFACE_CLASS_NAME } -private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { - private fun Class.getFunctionalInterfaces( - interfaces: MutableSet = mutableSetOf() - ): Set { - if (isLambda) { - interfaces.add(this) - } - allAncestors.forEach { it.getFunctionalInterfaces(interfaces) } - return interfaces +private fun Class.getFunctionalInterfaces( + interfaces: MutableSet = mutableSetOf() +): Set { + if (isLambda) { + interfaces.add(this) } + allAncestors.forEach { it.getFunctionalInterfaces(interfaces) } + return interfaces +} +private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { return expectedClass?.isLambda == true && descriptor is ObjectDescriptor } @@ -83,6 +82,8 @@ private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { internal class CompositeMockMaker(private val mockMakers: List) : MockMaker { + constructor(vararg mockMakers: MockMaker) : this(mockMakers.toList()) + override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { return mockMakers.any { mockMaker -> mockMaker.canMock(descriptor, expectedClass) } } @@ -108,11 +109,21 @@ private class ExcludingMockMaker( } } -fun MockMaker.excluding(excludePredicate: (Descriptor) -> Boolean): MockMaker = +fun MockMaker.filterNot(excludePredicate: (Descriptor) -> Boolean): MockMaker = ExcludingMockMaker(this, excludePredicate) +fun MockMaker.filter(includePredicate: (Descriptor) -> Boolean): MockMaker = + ExcludingMockMaker(this) { descriptor -> includePredicate(descriptor).not() } + fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { - MockingRule.LAMBDA -> LambdaMockMaker(ctx) + MockingRule.LAMBDA -> CompositeMockMaker( + LambdaMockMaker(ctx), + UnimplementedMockMaker(ctx).filter { descriptor -> + val klass = (descriptor.type.getKfgType(ctx.types) as? ClassType)?.klass + klass?.getFunctionalInterfaces()?.isNotEmpty() ?: false + } + ) + MockingRule.ANY -> AllMockMaker(ctx) MockingRule.UNIMPLEMENTED -> UnimplementedMockMaker(ctx) } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt index c13f69917..f4f82e780 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -5,7 +5,7 @@ import org.vorpal.research.kex.config.Config enum class MockingRule { // Order is important! First rule applies first - ANY, UNIMPLEMENTED, LAMBDA + UNIMPLEMENTED, ANY, LAMBDA } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index da33ebee7..71525488f 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -11,7 +11,7 @@ import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType -import org.vorpal.research.kex.mocking.excluding +import org.vorpal.research.kex.mocking.filterNot import org.vorpal.research.kex.mocking.getMockMaker import org.vorpal.research.kex.parameters.* import org.vorpal.research.kex.smt.AsyncChecker @@ -109,7 +109,7 @@ private fun Parameters.finalizeDescriptors( .filter { (klass, _) -> klass != null } .associate { (klass, descriptor) -> descriptor to klass!! } - val mockMaker = kexConfig.getMockMaker(ctx).excluding { desc -> desc == instance } + val mockMaker = kexConfig.getMockMaker(ctx).filterNot { desc -> desc == instance } if (!kexConfig.isExpectMocks) { val visited = mutableSetOf() if (this.asList.none { it.requireMocks(mockMaker, expectedClasses, visited) }) { From 86a094e07e135639bd2a4a40e5c913143c0cd26d Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Tue, 19 Mar 2024 23:58:42 +0100 Subject: [PATCH 115/128] All MockMaker subclasses are private. Public functions are used instead --- .../vorpal/research/kex/mocking/MockMaker.kt | 42 +++++++++++-------- .../vorpal/research/kex/mocking/mockUtils.kt | 2 +- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index bac8ee055..c059be0c0 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -54,20 +54,6 @@ private class UnimplementedMockMaker(ctx: ExecutionContext) : AbstractMockMaker( } } -private const val FUNCTIONAL_INTERFACE_CLASS_NAME = "java/lang/FunctionalInterface" -private val Class.isLambda: Boolean - get() = isInterface && annotations.any { it.type.name == FUNCTIONAL_INTERFACE_CLASS_NAME } - -private fun Class.getFunctionalInterfaces( - interfaces: MutableSet = mutableSetOf() -): Set { - if (isLambda) { - interfaces.add(this) - } - allAncestors.forEach { it.getFunctionalInterfaces(interfaces) } - return interfaces -} - private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { return expectedClass?.isLambda == true && descriptor is ObjectDescriptor @@ -81,7 +67,7 @@ private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { } -internal class CompositeMockMaker(private val mockMakers: List) : MockMaker { +private class CompositeMockMaker(private val mockMakers: List) : MockMaker { constructor(vararg mockMakers: MockMaker) : this(mockMakers.toList()) override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { @@ -115,15 +101,35 @@ fun MockMaker.filterNot(excludePredicate: (Descriptor) -> Boolean): MockMaker = fun MockMaker.filter(includePredicate: (Descriptor) -> Boolean): MockMaker = ExcludingMockMaker(this) { descriptor -> includePredicate(descriptor).not() } +fun composeMockMakers(mockMakers: List): MockMaker = CompositeMockMaker(mockMakers) +fun composeMockMakers(vararg mockMakers: MockMaker): MockMaker = CompositeMockMaker(*mockMakers) + fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { - MockingRule.LAMBDA -> CompositeMockMaker( + MockingRule.LAMBDA -> composeMockMakers( LambdaMockMaker(ctx), UnimplementedMockMaker(ctx).filter { descriptor -> - val klass = (descriptor.type.getKfgType(ctx.types) as? ClassType)?.klass - klass?.getFunctionalInterfaces()?.isNotEmpty() ?: false + descriptor.kfgClass(ctx.types)?.getFunctionalInterfaces()?.isNotEmpty() ?: false } ) MockingRule.ANY -> AllMockMaker(ctx) MockingRule.UNIMPLEMENTED -> UnimplementedMockMaker(ctx) } + + +private const val FUNCTIONAL_INTERFACE_CLASS_NAME = "java/lang/FunctionalInterface" +private val Class.isLambda: Boolean + get() = isInterface && annotations.any { it.type.name == FUNCTIONAL_INTERFACE_CLASS_NAME } + +private fun Class.getFunctionalInterfaces( + interfaces: MutableSet = mutableSetOf() +): Set { + if (isLambda) { + interfaces.add(this) + } + allAncestors.forEach { it.getFunctionalInterfaces(interfaces) } + return interfaces +} + +private fun Descriptor.kfgClass(types: TypeFactory): Class? = + (type.getKfgType(types) as? ClassType)?.klass diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt index f4f82e780..34c1f7519 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -14,4 +14,4 @@ fun Config.getMockMaker(ctx: ExecutionContext): MockMaker = .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } .sortedBy { rule -> rule.ordinal } .map { rule -> createMockMaker(rule, ctx) } - .let { mockMakers -> CompositeMockMaker(mockMakers) } \ No newline at end of file + .let { mockMakers -> composeMockMakers(mockMakers) } \ No newline at end of file From 56ea8175bb430b5fb611bbf7b860595a4e22f1a5 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 20 Mar 2024 12:31:35 +0100 Subject: [PATCH 116/128] MockMaker fixes --- .../org/vorpal/research/kex/mocking/MockMaker.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index c059be0c0..ee179c79b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -18,14 +18,13 @@ interface MockMaker { } private sealed class AbstractMockMaker(protected val ctx: ExecutionContext) : MockMaker { - protected val types: TypeFactory get() = ctx.types protected fun satisfiesNecessaryConditions(descriptor: Descriptor): Boolean { val klass = descriptor.kfgClass ?: return false return !klass.isFinal && !descriptor.type.isKexRt && descriptor is ObjectDescriptor } protected val Descriptor.kfgClass: Class? - get() = (type.getKfgType(types) as? ClassType)?.klass + get() = kfgClass(ctx.types) } private class AllMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { @@ -42,7 +41,7 @@ private class AllMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { private class UnimplementedMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { - val klass = (descriptor.type.getKfgType(types) as? ClassType)?.klass ?: return false + val klass = descriptor.kfgClass ?: return false return satisfiesNecessaryConditions(descriptor) && !instantiationManager.isInstantiable(klass) } @@ -106,10 +105,10 @@ fun composeMockMakers(vararg mockMakers: MockMaker): MockMaker = CompositeMockMa fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { MockingRule.LAMBDA -> composeMockMakers( - LambdaMockMaker(ctx), - UnimplementedMockMaker(ctx).filter { descriptor -> + AllMockMaker(ctx).filter { descriptor -> descriptor.kfgClass(ctx.types)?.getFunctionalInterfaces()?.isNotEmpty() ?: false - } + }, + LambdaMockMaker(ctx), ) MockingRule.ANY -> AllMockMaker(ctx) From da7f66a7792fd2eba420cf1313ea5d2525b0df07 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 20 Mar 2024 12:41:43 +0100 Subject: [PATCH 117/128] LambdaMockMaker tries to preserve original class in mock --- .../main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index ee179c79b..5501128a2 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -60,8 +60,11 @@ private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { override fun mockOrNull(original: Descriptor, expectedClass: Class?): MockDescriptor? { if (!canMock(original, expectedClass)) return null + original as ObjectDescriptor - return descriptor { mock(original as ObjectDescriptor, expectedClass!!.kexType) } + val mockKlass = original.kfgClass!!.let { if (it.isFinal) it.superClass!! else it }.kexType + val interfaces = setOf(expectedClass!!.kexType).filter { it != mockKlass }.toSet() + return descriptor { mock(original, mockKlass, interfaces) } } } From 9cf0d401c550c0826a241d261e29f49228f22a97 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 20 Mar 2024 12:43:04 +0100 Subject: [PATCH 118/128] Config refactoring --- kex-test.ini | 4 +--- kex.ini | 7 +------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/kex-test.ini b/kex-test.ini index 32cd117da..b6e24bf6f 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -36,12 +36,10 @@ rule = lambda ; true -- mocks are usually needed (hot-path) expectMocks = true -;easy-random tweaks -easyRandomExcludeLambdas = true - ; workarounds mockitoClassesWorkaround = true java8WorkaroundEnabled = true +easyRandomExcludeLambdas = true ; extra logging logTypeFix = false diff --git a/kex.ini b/kex.ini index 6aeed0b1b..c0e9d58a9 100644 --- a/kex.ini +++ b/kex.ini @@ -48,18 +48,13 @@ rule = lambda ;rule = any ;rule = unimplemented -; normally should be missing or false -test = false - ; true -- mocks are usually needed (hot-path) expectMocks = true -;easy-random -easyRandomExcludeLambdas = false - ; workarounds mockitoClassesWorkaround = true java8WorkaroundEnabled = true +easyRandomExcludeLambdas = false ; logging logTypeFix = false From 92cf2db375ac3e7bf37642b8968937574179d185 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 21 Mar 2024 13:40:24 +0100 Subject: [PATCH 119/128] Don't mock methods mockito cant mock --- .../research/kex/parameters/Parameters.kt | 17 +++++++++++++++-- .../kex/asm/analysis/util/extensions.kt | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 2ce4b3bb3..7c558fe97 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -13,6 +13,9 @@ import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.util.KfgTargetFilter import org.vorpal.research.kfg.ClassManager import org.vorpal.research.kfg.ir.Class +import org.vorpal.research.kfg.ir.Method +import org.vorpal.research.kfg.type.TypeFactory +import org.vorpal.research.kfg.type.objectType import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn import kotlin.random.Random @@ -123,15 +126,25 @@ fun Descriptor.requireMocks( any(visited) { descriptor -> mockMaker.canMock(descriptor, expectedClass[descriptor]) } +private fun Method.mockitoCanMock(types: TypeFactory): Boolean = when { + name == "getClass" && argTypes.isEmpty() -> false + name == "hashCode" && argTypes.isEmpty() -> false + name == "equals" && argTypes == listOf(types.objectType) -> false + isFinal || isPrivate -> false + + else -> true +} + fun setupMocks( + types: TypeFactory, methodCalls: List, termToDescriptor: Map, - descriptorToMock: Map + descriptorToMock: Map, ) { for (callPredicate in methodCalls) { if (!callPredicate.hasLhv) continue val call = callPredicate.call as CallTerm - if (call.method.name == "getClass") continue + if (call.method.mockitoCanMock(types).not()) continue val mock = termToDescriptor[call.owner]?.let { descriptorToMock[it] ?: it } as? MockDescriptor diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index 71525488f..e5145ece7 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -126,7 +126,7 @@ private fun Parameters.finalizeDescriptors( val descriptorToMock = createDescriptorToMock(generator.allValues, mockMaker, expectedClasses) val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = state.methodCalls() - setupMocks(methodCalls, generator.memory, descriptorToMock) + setupMocks(ctx.types, methodCalls, generator.memory, descriptorToMock) return withMocks } From 1d0f806d820e4a8a2a53aaa042aa74a2cb71590a Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 21 Mar 2024 13:40:43 +0100 Subject: [PATCH 120/128] Don't mock classes/interfaces mockito can't mock --- .../vorpal/research/kex/mocking/MockMaker.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index 5501128a2..e6582579c 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -17,10 +17,14 @@ interface MockMaker { fun mockOrNull(original: Descriptor, expectedClass: Class? = null): MockDescriptor? } +// 2 last conditions are Kex limitations +private fun Class.canMock(): Boolean = + !isFinal && !isPrivate && !isKexRt && !(!isPublic && pkg.concreteName.startsWith("java")) + private sealed class AbstractMockMaker(protected val ctx: ExecutionContext) : MockMaker { protected fun satisfiesNecessaryConditions(descriptor: Descriptor): Boolean { val klass = descriptor.kfgClass ?: return false - return !klass.isFinal && !descriptor.type.isKexRt && descriptor is ObjectDescriptor + return klass.canMock() && !descriptor.type.isKexRt && descriptor is ObjectDescriptor } protected val Descriptor.kfgClass: Class? @@ -42,8 +46,8 @@ private class AllMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { private class UnimplementedMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { val klass = descriptor.kfgClass ?: return false - return satisfiesNecessaryConditions(descriptor) && - !instantiationManager.isInstantiable(klass) + return satisfiesNecessaryConditions(descriptor) + && !instantiationManager.isInstantiable(klass) } override fun mockOrNull(original: Descriptor, expectedClass: Class?): MockDescriptor? { @@ -62,9 +66,17 @@ private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { if (!canMock(original, expectedClass)) return null original as ObjectDescriptor - val mockKlass = original.kfgClass!!.let { if (it.isFinal) it.superClass!! else it }.kexType - val interfaces = setOf(expectedClass!!.kexType).filter { it != mockKlass }.toSet() - return descriptor { mock(original, mockKlass, interfaces) } + var mockKlass = original.kfgClass!! + val interfaces = mutableSetOf(expectedClass!!) + while (!mockKlass.canMock()) { + interfaces.addAll(mockKlass.interfaces) + mockKlass = mockKlass.superClass!! + } + interfaces.remove(mockKlass) + interfaces.removeIf { klass -> !klass.canMock() } + return descriptor { + mock(original, mockKlass.kexType, interfaces.map { it.kexType }.toSet()) + } } } From f4c971f305867308412d28c1da28d25edec66812 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Fri, 22 Mar 2024 11:21:12 +0100 Subject: [PATCH 121/128] UnimplementedMockMaker refactoring --- .../vorpal/research/kex/mocking/MockMaker.kt | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index e6582579c..79d41ab16 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -17,7 +17,6 @@ interface MockMaker { fun mockOrNull(original: Descriptor, expectedClass: Class? = null): MockDescriptor? } -// 2 last conditions are Kex limitations private fun Class.canMock(): Boolean = !isFinal && !isPrivate && !isKexRt && !(!isPublic && pkg.concreteName.startsWith("java")) @@ -43,20 +42,6 @@ private class AllMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { } } -private class UnimplementedMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { - override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { - val klass = descriptor.kfgClass ?: return false - return satisfiesNecessaryConditions(descriptor) - && !instantiationManager.isInstantiable(klass) - } - - override fun mockOrNull(original: Descriptor, expectedClass: Class?): MockDescriptor? { - if (!canMock(original)) return null - original as ObjectDescriptor - return MockDescriptor(original).also { it.fields.putAll(original.fields) } - } -} - private class LambdaMockMaker(ctx: ExecutionContext) : AbstractMockMaker(ctx) { override fun canMock(descriptor: Descriptor, expectedClass: Class?): Boolean { return expectedClass?.isLambda == true && descriptor is ObjectDescriptor @@ -127,7 +112,11 @@ fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when ) MockingRule.ANY -> AllMockMaker(ctx) - MockingRule.UNIMPLEMENTED -> UnimplementedMockMaker(ctx) + + MockingRule.UNIMPLEMENTED -> AllMockMaker(ctx).filter { descriptor -> + val klass = descriptor.kfgClass(ctx.types) ?: return@filter false + instantiationManager.isInstantiable(klass).not() + } } From 0979530d20ccd7895b4f02b923563cd64c49c17e Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 4 Apr 2024 12:36:01 +0200 Subject: [PATCH 122/128] `finalizeDescriptors` -> `performMocking`, `generateInitialDescriptors` returns `InitialDescriptors` --- .../kex/asm/analysis/util/extensions.kt | 66 +++++++++---------- .../generator/GeneratorContext.kt | 2 +- .../state/transformer/DescriptorGenerator.kt | 23 ++++--- 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index e5145ece7..e19264767 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -31,6 +31,11 @@ import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn +private fun SymbolicState.methodCalls(): List { + return clauses.map { clause -> clause.predicate }.filterIsInstance() +} + + suspend fun Method.analyzeOrTimeout( accessLevel: AccessModifier, analysis: suspend (Method) -> Unit @@ -50,11 +55,6 @@ suspend fun Method.analyzeOrTimeout( } -fun SymbolicState.methodCalls(): List { - return clauses.map { clause -> clause.predicate }.filterIsInstance() -} - - suspend fun Method.checkAsync( ctx: ExecutionContext, state: SymbolicState, @@ -73,16 +73,8 @@ suspend fun Method.checkAsync( } return try { - val (initialDescriptors, generator) = generateInitialDescriptors( - this, - ctx, - result.model, - checker.state - ) - - val finalDescriptors = initialDescriptors.finalizeDescriptors(ctx, generator, state, this) - - finalDescriptors + generateInitialDescriptors(this, ctx, result.model, checker.state) + .performMocking(ctx, state, this) .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random) .also { log.debug { "Generated params:\n$it" } } .filterIgnoredStatic() @@ -93,38 +85,37 @@ suspend fun Method.checkAsync( } -private fun Parameters.finalizeDescriptors( +private fun InitialDescriptors.performMocking( ctx: ExecutionContext, - generator: DescriptorGenerator, state: SymbolicState, method: Method ): Parameters { if (!kexConfig.isMockingEnabled) { - return this + return descriptors } val expectedClasses = method.argTypes .map { (it as? ClassType)?.klass } - .zip(arguments) + .zip(descriptors.arguments) .filter { (klass, _) -> klass != null } .associate { (klass, descriptor) -> descriptor to klass!! } - val mockMaker = kexConfig.getMockMaker(ctx).filterNot { desc -> desc == instance } + val mockMaker = kexConfig.getMockMaker(ctx).filterNot { desc -> desc == descriptors.instance } if (!kexConfig.isExpectMocks) { val visited = mutableSetOf() - if (this.asList.none { it.requireMocks(mockMaker, expectedClasses, visited) }) { - return this + if (descriptors.asList.none { it.requireMocks(mockMaker, expectedClasses, visited) }) { + return descriptors } } generator.generateAll() val visited = mutableSetOf() if (generator.allValues.none { it.requireMocks(mockMaker, expectedClasses, visited) }) { - return this + return descriptors } val descriptorToMock = createDescriptorToMock(generator.allValues, mockMaker, expectedClasses) - val withMocks = this.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } + val withMocks = descriptors.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = state.methodCalls() setupMocks(ctx.types, methodCalls, generator.memory, descriptorToMock) return withMocks @@ -157,14 +148,16 @@ suspend fun Method.checkAsyncAndSlice( ) val filteredParams = params.concreteParameters(ctx.cm, ctx.accessLevel, ctx.random) - .also {log.debug { "Generated params:\n$it" } - } + .also { + log.debug { "Generated params:\n$it" } + } .filterIgnoredStatic() val (thisTerm, argTerms) = collectArguments(checker.state) - val termParams = Parameters(thisTerm, this@checkAsyncAndSlice.argTypes.mapIndexed { index, type -> - argTerms[index] ?: term { arg(type.kexType, index) } - }) + val termParams = + Parameters(thisTerm, this@checkAsyncAndSlice.argTypes.mapIndexed { index, type -> + argTerms[index] ?: term { arg(type.kexType, index) } + }) filteredParams to ConstraintExceptionPrecondition( termParams, @@ -195,7 +188,8 @@ suspend fun Method.checkAsyncIncremental( this, IncrementalPredicateState( clauses + query, - queries.map { PredicateQuery(it.clauses.asState() + it.path.asState()) }.toPersistentList() + queries.map { PredicateQuery(it.clauses.asState() + it.path.asState()) } + .toPersistentList() ), concreteTypeInfo, enableInlining @@ -206,9 +200,7 @@ suspend fun Method.checkAsyncIncremental( is Result.SatResult -> try { val fullPS = checker.state + checker.queries[index].hardConstraints generateInitialDescriptors(this, ctx, result.model, fullPS) - .let { (descriptors, generator) -> - descriptors.finalizeDescriptors(ctx, generator, state, this) - } + .performMocking(ctx, state, this) .concreteParameters(ctx.cm, ctx.accessLevel, ctx.random).also { log.debug { "Generated params:\n$it" } } @@ -243,7 +235,8 @@ suspend fun Method.checkAsyncIncrementalAndSlice( this, IncrementalPredicateState( clauses + query, - queries.map { PredicateQuery(it.clauses.asState() + it.path.asState()) }.toPersistentList() + queries.map { PredicateQuery(it.clauses.asState() + it.path.asState()) } + .toPersistentList() ), concreteTypeInfo, enableInlining @@ -274,7 +267,10 @@ suspend fun Method.checkAsyncIncrementalAndSlice( filteredParams to ConstraintExceptionPrecondition( termParams, - SymbolicStateForwardSlicer(termParams.asList.toSet(), aa).apply(state + queries[index]) + SymbolicStateForwardSlicer( + termParams.asList.toSet(), + aa + ).apply(state + queries[index]) ) } catch (e: Throwable) { log.error("Error during descriptor generation: ", e) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/GeneratorContext.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/GeneratorContext.kt index 27f720098..52b2f7a6a 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/GeneratorContext.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/GeneratorContext.kt @@ -287,7 +287,7 @@ class GeneratorContext( return when (val result = checker.check(checkedState)) { is Result.SatResult -> { log.debug("Model: {}", result.model) - generateInitialDescriptors(this, context, result.model, checker.state).first + generateInitialDescriptors(this, context, result.model, checker.state).descriptors } else -> null diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index 233aaf9f2..88cd99b88 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -130,21 +130,28 @@ fun generateFinalTypeInfoMap( ) } +data class InitialDescriptors internal constructor( + val descriptors: Parameters, + val generator: DescriptorGenerator +) + fun generateInitialDescriptors( method: Method, ctx: ExecutionContext, model: SMTModel, state: PredicateState -): Pair, DescriptorGenerator> { +): InitialDescriptors { val generator = DescriptorGenerator(method, ctx, model, InitialDescriptorReanimator(model, ctx)) generator.apply(state) - return Parameters( - generator.instance, - generator.args.mapIndexed { index, arg -> - arg ?: descriptor { default(method.argTypes[index].kexType) } - }, - generator.staticFields - ) to generator + return InitialDescriptors( + Parameters( + generator.instance, + generator.args.mapIndexed { index, arg -> + arg ?: descriptor { default(method.argTypes[index].kexType) } + }, + generator.staticFields + ), generator + ) } From 5c4eaf1a165f30f3a582d8246f69dc112a5bb892 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 4 Apr 2024 14:31:16 +0200 Subject: [PATCH 123/128] Refactoring --- .../kex/descriptor/functionalExtensions.kt | 12 +- .../vorpal/research/kex/mocking/MockMaker.kt | 3 - .../vorpal/research/kex/mocking/mockUtils.kt | 22 +++- .../vorpal/research/kex/mocking/mocking.kt | 114 ++++++++++++++++++ .../research/kex/parameters/Parameters.kt | 74 +----------- .../kex/asm/analysis/util/extensions.kt | 48 +------- .../state/transformer/DescriptorGenerator.kt | 18 ++- 7 files changed, 160 insertions(+), 131 deletions(-) create mode 100644 kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mocking.kt diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt index 2c36c212d..1277c8bd6 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt @@ -22,32 +22,32 @@ fun Descriptor.transform( private fun Descriptor.transformChildren( mapped: MutableMap, - failureMappings: MutableSet, + failedMappings: MutableSet, transformOrNull: (Descriptor) -> T? ) { when (this) { is ConstantDescriptor -> Unit is ClassDescriptor -> fields.replaceAll { _, descriptor -> - descriptor.transform(mapped, failureMappings, transformOrNull) + descriptor.transform(mapped, failedMappings, transformOrNull) } is ObjectDescriptor -> fields.replaceAll { _, descriptor -> - descriptor.transform(mapped, failureMappings, transformOrNull) + descriptor.transform(mapped, failedMappings, transformOrNull) } is MockDescriptor -> { fields.replaceAll { _, descriptor -> - descriptor.transform(mapped, failureMappings, transformOrNull) + descriptor.transform(mapped, failedMappings, transformOrNull) } methodReturns.values.forEach { values -> values.replaceAll { descriptor -> - descriptor.transform(mapped, failureMappings, transformOrNull) + descriptor.transform(mapped, failedMappings, transformOrNull) } } } is ArrayDescriptor -> elements.replaceAll { _, descriptor -> - descriptor.transform(mapped, failureMappings, transformOrNull) + descriptor.transform(mapped, failedMappings, transformOrNull) } } } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index 79d41ab16..edf472f7e 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -17,9 +17,6 @@ interface MockMaker { fun mockOrNull(original: Descriptor, expectedClass: Class? = null): MockDescriptor? } -private fun Class.canMock(): Boolean = - !isFinal && !isPrivate && !isKexRt && !(!isPublic && pkg.concreteName.startsWith("java")) - private sealed class AbstractMockMaker(protected val ctx: ExecutionContext) : MockMaker { protected fun satisfiesNecessaryConditions(descriptor: Descriptor): Boolean { val klass = descriptor.kfgClass ?: return false diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt index 34c1f7519..3f3a5f95b 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -2,6 +2,13 @@ package org.vorpal.research.kex.mocking import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.config.Config +import org.vorpal.research.kex.ktype.KexRtManager.isKexRt +import org.vorpal.research.kfg.ir.Class +import org.vorpal.research.kfg.ir.Method +import org.vorpal.research.kfg.type.TypeFactory +import org.vorpal.research.kfg.type.objectType +import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.logging.warn enum class MockingRule { // Order is important! First rule applies first @@ -14,4 +21,17 @@ fun Config.getMockMaker(ctx: ExecutionContext): MockMaker = .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } .sortedBy { rule -> rule.ordinal } .map { rule -> createMockMaker(rule, ctx) } - .let { mockMakers -> composeMockMakers(mockMakers) } \ No newline at end of file + .let { mockMakers -> composeMockMakers(mockMakers) } + + +fun Class.canMock(): Boolean = + !isFinal && !isPrivate && !isKexRt && !(!isPublic && pkg.concreteName.startsWith("java")) + +fun Method.canMock(types: TypeFactory): Boolean = when { + name == "getClass" && argTypes.isEmpty() -> false + name == "hashCode" && argTypes.isEmpty() -> false + name == "equals" && argTypes == listOf(types.objectType) -> false + isFinal || isPrivate -> false + + else -> true +} \ No newline at end of file diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mocking.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mocking.kt new file mode 100644 index 000000000..aff7ab9f1 --- /dev/null +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mocking.kt @@ -0,0 +1,114 @@ +package org.vorpal.research.kex.mocking + +import org.vorpal.research.kex.ExecutionContext +import org.vorpal.research.kex.config.kexConfig +import org.vorpal.research.kex.descriptor.Descriptor +import org.vorpal.research.kex.descriptor.MockDescriptor +import org.vorpal.research.kex.descriptor.any +import org.vorpal.research.kex.descriptor.transform +import org.vorpal.research.kex.parameters.Parameters +import org.vorpal.research.kex.parameters.map +import org.vorpal.research.kex.state.predicate.CallPredicate +import org.vorpal.research.kex.state.term.CallTerm +import org.vorpal.research.kex.state.term.Term +import org.vorpal.research.kex.trace.symbolic.SymbolicState +import org.vorpal.research.kex.util.isExpectMocks +import org.vorpal.research.kex.util.isMockingEnabled +import org.vorpal.research.kfg.ir.Class +import org.vorpal.research.kfg.ir.Method +import org.vorpal.research.kfg.type.ClassType +import org.vorpal.research.kfg.type.TypeFactory +import org.vorpal.research.kthelper.logging.log +import org.vorpal.research.kthelper.logging.warn + +fun Descriptor.isRequireMocks( + mockMaker: MockMaker, + expectedClass: Map, + visited: MutableSet = mutableSetOf() +): Boolean = + any(visited) { descriptor -> mockMaker.canMock(descriptor, expectedClass[descriptor]) } + +fun createDescriptorToMock( + allDescriptors: Iterable, + mockMaker: MockMaker, + expectedClasses: Map +): Map { + val descriptorToMock = mutableMapOf() + allDescriptors.forEach { + it.transform(descriptorToMock) { descriptor -> + mockMaker.mockOrNull(descriptor, expectedClasses[descriptor]) + } + } + return descriptorToMock +} + +fun setupMocks( + types: TypeFactory, + methodCalls: List, + termToDescriptor: Map, + descriptorToMock: Map, +) { + for (callPredicate in methodCalls) { + if (!callPredicate.hasLhv) continue + val call = callPredicate.call as CallTerm + if (call.method.canMock(types).not()) continue + + val mock = + termToDescriptor[call.owner]?.let { descriptorToMock[it] ?: it } as? MockDescriptor + val value = termToDescriptor[callPredicate.lhvUnsafe]?.let { descriptorToMock[it] ?: it } + mock ?: log.warn { "No mock for $call" } + + if (mock is MockDescriptor && value != null) { + mock.addReturnValue(call.method, value) + } + } +} + + +interface NonMockedDescriptors { + val descriptors: Parameters + val termToDescriptor: Map + val allDescriptors: Iterable + + fun generateAllDescriptors() +} + +fun NonMockedDescriptors.performMocking( + ctx: ExecutionContext, + state: SymbolicState, + method: Method +): Parameters { + if (!kexConfig.isMockingEnabled) { + return descriptors + } + + val expectedClasses = method.argTypes + .map { (it as? ClassType)?.klass } + .zip(descriptors.arguments) + .filter { (klass, _) -> klass != null } + .associate { (klass, descriptor) -> descriptor to klass!! } + + val mockMaker = kexConfig.getMockMaker(ctx).filterNot { desc -> desc == descriptors.instance } + if (!kexConfig.isExpectMocks) { + val visited = mutableSetOf() + if (descriptors.asList.none { it.isRequireMocks(mockMaker, expectedClasses, visited) }) { + return descriptors + } + } + generateAllDescriptors() + val visited = mutableSetOf() + if (allDescriptors.none { it.isRequireMocks(mockMaker, expectedClasses, visited) }) { + return descriptors + } + + + val descriptorToMock = createDescriptorToMock(allDescriptors, mockMaker, expectedClasses) + val withMocks = descriptors.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } + val methodCalls = state.methodCalls() + setupMocks(ctx.types, methodCalls, termToDescriptor, descriptorToMock) + return withMocks +} + +private fun SymbolicState.methodCalls(): List { + return clauses.map { clause -> clause.predicate }.filterIsInstance() +} diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt index 7c558fe97..d5ba9c902 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/Parameters.kt @@ -6,18 +6,8 @@ import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexClass import org.vorpal.research.kex.ktype.KexRtManager.rtMapped -import org.vorpal.research.kex.mocking.MockMaker -import org.vorpal.research.kex.state.predicate.CallPredicate -import org.vorpal.research.kex.state.term.CallTerm -import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.util.KfgTargetFilter import org.vorpal.research.kfg.ClassManager -import org.vorpal.research.kfg.ir.Class -import org.vorpal.research.kfg.ir.Method -import org.vorpal.research.kfg.type.TypeFactory -import org.vorpal.research.kfg.type.objectType -import org.vorpal.research.kthelper.logging.log -import org.vorpal.research.kthelper.logging.warn import kotlin.random.Random @Serializable @@ -37,11 +27,12 @@ data class Parameters( } } -fun Parameters.map(transform: (T) -> U): Parameters { +// `instance` is always nullable, so `transform` may or may not accept null +inline fun Parameters.map(transform: (F) -> U): Parameters { return Parameters( - this.instance?.let(transform), - this.arguments.map(transform), - this.statics.mapTo(mutableSetOf(), transform) + if (instance is F) transform(instance) else null, + arguments.map(transform), + statics.mapTo(mutableSetOf(), transform) ) } @@ -101,58 +92,3 @@ fun Parameters.filterIgnoredStatic(): Parameters { } return Parameters(instance, arguments, filteredStatics) } - - -fun createDescriptorToMock( - allDescriptors: Collection, - mockMaker: MockMaker, - expectedClasses: Map -): Map { - val descriptorToMock = mutableMapOf() - allDescriptors.forEach { - it.transform(descriptorToMock) { descriptor -> - mockMaker.mockOrNull(descriptor, expectedClasses[descriptor]) - } - } - return descriptorToMock -} - - -fun Descriptor.requireMocks( - mockMaker: MockMaker, - expectedClass: Map, - visited: MutableSet = mutableSetOf() -): Boolean = - any(visited) { descriptor -> mockMaker.canMock(descriptor, expectedClass[descriptor]) } - - -private fun Method.mockitoCanMock(types: TypeFactory): Boolean = when { - name == "getClass" && argTypes.isEmpty() -> false - name == "hashCode" && argTypes.isEmpty() -> false - name == "equals" && argTypes == listOf(types.objectType) -> false - isFinal || isPrivate -> false - - else -> true -} - -fun setupMocks( - types: TypeFactory, - methodCalls: List, - termToDescriptor: Map, - descriptorToMock: Map, -) { - for (callPredicate in methodCalls) { - if (!callPredicate.hasLhv) continue - val call = callPredicate.call as CallTerm - if (call.method.mockitoCanMock(types).not()) continue - - val mock = - termToDescriptor[call.owner]?.let { descriptorToMock[it] ?: it } as? MockDescriptor - val value = termToDescriptor[callPredicate.lhvUnsafe]?.let { descriptorToMock[it] ?: it } - mock ?: log.warn { "No mock for $call" } - - if (mock is MockDescriptor && value != null) { - mock.addReturnValue(call.method, value) - } - } -} \ No newline at end of file diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt index e19264767..84c146f89 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/asm/analysis/util/extensions.kt @@ -6,35 +6,25 @@ import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.asm.analysis.crash.precondition.ConstraintExceptionPrecondition import org.vorpal.research.kex.asm.manager.MethodManager import org.vorpal.research.kex.asm.util.AccessModifier -import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.descriptor.* import org.vorpal.research.kex.ktype.KexRtManager.isJavaRt import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType -import org.vorpal.research.kex.mocking.filterNot -import org.vorpal.research.kex.mocking.getMockMaker +import org.vorpal.research.kex.mocking.* import org.vorpal.research.kex.parameters.* import org.vorpal.research.kex.smt.AsyncChecker import org.vorpal.research.kex.smt.AsyncIncrementalChecker import org.vorpal.research.kex.smt.Result import org.vorpal.research.kex.state.IncrementalPredicateState import org.vorpal.research.kex.state.PredicateQuery -import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.term.term import org.vorpal.research.kex.state.transformer.* import org.vorpal.research.kex.trace.symbolic.SymbolicState -import org.vorpal.research.kex.util.isExpectMocks -import org.vorpal.research.kex.util.isMockingEnabled import org.vorpal.research.kfg.ir.Method -import org.vorpal.research.kfg.type.ClassType import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.logging.warn -private fun SymbolicState.methodCalls(): List { - return clauses.map { clause -> clause.predicate }.filterIsInstance() -} - suspend fun Method.analyzeOrTimeout( accessLevel: AccessModifier, @@ -85,42 +75,6 @@ suspend fun Method.checkAsync( } -private fun InitialDescriptors.performMocking( - ctx: ExecutionContext, - state: SymbolicState, - method: Method -): Parameters { - if (!kexConfig.isMockingEnabled) { - return descriptors - } - - val expectedClasses = method.argTypes - .map { (it as? ClassType)?.klass } - .zip(descriptors.arguments) - .filter { (klass, _) -> klass != null } - .associate { (klass, descriptor) -> descriptor to klass!! } - - val mockMaker = kexConfig.getMockMaker(ctx).filterNot { desc -> desc == descriptors.instance } - if (!kexConfig.isExpectMocks) { - val visited = mutableSetOf() - if (descriptors.asList.none { it.requireMocks(mockMaker, expectedClasses, visited) }) { - return descriptors - } - } - generator.generateAll() - val visited = mutableSetOf() - if (generator.allValues.none { it.requireMocks(mockMaker, expectedClasses, visited) }) { - return descriptors - } - - - val descriptorToMock = createDescriptorToMock(generator.allValues, mockMaker, expectedClasses) - val withMocks = descriptors.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } - val methodCalls = state.methodCalls() - setupMocks(ctx.types, methodCalls, generator.memory, descriptorToMock) - return withMocks -} - @Suppress("unused") suspend fun Method.checkAsyncAndSlice( ctx: ExecutionContext, diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index 88cd99b88..aacebdbcf 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -8,6 +8,7 @@ import org.vorpal.research.kex.ktype.KexPointer import org.vorpal.research.kex.ktype.KexReference import org.vorpal.research.kex.ktype.KexRtManager.rtMapped import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.mocking.NonMockedDescriptors import org.vorpal.research.kex.parameters.Parameters import org.vorpal.research.kex.smt.FinalDescriptorReanimator import org.vorpal.research.kex.smt.InitialDescriptorReanimator @@ -130,17 +131,24 @@ fun generateFinalTypeInfoMap( ) } -data class InitialDescriptors internal constructor( - val descriptors: Parameters, - val generator: DescriptorGenerator -) +private data class InitialDescriptors( + override val descriptors: Parameters, + private val generator: DescriptorGenerator, +) : NonMockedDescriptors { + override val termToDescriptor: Map get() = generator.memory + override val allDescriptors: Iterable get() = generator.allValues + + override fun generateAllDescriptors() { + generator.generateAll() + } +} fun generateInitialDescriptors( method: Method, ctx: ExecutionContext, model: SMTModel, state: PredicateState -): InitialDescriptors { +): NonMockedDescriptors { val generator = DescriptorGenerator(method, ctx, model, InitialDescriptorReanimator(model, ctx)) generator.apply(state) return InitialDescriptors( From 716f9d88f1d9cda1439fddd00390bec50e002ce7 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 4 Apr 2024 14:50:48 +0200 Subject: [PATCH 124/128] Useful config entry for tests --- .../kotlin/org/vorpal/research/kex/util/rt.kt | 3 +++ .../research/kex/concolic/MockConcolicLongTest.kt | 15 ++++++++------- kex-test.ini | 3 +++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index c309e6592..ac5aea4db 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -81,6 +81,9 @@ val Config.isFixConcreteLambdas: Boolean val Config.isEasyRandomExcludeLambdas: Boolean get() = getBooleanValue("mock", "easyRandomExcludeLambdas", false) +val Config.isZeroCoverageEpsilon: Boolean + get() = getBooleanValue("mock", "zeroCoverageEpsilon", false) + val Config.mockito: Container? get() { val libPath = libPath ?: return null diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index 3a27c6c64..345fba146 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -4,8 +4,8 @@ import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import org.junit.Test -import org.vorpal.research.kex.config.RuntimeConfig import org.vorpal.research.kex.config.kexConfig +import org.vorpal.research.kex.util.isZeroCoverageEpsilon import kotlin.time.ExperimentalTime @ExperimentalTime @@ -15,6 +15,10 @@ import kotlin.time.ExperimentalTime class MockConcolicLongTest : ConcolicTest("mock-concolic") { val prefix = "org/vorpal/research/kex/test/concolic/mock/" + companion object { + private fun eps(eps: Double): Double = if (kexConfig.isZeroCoverageEpsilon) 0.0 else eps + } + @Test fun mockTest() { assertCoverage(cm[prefix + "MockTests"], 1.0) @@ -42,14 +46,12 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockStaticsTests() { - val eps = 0.03 - assertCoverage(cm[prefix + "MockStaticsTests"], 1.0, eps) + assertCoverage(cm[prefix + "MockStaticsTests"], 1.0) } @Test fun mockListTests() { - val eps = 0.12 - assertCoverage(cm[prefix + "MockListTests"], 1.0, eps) + assertCoverage(cm[prefix + "MockListTests"], 1.0, eps(0.12)) } @Test @@ -59,8 +61,7 @@ class MockConcolicLongTest : ConcolicTest("mock-concolic") { @Test fun mockSetTests() { - val eps = 0.5 - assertCoverage(cm[prefix + "MockSetTests"], 1.0, eps) + assertCoverage(cm[prefix + "MockSetTests"], 1.0, eps(0.5)) } @Test diff --git a/kex-test.ini b/kex-test.ini index b6e24bf6f..189322ca3 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -45,6 +45,9 @@ easyRandomExcludeLambdas = true logTypeFix = false logStackTraceTypeFix = false +; for testing +zeroCoverageEpsilon = false + [reanimator] enabled = true maxStackSize = 5 From 7f60cf562d0f4cd44e630b304761fa7c2f060dd0 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 4 Apr 2024 14:53:45 +0200 Subject: [PATCH 125/128] fix --- .../research/kex/state/transformer/DescriptorGenerator.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt index aacebdbcf..96e8d16c4 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/state/transformer/DescriptorGenerator.kt @@ -131,7 +131,8 @@ fun generateFinalTypeInfoMap( ) } -private data class InitialDescriptors( + +private class InitialDescriptors( override val descriptors: Parameters, private val generator: DescriptorGenerator, ) : NonMockedDescriptors { From 7885fa0f8f3af3710c07d64157a722b90fcae3a4 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 4 Apr 2024 15:15:05 +0200 Subject: [PATCH 126/128] config fix --- kex-test.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kex-test.ini b/kex-test.ini index 61f2f1195..722f6be84 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -31,7 +31,7 @@ mockitoVersion = 4.11.0 rule = lambda ;rule = any -;rule = unimplemented +rule = unimplemented ; true -- mocks are usually needed (hot-path) expectMocks = true From 35755dc1aca5f0ae28ddfb30bf8cc69c1d69fd75 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 11 Apr 2024 13:41:50 +0200 Subject: [PATCH 127/128] Better Descriptor.transform fixes MockSetField, MockSequence -> MockList --- .../research/kex/descriptor/converter.kt | 1 + .../research/kex/descriptor/descriptor.kt | 2 +- .../kex/descriptor/functionalExtensions.kt | 33 +++++---- .../vorpal/research/kex/mocking/MockMaker.kt | 2 +- .../vorpal/research/kex/mocking/mockUtils.kt | 47 ++++++++++--- .../vorpal/research/kex/mocking/mocking.kt | 7 +- .../kex/random/easyrandom/easyRandom.kt | 1 + ...kt => ClassLoaderWithMockitoWorkaround.kt} | 9 +-- .../research/kex/util/KfgClassLoader.kt | 2 +- .../research/kex/util/PathClassLoader.kt | 2 +- .../org/vorpal/research/kex/util/kfg.kt | 2 + .../kotlin/org/vorpal/research/kex/util/rt.kt | 43 ++---------- .../research/kex/launcher/WorkerLauncher.kt | 2 +- .../research/kex/compile/CompilerHelper.kt | 4 +- .../actionsequence/ActionSequence.kt | 67 +++++++++++++------ .../actionsequence/generator/MockGenerator.kt | 15 ++--- .../javagen/ActionSequence2JavaPrinter.kt | 15 +++-- .../codegen/javagen/ExecutorAS2JavaPrinter.kt | 35 +++++----- .../kotlingen/ActionSequence2KotlinPrinter.kt | 2 +- .../kex/concolic/MockConcolicLongTest.kt | 2 +- 20 files changed, 161 insertions(+), 132 deletions(-) rename kex-core/src/main/kotlin/org/vorpal/research/kex/util/{HackedClassLoader.kt => ClassLoaderWithMockitoWorkaround.kt} (71%) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt index a855b4010..ec836c60e 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/converter.kt @@ -16,6 +16,7 @@ import org.vorpal.research.kex.ktype.KexShort import org.vorpal.research.kex.ktype.KexString import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.ktype.asArray +import org.vorpal.research.kex.mocking.isMockitoClassesWorkaroundEnabled import org.vorpal.research.kex.util.* import org.vorpal.research.kfg.type.SystemTypeNames import org.vorpal.research.kthelper.assert.unreachable diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt index 147d5e4b3..1fa1142e2 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/descriptor.kt @@ -861,7 +861,7 @@ class MockDescriptor(term: Term, type: KexClass, extraInterfaces: Set } override fun collectQuery(set: MutableSet): PredicateState { - TODO("Mock. Unimplemented") + TODO("Unimplemented") } override fun deepCopy(copied: MutableMap): Descriptor { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt index 1277c8bd6..08b33912c 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/descriptor/functionalExtensions.kt @@ -2,52 +2,51 @@ package org.vorpal.research.kex.descriptor fun Descriptor.transform( - mapped: MutableMap, - failureMappings: MutableSet = mutableSetOf(), + mapping: MutableMap, transformOrNull: (Descriptor) -> T? ): Descriptor { - if (mapped[this] != null) return mapped[this]!! - if (this in failureMappings) return this + if (this in mapping) return mapping[this] ?: this - val newDescriptor = transformOrNull(this) ?: return this.also { - failureMappings.add(this) - this.transformChildren(mapped, failureMappings, transformOrNull) + val newDescriptor = transformOrNull(this) + mapping[this] = newDescriptor + + if (newDescriptor == null) { + this.transformChildren(mapping, transformOrNull) + return this } - mapped[this] = newDescriptor - mapped[newDescriptor] = newDescriptor - newDescriptor.transformChildren(mapped, failureMappings, transformOrNull) + mapping[newDescriptor] = newDescriptor + newDescriptor.transformChildren(mapping, transformOrNull) return newDescriptor } private fun Descriptor.transformChildren( - mapped: MutableMap, - failedMappings: MutableSet, + mapped: MutableMap, transformOrNull: (Descriptor) -> T? ) { when (this) { is ConstantDescriptor -> Unit is ClassDescriptor -> fields.replaceAll { _, descriptor -> - descriptor.transform(mapped, failedMappings, transformOrNull) + descriptor.transform(mapped, transformOrNull) } is ObjectDescriptor -> fields.replaceAll { _, descriptor -> - descriptor.transform(mapped, failedMappings, transformOrNull) + descriptor.transform(mapped, transformOrNull) } is MockDescriptor -> { fields.replaceAll { _, descriptor -> - descriptor.transform(mapped, failedMappings, transformOrNull) + descriptor.transform(mapped, transformOrNull) } methodReturns.values.forEach { values -> values.replaceAll { descriptor -> - descriptor.transform(mapped, failedMappings, transformOrNull) + descriptor.transform(mapped, transformOrNull) } } } is ArrayDescriptor -> elements.replaceAll { _, descriptor -> - descriptor.transform(mapped, failedMappings, transformOrNull) + descriptor.transform(mapped, transformOrNull) } } } diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt index edf472f7e..fe3479c3a 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/MockMaker.kt @@ -103,7 +103,7 @@ fun composeMockMakers(vararg mockMakers: MockMaker): MockMaker = CompositeMockMa fun createMockMaker(rule: MockingRule, ctx: ExecutionContext): MockMaker = when (rule) { MockingRule.LAMBDA -> composeMockMakers( AllMockMaker(ctx).filter { descriptor -> - descriptor.kfgClass(ctx.types)?.getFunctionalInterfaces()?.isNotEmpty() ?: false + descriptor.kfgClass(ctx.types)?.getFunctionalInterfaces()?.isNotEmpty() == true }, LambdaMockMaker(ctx), ) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt index 3f3a5f95b..612a987fb 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mockUtils.kt @@ -7,8 +7,6 @@ import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kfg.type.TypeFactory import org.vorpal.research.kfg.type.objectType -import org.vorpal.research.kthelper.logging.log -import org.vorpal.research.kthelper.logging.warn enum class MockingRule { // Order is important! First rule applies first @@ -16,14 +14,6 @@ enum class MockingRule { } -fun Config.getMockMaker(ctx: ExecutionContext): MockMaker = - getMultipleStringValue("mock", "rule") - .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } - .sortedBy { rule -> rule.ordinal } - .map { rule -> createMockMaker(rule, ctx) } - .let { mockMakers -> composeMockMakers(mockMakers) } - - fun Class.canMock(): Boolean = !isFinal && !isPrivate && !isKexRt && !(!isPublic && pkg.concreteName.startsWith("java")) @@ -34,4 +24,39 @@ fun Method.canMock(types: TypeFactory): Boolean = when { isFinal || isPrivate -> false else -> true -} \ No newline at end of file +} + + +fun Config.getMockMaker(ctx: ExecutionContext): MockMaker = + getMultipleStringValue("mock", "rule") + .map { enumName -> MockingRule.valueOf(enumName.uppercase()) } + .sortedBy { rule -> rule.ordinal } + .map { rule -> createMockMaker(rule, ctx) } + .let { mockMakers -> composeMockMakers(mockMakers) } + +val Config.isMockitoClassesWorkaroundEnabled: Boolean + get() = getBooleanValue("mock", "mockitoClassesWorkaround", true) + +val Config.isMockitoJava8WorkaroundEnabled: Boolean + get() = getBooleanValue("mock", "java8WorkaroundEnabled", false) + +val Config.logTypeFix: Boolean + get() = getBooleanValue("mock", "logTypeFix", false) + +val Config.logStackTraceTypeFix: Boolean + get() = getBooleanValue("mock", "logStackTraceTypeFix", false) + +val Config.isExpectMocks: Boolean + get() = getBooleanValue("mock", "expectMocks", false) + +val Config.isEasyRandomExcludeLambdas: Boolean + get() = getBooleanValue("mock", "easyRandomExcludeLambdas", false) + +val Config.isZeroCoverageEpsilon: Boolean + get() = getBooleanValue("mock", "zeroCoverageEpsilon", false) + + +// debug purposes, normally should be false +@Suppress("unused") +val Config.isMockTest: Boolean + get() = getBooleanValue("mock", "test", false).also { if (it) println("Test feature invoked!") } \ No newline at end of file diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mocking.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mocking.kt index aff7ab9f1..0cc7dbcb2 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mocking.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/mocking/mocking.kt @@ -12,7 +12,6 @@ import org.vorpal.research.kex.state.predicate.CallPredicate import org.vorpal.research.kex.state.term.CallTerm import org.vorpal.research.kex.state.term.Term import org.vorpal.research.kex.trace.symbolic.SymbolicState -import org.vorpal.research.kex.util.isExpectMocks import org.vorpal.research.kex.util.isMockingEnabled import org.vorpal.research.kfg.ir.Class import org.vorpal.research.kfg.ir.Method @@ -33,13 +32,14 @@ fun createDescriptorToMock( mockMaker: MockMaker, expectedClasses: Map ): Map { - val descriptorToMock = mutableMapOf() + val descriptorToMock = mutableMapOf() allDescriptors.forEach { it.transform(descriptorToMock) { descriptor -> mockMaker.mockOrNull(descriptor, expectedClasses[descriptor]) } } - return descriptorToMock + + return descriptorToMock.filterValues { value -> value != null }.mapValues { (_, v) -> v!! } } fun setupMocks( @@ -101,7 +101,6 @@ fun NonMockedDescriptors.performMocking( return descriptors } - val descriptorToMock = createDescriptorToMock(allDescriptors, mockMaker, expectedClasses) val withMocks = descriptors.map { descriptor -> descriptorToMock[descriptor] ?: descriptor } val methodCalls = state.methodCalls() diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt index 1909d84ec..2880260d3 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/random/easyrandom/easyRandom.kt @@ -12,6 +12,7 @@ import org.objenesis.ObjenesisStd import org.reflections.Reflections import org.reflections.util.ConfigurationBuilder import org.vorpal.research.kex.config.kexConfig +import org.vorpal.research.kex.mocking.isEasyRandomExcludeLambdas import org.vorpal.research.kex.random.GenerationException import org.vorpal.research.kex.random.Randomizer import org.vorpal.research.kex.random.UnknownTypeException diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/HackedClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/ClassLoaderWithMockitoWorkaround.kt similarity index 71% rename from kex-core/src/main/kotlin/org/vorpal/research/kex/util/HackedClassLoader.kt rename to kex-core/src/main/kotlin/org/vorpal/research/kex/util/ClassLoaderWithMockitoWorkaround.kt index ecbb7ac23..3bb7518c5 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/HackedClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/ClassLoaderWithMockitoWorkaround.kt @@ -1,19 +1,20 @@ package org.vorpal.research.kex.util import org.vorpal.research.kex.config.kexConfig +import org.vorpal.research.kex.mocking.isMockitoJava8WorkaroundEnabled import org.vorpal.research.kthelper.logging.info import org.vorpal.research.kthelper.logging.warn import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.tryOrNull -sealed class HackedClassLoader(parent: ClassLoader = getSystemClassLoader()) : +sealed class ClassLoaderWithMockitoWorkaround(parent: ClassLoader = getSystemClassLoader()) : ClassLoader(parent) { init { - val isJava8 = tryOrNull { System.getProperty("java.version") }?.startsWith("1.8") == true + val isJava8 = getJvmVersion() == 8 if (kexConfig.isMockingEnabled && kexConfig.isMockitoJava8WorkaroundEnabled && isJava8) { log.info { "Applying workaround for mockito on java 8" } - if (applyJava8Workaround()) { + if (applyJava8MockitoWorkaround()) { log.info { "Workaround successfully applied" } } else { log.warn { "Workaround failed" } @@ -21,7 +22,7 @@ sealed class HackedClassLoader(parent: ClassLoader = getSystemClassLoader()) : } } - private fun applyJava8Workaround(): Boolean { + private fun applyJava8MockitoWorkaround(): Boolean { return tryOrNull { definePackage("org.mockito.codegen", "", "", "", "", "", "", null) } != null diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt index 79f444944..8a72d2902 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/KfgClassLoader.kt @@ -16,7 +16,7 @@ class KfgClassLoader( private val includes: Set = INCLUDES, private val excludes: Set = EXCLUDES, val transformation: (ConcreteClass) -> Unit = {} -) : HackedClassLoader() { +) : ClassLoaderWithMockitoWorkaround() { private val cache = hashMapOf>() val fallback = PathClassLoader(paths) diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt index e7baba928..fbb10e2b9 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/PathClassLoader.kt @@ -9,7 +9,7 @@ import kotlin.io.path.readBytes class PathClassLoader( val paths: List, parent: ClassLoader = PathClassLoader::class.java.classLoader -) : HackedClassLoader(parent) { +) : ClassLoaderWithMockitoWorkaround(parent) { private val cache = hashMapOf>() private fun readClassFromJar(name: String, path: Path): ByteArray? { diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt index 16231b6b7..696fbd401 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/kfg.kt @@ -5,6 +5,8 @@ package org.vorpal.research.kex.util import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.ktype.KexType import org.vorpal.research.kex.ktype.kexType +import org.vorpal.research.kex.mocking.logStackTraceTypeFix +import org.vorpal.research.kex.mocking.logTypeFix import org.vorpal.research.kfg.Package import org.vorpal.research.kfg.UnknownInstanceException import org.vorpal.research.kfg.ir.Class diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt index c6fc9f96e..2c43d365d 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/util/rt.kt @@ -66,42 +66,13 @@ fun getJavaPath(): Path = Paths.get(System.getProperty("java.home"), "bin", "jav val Config.isMockingEnabled: Boolean get() = getBooleanValue("mock", "enabled", false) -val Config.isMockitoClassesWorkaroundEnabled: Boolean - get() = getBooleanValue("mock", "mockitoClassesWorkaround", true) - -val Config.isMockitoJava8WorkaroundEnabled: Boolean - get() = getBooleanValue("mock", "java8WorkaroundEnabled", false) - -val Config.logTypeFix: Boolean - get() = getBooleanValue("mock", "logTypeFix", false) - -val Config.logStackTraceTypeFix: Boolean - get() = getBooleanValue("mock", "logStackTraceTypeFix", false) - -val Config.isExpectMocks: Boolean - get() = getBooleanValue("mock", "expectMocks", false) - -val Config.isFixConcreteLambdas: Boolean - get() = getBooleanValue("mock", "concreteLambdasPassEnabled", false) - -val Config.isEasyRandomExcludeLambdas: Boolean - get() = getBooleanValue("mock", "easyRandomExcludeLambdas", false) - -val Config.isZeroCoverageEpsilon: Boolean - get() = getBooleanValue("mock", "zeroCoverageEpsilon", false) - -val Config.mockito: Container? - get() { - val libPath = libPath ?: return null - val mockitoVersion = getStringValue("mock", "mockitoVersion") ?: return null - val mockitoPath = libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath() - return JarContainer(mockitoPath, Package("org.mockito")) - } - -// debug purposes, normally should be false -val Config.isMockTest: Boolean - get() = getBooleanValue("mock", "test", false).also { if (it) println("Test feature invoked!") } - +fun getMockito(): Container? { + val config = kexConfig + val libPath = config.libPath ?: return null + val mockitoVersion = config.getStringValue("mock", "mockitoVersion") ?: return null + val mockitoPath = libPath.resolve("mockito-core-$mockitoVersion.jar").toAbsolutePath() + return JarContainer(mockitoPath, Package("org.mockito")) +} fun getRuntime(): Container? { if (!kexConfig.getBooleanValue("kex", "useJavaRuntime", true)) return null diff --git a/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt b/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt index 26da869a6..11464fe98 100644 --- a/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt +++ b/kex-executor/src/main/kotlin/org/vorpal/research/kex/launcher/WorkerLauncher.kt @@ -77,7 +77,7 @@ class WorkerLauncher(args: Array) { kexConfig.compiledCodeDirectory, *getJunit().mapToArray { it.path }, getIntrinsics()?.path, - kexConfig.mockito?.path, + getMockito()?.path, ) ) { kfgClass -> val instrumenter = SymbolicTraceInstrumenter(classManager) diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/compile/CompilerHelper.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/compile/CompilerHelper.kt index 3091b71f0..8ec610456 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/compile/CompilerHelper.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/compile/CompilerHelper.kt @@ -4,7 +4,7 @@ import org.vorpal.research.kex.ExecutionContext import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.util.compiledCodeDirectory import org.vorpal.research.kex.util.getJunit -import org.vorpal.research.kex.util.mockito +import org.vorpal.research.kex.util.getMockito import org.vorpal.research.kex.util.testcaseDirectory import ru.spbstu.wheels.mapToArray import java.nio.file.Path @@ -20,7 +20,7 @@ class CompilerHelper(val ctx: ExecutionContext) { if (!enabled) return val compilerDriver = JavaCompilerDriver( - listOfNotNull(*ctx.classPath.toTypedArray(), *getJunit().mapToArray { it.path }, kexConfig.mockito?.path, testDirectory), compileDir + listOfNotNull(*ctx.classPath.toTypedArray(), *getJunit().mapToArray { it.path }, getMockito()?.path, testDirectory), compileDir ) compilerDriver.compile(listOf(file)) } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt index bc8d1114f..392026c9e 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/ActionSequence.kt @@ -188,12 +188,11 @@ class ReflectionList( } } -class MockSequence( +class MockList( name: String, - val mockCalls: MutableList, // create instance and setup methods - val reflectionCalls: MutableList // only setup fields -) : ActionSequence(name) { - constructor(name: String) : this(name, mutableListOf(), mutableListOf()) + internal val mockCalls: MutableList, +) : ActionSequence(name), Iterable by mockCalls { + constructor(name: String) : this(name, mutableListOf()) override fun print(): String { val builder = StringBuilder() @@ -207,12 +206,9 @@ class MockSequence( for (mockCall in mockCalls) { mockCall.print(this, builder, visited) } - for (reflectionCall in reflectionCalls) { - reflectionCall.print(this, builder, visited) - } } - override fun clone() = MockSequence(name, mockCalls.toMutableList(), reflectionCalls.toMutableList()) + override fun clone() = MockList(name, mockCalls.toMutableList()) } sealed interface MockCall { @@ -227,7 +223,11 @@ data class MockNewInstance(val klass: Class, val extraInterfaces: Set) : override fun toString() = "Mockito.mock(${klass.fullName}})" - override fun print(owner: ActionSequence, builder: StringBuilder, visited: MutableSet) { + override fun print( + owner: ActionSequence, + builder: StringBuilder, + visited: MutableSet + ) { builder.appendLine { "${owner.name} = $this" } } } @@ -238,11 +238,32 @@ data class MockSetupMethod( override val parameters: List get() = returnValues - override fun print(owner: ActionSequence, builder: StringBuilder, visited: MutableSet) { + override fun print( + owner: ActionSequence, + builder: StringBuilder, + visited: MutableSet + ) { builder.appendLine { "${owner.name}.$method returns $returnValues" } } } +data class MockSetField(val field: Field, val value: ActionSequence) : MockCall { + override val parameters: List get() = listOf(value) + + + override fun toString() = "setField(${field.name}, $value)" + + override fun print( + owner: ActionSequence, + builder: StringBuilder, + visited: MutableSet + ) { + value.print(builder, visited) + builder.appendLine("setField(${owner.name}, ${field.name}, $value") + } +} + + sealed interface CodeAction { val parameters: List @@ -595,19 +616,14 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { } } - is MockSequence -> when (ct) { + is MockList -> when (ct) { in cache -> cache[ct]!! else -> { - val res = MockSequence(ct.name.mapped) + val res = MockList(ct.name.mapped) cache[ct] = res for (mockCall in ct.mockCalls) { res.mockCalls += map(mockCall) } - for (reflectionCall in ct.reflectionCalls) { - res.reflectionCalls += map(reflectionCall) - } - - // TODO: Mock. Not sure if it works res } } @@ -761,7 +777,18 @@ class ActionSequenceRtMapper(private val mode: KexRtManager.Mode) { } fun map(api: MockCall): MockCall = when (api) { - is MockNewInstance -> MockNewInstance(api.klass.mapped, api.extraInterfaces.map{it.mapped}.toSet()) - is MockSetupMethod -> MockSetupMethod(api.method.mapped, api.returnValues.map { value -> map(value) }) + is MockNewInstance -> MockNewInstance( + api.klass.mapped, api.extraInterfaces.map { it.mapped }.toSet() + ) + + is MockSetupMethod -> MockSetupMethod( + api.method.mapped, api.returnValues.map { value -> map(value) } + ) + + is MockSetField -> { + val unmappedKlass = api.field.klass.mapped + val unmappedField = unmappedKlass.getField(api.field.name, api.field.type.mapped) + MockSetField(unmappedField, map(api.value)) + } } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt index fba798baf..861355d05 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/actionsequence/generator/MockGenerator.kt @@ -20,15 +20,15 @@ class MockGenerator(private val fallback: Generator) : Generator { with(context) { descriptor as? MockDescriptor ?: throw IllegalArgumentException("Expected MockDescriptor. Got: $descriptor") - descriptor as MockDescriptor // helps autocompletion val name = "${descriptor.term}" - val actionSequence = MockSequence(name) + val actionSequence = MockList(name) saveToCache(descriptor, actionSequence) val kfgClass = (descriptor.type.getKfgType(types) as ClassType).klass val extraInterfaces = descriptor.extraInterfaces .map { (it.getKfgType(types) as ClassType).klass } .toSet() + actionSequence.mockCalls.add(MockNewInstance(kfgClass, extraInterfaces)) for ((method, returnValuesDesc) in descriptor.methodReturns) { @@ -36,11 +36,8 @@ class MockGenerator(private val fallback: Generator) : Generator { actionSequence.mockCalls += MockSetupMethod(method, returnValues) } - actionSequence.reflectionCalls.addSetupFieldsCalls( - descriptor.fields, - kfgClass, - types, - fallback + actionSequence.mockCalls.addSetupFieldsCalls( + descriptor.fields, kfgClass, types, fallback ) getFromCache(descriptor)!! @@ -49,7 +46,7 @@ class MockGenerator(private val fallback: Generator) : Generator { } -fun MutableList.addSetupFieldsCalls( +private fun MutableList.addSetupFieldsCalls( fields: MutableMap, Descriptor>, kfgClass: Class, types: TypeFactory, @@ -64,6 +61,6 @@ fun MutableList.addSetupFieldsCalls( continue } val valueAS = fallback.generate(value) - this += ReflectionSetField(kfgField, valueAS) + this += MockSetField(kfgField, valueAS) } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt index d61cce46d..7b8599640 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ActionSequence2JavaPrinter.kt @@ -252,9 +252,8 @@ open class ActionSequence2JavaPrinter( actionSequence.args.forEach { resolveTypes(it, visited) } } - is MockSequence -> { + is MockList -> { actionSequence.mockCalls.reversed().map { resolveTypes(it, visited) } - actionSequence.reflectionCalls.reversed().map { resolveTypes(it, visited) } } else -> {} @@ -340,6 +339,7 @@ open class ActionSequence2JavaPrinter( private fun resolveTypes(mockCall: MockCall, visited: MutableSet): Unit = when (mockCall) { is MockNewInstance -> {} is MockSetupMethod -> mockCall.returnValues.forEach { value -> resolveTypes(value, visited) } + is MockSetField -> resolveTypes(mockCall.value, visited) } protected open fun ActionSequence.printAsJava() { @@ -358,7 +358,7 @@ open class ActionSequence2JavaPrinter( asConstant } - is MockSequence -> printMockSequence(this) + is MockList -> printMockList(this) } with(current) { for (statement in statements) @@ -372,7 +372,7 @@ open class ActionSequence2JavaPrinter( protected open fun printReflectionList(reflectionList: ReflectionList): List = reflectionList.flatMap { printReflectionCall(reflectionList, it) } - protected open fun printMockSequence(mockSequence: MockSequence): List = + protected open fun printMockList(mockSequence: MockList): List = unreachable { log.error("Mock calls are not supported in AS 2 Java printer") } @@ -446,9 +446,10 @@ open class ActionSequence2JavaPrinter( is ArrayClassConstantGetter -> printArrayClassConstantGetter(owner, codeAction) } - private fun printMockCall(owner: MockSequence, mockCall: MockCall): List = when (mockCall) { + private fun printMockCall(owner: MockList, mockCall: MockCall): List = when (mockCall) { is MockNewInstance -> printMockNewInstance(owner, mockCall) is MockSetupMethod -> printMockSetupMethod(owner, mockCall) + is MockSetField -> printMockSetField(owner, mockCall) } protected val PrimaryValue.asConstant: String @@ -907,6 +908,9 @@ open class ActionSequence2JavaPrinter( protected open fun printMockSetupMethod(owner: ActionSequence, call: MockSetupMethod): List = unreachable { log.error("Mock calls are not supported in AS 2 Java printer") } + protected open fun printMockSetField(owner: MockList, mockCall: MockSetField): List = + unreachable { log.error("Mock calls are not supported in AS 2 Java printer") } + protected open fun printUnknownSequence(sequence: UnknownSequence): List { val actualType = sequence.target.type.asType return listOf( @@ -921,5 +925,4 @@ open class ActionSequence2JavaPrinter( } ) } - } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt index c759e9585..02c91166f 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/javagen/ExecutorAS2JavaPrinter.kt @@ -108,7 +108,7 @@ class ExecutorAS2JavaPrinter( } } ?: unreachable { log.error("Unexpected call in arg") } - is MockSequence -> arg.mockCalls.firstNotNullOfOrNull { + is MockList -> arg.mockCalls.firstNotNullOfOrNull { when (it) { is MockNewInstance -> it.klass.asType else -> null @@ -263,7 +263,7 @@ class ExecutorAS2JavaPrinter( return res } - override fun printMockSequence(mockSequence: MockSequence): List { + override fun printMockList(mockSequence: MockList): List { val res = mutableListOf() printDeclarations(mockSequence, res) printInsides(mockSequence, res) @@ -273,14 +273,14 @@ class ExecutorAS2JavaPrinter( private fun printDeclarations(owner: ActionSequence, result: MutableList) { when (owner) { is ReflectionList -> printReflectionListDeclarations(owner, result) - is MockSequence -> printMockSequenceDeclarations(owner, result) + is MockList -> printMockSequenceDeclarations(owner, result) else -> { owner.printAsJava() } } } - private fun printMockSequenceDeclarations(owner: MockSequence, result: MutableList) { + private fun printMockSequenceDeclarations(owner: MockList, result: MutableList) { if (owner.name in printedDeclarations) return printedDeclarations += owner.name @@ -288,16 +288,11 @@ class ExecutorAS2JavaPrinter( when (mockCall) { is MockNewInstance -> result += printMockNewInstance(owner, mockCall) is MockSetupMethod -> mockCall.returnValues.forEach { - printDeclarations( - it, - result - ) + printDeclarations(it,result) } + is MockSetField -> printDeclarations(mockCall.value, result) } } - for (reflectionCall in owner.reflectionCalls) { - printReflectionCallDeclarations(reflectionCall, result, owner) - } } private fun printReflectionListDeclarations( @@ -332,14 +327,14 @@ class ExecutorAS2JavaPrinter( private fun printInsides(owner: ActionSequence, result: MutableList): Unit = when (owner) { is ReflectionList -> printReflectionListInsides(owner, result) - is MockSequence -> printMockSequenceInsides(owner, result) + is MockList -> printMockSequenceInsides(owner, result) else -> { owner.printAsJava() } } private fun printMockSequenceInsides( - owner: MockSequence, + owner: MockList, result: MutableList ) { if (owner.name in printedInsides) return @@ -353,11 +348,12 @@ class ExecutorAS2JavaPrinter( } is MockNewInstance -> {} + is MockSetField -> { + printInsides(mockCall.value, result) + result += printMockSetField(owner, mockCall) + } } } - for (reflectionCall in owner.reflectionCalls) { - printReflectionCallInsides(reflectionCall, result, owner) - } } private fun printReflectionListInsides( @@ -616,4 +612,11 @@ class ExecutorAS2JavaPrinter( val instance = "(${owner.cast(call.method.klass.asType.asType)})" return listOf("Mockito.when($instance.$methodName($anys)).thenReturn($returns)") } + + override fun printMockSetField(owner: MockList, mockCall: MockSetField): List { + val setFieldMethod = + mockCall.field.type.kexType.primitiveName?.let { reflectionUtils.setPrimitiveFieldMap[it]!! } + ?: reflectionUtils.setField + return listOf("${setFieldMethod.name}(${owner.name}, ${owner.name}.getClass(), \"${mockCall.field.name}\", ${mockCall.value.stackName})") + } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/kotlingen/ActionSequence2KotlinPrinter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/kotlingen/ActionSequence2KotlinPrinter.kt index 5aa0ecfb8..9d483ecc6 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/kotlingen/ActionSequence2KotlinPrinter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/reanimator/codegen/kotlingen/ActionSequence2KotlinPrinter.kt @@ -337,7 +337,7 @@ open class ActionSequence2KotlinPrinter( asConstant } - is MockSequence -> TODO("Mock") + is MockList -> TODO("Unimplemented") } with(current) { for (statement in statements) diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt index 345fba146..96cfc901b 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/MockConcolicLongTest.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import org.junit.Test import org.vorpal.research.kex.config.kexConfig -import org.vorpal.research.kex.util.isZeroCoverageEpsilon +import org.vorpal.research.kex.mocking.isZeroCoverageEpsilon import kotlin.time.ExperimentalTime @ExperimentalTime From 74ebec695dfe294bdad905af0bf6ae1683513d28 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Thu, 25 Apr 2024 12:23:23 +0200 Subject: [PATCH 128/128] comment --- kex.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/kex.ini b/kex.ini index b64b80cc9..b6c00e46a 100644 --- a/kex.ini +++ b/kex.ini @@ -34,6 +34,7 @@ ignoreStatic = package java.time.* ignoreStatic = package org.slf4j.* ; classes won't appear in instantiationManager +; external dependencies ignoreInstantiation = package org.junit.* ignoreInstantiation = package org.objenesis.* ignoreInstantiation = package net.bytebuddy.*