Skip to content

Commit 410613a

Browse files
committed
Make UtEnumConstModel and UtClassRefModel reference models #414
Historically `UtEnumConstModel` and `UtClassRefModel` have been processed not as other reference models, but in a special way, more like to primitive types. This approach leads to several problems, especially to class cast errors when processing generic collections with enums or class references as elements. This commit makes `UtEnumConstModel` and `UtClassRefModel` subtypes of `UtReferenceModel`. * Concrete executor is modified to respect the identity of static fields to avoid rewriting enum values and `Class<?>` instances. * Special processing for enums is implemented. When a new enum value is created, or an `Object` is being cast to the enum type, static values for the enum class are initialized, and the set of hard constraint is added to require that the new instance has the same address and ordinal as any one of enum constants to implement reference equality for enums. * Corresponding changes in fuzzer model providers have been implemented. * UtModelVisitor has been updated to reflect the new inheritance hierarchy.
1 parent 45e687e commit 410613a

File tree

22 files changed

+458
-71
lines changed

22 files changed

+458
-71
lines changed

utbot-framework-api/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ dependencies {
1414
implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version
1515
}
1616

17+
compileKotlin {
18+
kotlinOptions {
19+
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
20+
}
21+
}
22+
1723
shadowJar {
1824
configurations = [project.configurations.compileClasspath]
1925
archiveClassifier.set('')

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import soot.jimple.JimpleBody
5050
import soot.jimple.Stmt
5151
import java.io.File
5252
import java.lang.reflect.Modifier
53+
import kotlin.contracts.ExperimentalContracts
54+
import kotlin.contracts.contract
5355
import kotlin.jvm.internal.CallableReference
5456
import kotlin.reflect.KCallable
5557
import kotlin.reflect.KClass
@@ -58,6 +60,8 @@ import kotlin.reflect.full.instanceParameter
5860
import kotlin.reflect.jvm.javaConstructor
5961
import kotlin.reflect.jvm.javaType
6062

63+
const val SYMBOLIC_NULL_ADDR: Int = 0
64+
6165
data class UtMethod<R>(
6266
val callable: KCallable<R>,
6367
val clazz: KClass<*>
@@ -282,6 +286,27 @@ fun UtModel.hasDefaultValue() =
282286
*/
283287
fun UtModel.isMockModel() = this is UtCompositeModel && isMock
284288

289+
/**
290+
* Get model id (symbolic null value for UtNullModel)
291+
* or null if model has no id (e.g., a primitive model) or the id is null.
292+
*/
293+
fun UtModel.idOrNull(): Int? = when (this) {
294+
is UtNullModel -> SYMBOLIC_NULL_ADDR
295+
is UtReferenceModel -> id
296+
else -> null
297+
}
298+
299+
/**
300+
* Returns the model id if it is available, or throws an [IllegalStateException].
301+
*/
302+
@OptIn(ExperimentalContracts::class)
303+
fun UtModel?.getIdOrThrow(): Int {
304+
contract {
305+
returns() implies (this@getIdOrThrow != null)
306+
}
307+
return this?.idOrNull() ?: throw IllegalStateException("Model id must not be null: $this")
308+
}
309+
285310
/**
286311
* Model for nulls.
287312
*/
@@ -323,20 +348,24 @@ object UtVoidModel : UtModel(voidClassId)
323348
* Model for enum constant
324349
*/
325350
data class UtEnumConstantModel(
351+
override val id: Int?,
326352
override val classId: ClassId,
327353
val value: Enum<*>
328-
) : UtModel(classId) {
329-
override fun toString(): String = "$value"
354+
) : UtReferenceModel(id, classId) {
355+
// Model id is included for debugging purposes
356+
override fun toString(): String = "$value@$id"
330357
}
331358

332359
/**
333360
* Model for class reference
334361
*/
335362
data class UtClassRefModel(
363+
override val id: Int?,
336364
override val classId: ClassId,
337365
val value: Class<*>
338-
) : UtModel(classId) {
339-
override fun toString(): String = "$value"
366+
) : UtReferenceModel(id, classId) {
367+
// Model id is included for debugging purposes
368+
override fun toString(): String = "$value@$id"
340369
}
341370

342371
/**

utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import org.utbot.framework.plugin.api.UtCompositeModel
2424
import org.utbot.framework.plugin.api.UtModel
2525
import org.utbot.framework.plugin.api.UtNullModel
2626
import org.utbot.framework.plugin.api.UtPrimitiveModel
27-
import org.utbot.framework.plugin.api.UtReferenceModel
27+
import org.utbot.framework.plugin.api.getIdOrThrow
28+
import org.utbot.framework.plugin.api.idOrNull
2829
import org.utbot.framework.plugin.api.util.id
2930
import org.utbot.framework.plugin.api.util.objectArrayClassId
3031
import org.utbot.framework.plugin.api.util.objectClassId
@@ -398,7 +399,7 @@ class AssociativeArrayWrapper : WrapperInterface {
398399
UtNullModel(objectClassId),
399400
stores = (0 until sizeValue).associateTo(mutableMapOf()) { i ->
400401
val model = touchedValues.stores[i]
401-
val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id!!
402+
val addr = model.getIdOrThrow()
402403
addr to resolver.resolveModel(
403404
ObjectValue(
404405
TypeStorage(OBJECT_TYPE),

utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import org.utbot.framework.plugin.api.UtCompositeModel
2222
import org.utbot.framework.plugin.api.UtExecutableCallModel
2323
import org.utbot.framework.plugin.api.UtModel
2424
import org.utbot.framework.plugin.api.UtNullModel
25-
import org.utbot.framework.plugin.api.UtReferenceModel
2625
import org.utbot.framework.plugin.api.UtStatementModel
2726
import org.utbot.framework.plugin.api.classId
27+
import org.utbot.framework.plugin.api.getIdOrThrow
2828
import org.utbot.framework.util.graph
2929
import org.utbot.framework.plugin.api.id
3030
import org.utbot.framework.plugin.api.util.booleanClassId
@@ -373,7 +373,7 @@ private fun constructKeysAndValues(keysModel: UtModel, valuesModel: UtModel, siz
373373
keysModel is UtArrayModel && valuesModel is UtArrayModel -> {
374374
List(size) {
375375
keysModel.stores[it].let { model ->
376-
val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id
376+
val addr = model.getIdOrThrow()
377377
// as we do not support generics for now, valuesModel.classId.elementClassId is unknown,
378378
// but it can be known with generics support
379379
val defaultValue = UtNullModel(valuesModel.classId.elementClassId ?: objectClassId)

utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import kotlinx.collections.immutable.persistentListOf
5656
import kotlinx.collections.immutable.persistentSetOf
5757
import kotlinx.collections.immutable.toPersistentList
5858
import kotlinx.collections.immutable.toPersistentMap
59+
import org.utbot.framework.plugin.api.classId
5960
import soot.ArrayType
6061
import soot.BooleanType
6162
import soot.ByteType
@@ -147,7 +148,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s
147148
private val speculativelyNotNullAddresses: UtArrayExpressionBase = UtConstArrayExpression(
148149
UtFalse,
149150
UtArraySort(UtAddrSort, UtBoolSort)
150-
)
151+
),
152+
private val symbolicEnumValues: PersistentList<ObjectValue> = persistentListOf()
151153
) {
152154
val chunkIds: Set<ChunkId>
153155
get() = initial.keys
@@ -297,7 +299,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s
297299
visitedValues = updVisitedValues,
298300
touchedAddresses = updTouchedAddresses,
299301
instanceFieldReadOperations = instanceFieldReadOperations.addAll(update.instanceFieldReads),
300-
speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses
302+
speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses,
303+
symbolicEnumValues = symbolicEnumValues.addAll(update.symbolicEnumValues)
301304
)
302305
}
303306

@@ -307,7 +310,6 @@ data class Memory( // TODO: split purely symbolic memory and information about s
307310
*/
308311
fun memoryForNestedMethod(): Memory = this.copy(updates = MemoryUpdate())
309312

310-
311313
/**
312314
* Returns copy of queued [updates] which consists only of updates of static fields.
313315
* This is necessary for substituting unbounded symbolic variables into the static fields.
@@ -350,6 +352,9 @@ data class Memory( // TODO: split purely symbolic memory and information about s
350352
fun findStaticInstanceOrNull(id: ClassId): ObjectValue? = staticInstanceStorage[id]
351353

352354
fun findTypeForArrayOrNull(addr: UtAddrExpression): ArrayType? = addrToArrayType[addr]
355+
356+
fun getSymbolicEnumValues(classId: ClassId): List<ObjectValue> =
357+
symbolicEnumValues.filter { it.type.classId == classId }
353358
}
354359

355360
/**
@@ -967,7 +972,8 @@ data class MemoryUpdate(
967972
val touchedAddresses: PersistentList<UtAddrExpression> = persistentListOf(),
968973
val classIdToClearStatics: ClassId? = null,
969974
val instanceFieldReads: PersistentSet<InstanceFieldReadOperation> = persistentHashSetOf(),
970-
val speculativelyNotNullAddresses: PersistentList<UtAddrExpression> = persistentListOf()
975+
val speculativelyNotNullAddresses: PersistentList<UtAddrExpression> = persistentListOf(),
976+
val symbolicEnumValues: PersistentList<ObjectValue> = persistentListOf()
971977
) {
972978
operator fun plus(other: MemoryUpdate) =
973979
this.copy(
@@ -986,7 +992,11 @@ data class MemoryUpdate(
986992
classIdToClearStatics = other.classIdToClearStatics,
987993
instanceFieldReads = instanceFieldReads.addAll(other.instanceFieldReads),
988994
speculativelyNotNullAddresses = speculativelyNotNullAddresses.addAll(other.speculativelyNotNullAddresses),
995+
symbolicEnumValues = symbolicEnumValues.addAll(other.symbolicEnumValues),
989996
)
997+
998+
fun getSymbolicEnumValues(classId: ClassId): List<ObjectValue> =
999+
symbolicEnumValues.filter { it.type.classId == classId }
9901000
}
9911001

9921002
// array - Java Array

utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import kotlin.math.max
7171
import kotlin.math.min
7272
import kotlinx.collections.immutable.persistentListOf
7373
import kotlinx.collections.immutable.persistentSetOf
74+
import org.utbot.framework.plugin.api.SYMBOLIC_NULL_ADDR
7475
import soot.ArrayType
7576
import soot.BooleanType
7677
import soot.ByteType
@@ -325,7 +326,7 @@ class Resolver(
325326
val mockInfoEnriched = mockInfos.getValue(concreteAddr)
326327
val mockInfo = mockInfoEnriched.mockInfo
327328

328-
if (concreteAddr == NULL_ADDR) {
329+
if (concreteAddr == SYMBOLIC_NULL_ADDR) {
329330
return UtNullModel(mockInfo.classId)
330331
}
331332

@@ -437,7 +438,7 @@ class Resolver(
437438

438439
private fun resolveObject(objectValue: ObjectValue): UtModel {
439440
val concreteAddr = holder.concreteAddr(objectValue.addr)
440-
if (concreteAddr == NULL_ADDR) {
441+
if (concreteAddr == SYMBOLIC_NULL_ADDR) {
441442
return UtNullModel(objectValue.type.sootClass.id)
442443
}
443444

@@ -498,7 +499,7 @@ class Resolver(
498499
actualType: RefType,
499500
): UtModel {
500501
val concreteAddr = holder.concreteAddr(addr)
501-
if (concreteAddr == NULL_ADDR) {
502+
if (concreteAddr == SYMBOLIC_NULL_ADDR) {
502503
return UtNullModel(defaultType.sootClass.id)
503504
}
504505

@@ -615,7 +616,7 @@ class Resolver(
615616
val modeledNumDimensions = holder.eval(numDimensionsArray.select(addrExpression)).intValue()
616617

617618
val classRef = classRefByName(modeledType, modeledNumDimensions)
618-
val model = UtClassRefModel(CLASS_REF_CLASS_ID, classRef)
619+
val model = UtClassRefModel(addr, CLASS_REF_CLASS_ID, classRef)
619620
addConstructedModel(addr, model)
620621

621622
return model
@@ -640,7 +641,7 @@ class Resolver(
640641
clazz.enumConstants.indices.random()
641642
}
642643
val value = clazz.enumConstants[index] as Enum<*>
643-
val model = UtEnumConstantModel(clazz.id, value)
644+
val model = UtEnumConstantModel(addr, clazz.id, value)
644645
addConstructedModel(addr, model)
645646

646647
return model
@@ -795,7 +796,7 @@ class Resolver(
795796
*/
796797
private fun constructArrayModel(instance: ArrayValue): UtModel {
797798
val concreteAddr = holder.concreteAddr(instance.addr)
798-
if (concreteAddr == NULL_ADDR) {
799+
if (concreteAddr == SYMBOLIC_NULL_ADDR) {
799800
return UtNullModel(instance.type.id)
800801
}
801802

@@ -829,7 +830,7 @@ class Resolver(
829830
concreteAddr: Address,
830831
details: ArrayExtractionDetails,
831832
): UtModel {
832-
if (concreteAddr == NULL_ADDR) {
833+
if (concreteAddr == SYMBOLIC_NULL_ADDR) {
833834
return UtNullModel(actualType.id)
834835
}
835836

@@ -903,7 +904,7 @@ class Resolver(
903904
elementType: ArrayType,
904905
details: ArrayExtractionDetails
905906
): UtModel {
906-
if (addr == NULL_ADDR) {
907+
if (addr == SYMBOLIC_NULL_ADDR) {
907908
return UtNullModel(elementType.id)
908909
}
909910

@@ -927,7 +928,7 @@ class Resolver(
927928
* Uses [constructTypeOrNull] to evaluate possible element type.
928929
*/
929930
private fun arrayOfObjectsElementModel(concreteAddr: Address, defaultType: RefType): UtModel {
930-
if (concreteAddr == NULL_ADDR) {
931+
if (concreteAddr == SYMBOLIC_NULL_ADDR) {
931932
return UtNullModel(defaultType.id)
932933
}
933934

@@ -976,8 +977,7 @@ private data class ArrayExtractionDetails(
976977
val oneDimensionalArray: UtArrayExpressionBase
977978
)
978979

979-
private const val NULL_ADDR = 0
980-
internal val nullObjectAddr = UtAddrExpression(mkInt(NULL_ADDR))
980+
internal val nullObjectAddr = UtAddrExpression(mkInt(SYMBOLIC_NULL_ADDR))
981981

982982

983983
fun SymbolicValue.isNullObject() =

0 commit comments

Comments
 (0)