Skip to content

Commit 06e56ad

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.
1 parent 64e8170 commit 06e56ad

File tree

21 files changed

+463
-54
lines changed

21 files changed

+463
-54
lines changed

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

+57-4
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,15 @@ fun UtModel.hasDefaultValue() =
266266
*/
267267
fun UtModel.isMockModel() = this is UtCompositeModel && isMock
268268

269+
/**
270+
* Get model id or null if id is null or the model has no id.
271+
*/
272+
fun UtModel.idOrNull(): Int? = when (this) {
273+
is UtNullModel -> 0
274+
is UtReferenceModel -> id
275+
else -> null
276+
}
277+
269278
/**
270279
* Model for nulls.
271280
*/
@@ -307,20 +316,64 @@ object UtVoidModel : UtModel(voidClassId)
307316
* Model for enum constant
308317
*/
309318
data class UtEnumConstantModel(
319+
override val id: Int?,
310320
override val classId: ClassId,
311321
val value: Enum<*>
312-
) : UtModel(classId) {
313-
override fun toString(): String = "$value"
322+
) : UtReferenceModel(id, classId) {
323+
override fun toString(): String = "$value@$id"
324+
325+
override fun equals(other: Any?): Boolean {
326+
if (this === other) return true
327+
if (javaClass != other?.javaClass) return false
328+
329+
other as UtEnumConstantModel
330+
331+
if (id != other.id) return false
332+
if (classId != other.classId) return false
333+
if (value != other.value) return false
334+
335+
return true
336+
}
337+
338+
339+
override fun hashCode(): Int {
340+
var result = id ?: 0
341+
result = 31 * result + classId.hashCode()
342+
result = 31 * result + value.hashCode()
343+
return result
344+
}
345+
314346
}
315347

316348
/**
317349
* Model for class reference
318350
*/
319351
data class UtClassRefModel(
352+
override val id: Int?,
320353
override val classId: ClassId,
321354
val value: Class<*>
322-
) : UtModel(classId) {
323-
override fun toString(): String = "$value"
355+
) : UtReferenceModel(id, classId) {
356+
override fun toString(): String = "$value:$id"
357+
358+
override fun equals(other: Any?): Boolean {
359+
if (this === other) return true
360+
if (javaClass != other?.javaClass) return false
361+
362+
other as UtClassRefModel
363+
364+
if (id != other.id) return false
365+
if (classId != other.classId) return false
366+
if (value != other.value) return false
367+
368+
return true
369+
}
370+
371+
override fun hashCode(): Int {
372+
var result = id ?: 0
373+
result = 31 * result + classId.hashCode()
374+
result = 31 * result + value.hashCode()
375+
return result
376+
}
324377
}
325378

326379
/**

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ 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.idOrNull
2828
import org.utbot.framework.plugin.api.util.id
2929
import org.utbot.framework.plugin.api.util.objectArrayClassId
3030
import org.utbot.framework.plugin.api.util.objectClassId
@@ -398,7 +398,7 @@ class AssociativeArrayWrapper : WrapperInterface {
398398
UtNullModel(objectClassId),
399399
stores = (0 until sizeValue).associateTo(mutableMapOf()) { i ->
400400
val model = touchedValues.stores[i]
401-
val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id!!
401+
val addr = model?.idOrNull() ?: throw IllegalStateException("Unsupported model: $model")
402402
addr to resolver.resolveModel(
403403
ObjectValue(
404404
TypeStorage(OBJECT_TYPE),

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.utbot.framework.plugin.api.UtStatementModel
2727
import org.utbot.framework.plugin.api.classId
2828
import org.utbot.framework.util.graph
2929
import org.utbot.framework.plugin.api.id
30+
import org.utbot.framework.plugin.api.idOrNull
3031
import org.utbot.framework.plugin.api.util.booleanClassId
3132
import org.utbot.framework.plugin.api.util.constructorId
3233
import org.utbot.framework.plugin.api.util.id
@@ -373,7 +374,7 @@ private fun constructKeysAndValues(keysModel: UtModel, valuesModel: UtModel, siz
373374
keysModel is UtArrayModel && valuesModel is UtArrayModel -> {
374375
List(size) {
375376
keysModel.stores[it].let { model ->
376-
val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id
377+
val addr = model?.idOrNull() ?: throw IllegalStateException("Unsupported model: $model")
377378
// as we do not support generics for now, valuesModel.classId.elementClassId is unknown,
378379
// but it can be known with generics support
379380
val defaultValue = UtNullModel(valuesModel.classId.elementClassId ?: objectClassId)

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

+1
Original file line numberDiff line numberDiff line change
@@ -484,3 +484,4 @@ val SootMethod.isUtMockAssumeOrExecuteConcretely
484484
*/
485485
val SootMethod.isPreconditionCheckMethod
486486
get() = declaringClass.isOverridden && name == "preconditionCheck"
487+

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

+15-4
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ import kotlinx.collections.immutable.mutate
5353
import kotlinx.collections.immutable.persistentHashMapOf
5454
import kotlinx.collections.immutable.persistentHashSetOf
5555
import kotlinx.collections.immutable.persistentListOf
56+
import kotlinx.collections.immutable.persistentMapOf
5657
import kotlinx.collections.immutable.persistentSetOf
5758
import kotlinx.collections.immutable.toPersistentList
5859
import kotlinx.collections.immutable.toPersistentMap
60+
import org.utbot.framework.plugin.api.classId
5961
import soot.ArrayType
6062
import soot.BooleanType
6163
import soot.ByteType
@@ -147,7 +149,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s
147149
private val speculativelyNotNullAddresses: UtArrayExpressionBase = UtConstArrayExpression(
148150
UtFalse,
149151
UtArraySort(UtAddrSort, UtBoolSort)
150-
)
152+
),
153+
private val symbolicEnumValues: PersistentList<ObjectValue> = persistentListOf()
151154
) {
152155
val chunkIds: Set<ChunkId>
153156
get() = initial.keys
@@ -297,7 +300,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s
297300
visitedValues = updVisitedValues,
298301
touchedAddresses = updTouchedAddresses,
299302
instanceFieldReadOperations = instanceFieldReadOperations.addAll(update.instanceFieldReads),
300-
speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses
303+
speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses,
304+
symbolicEnumValues = symbolicEnumValues.addAll(update.symbolicEnumValues)
301305
)
302306
}
303307

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

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

352355
fun findTypeForArrayOrNull(addr: UtAddrExpression): ArrayType? = addrToArrayType[addr]
356+
357+
fun getSymbolicEnumValues(classId: ClassId): Collection<ObjectValue> =
358+
symbolicEnumValues.filter { it.type.classId == classId }
353359
}
354360

355361
/**
@@ -967,7 +973,8 @@ data class MemoryUpdate(
967973
val touchedAddresses: PersistentList<UtAddrExpression> = persistentListOf(),
968974
val classIdToClearStatics: ClassId? = null,
969975
val instanceFieldReads: PersistentSet<InstanceFieldReadOperation> = persistentHashSetOf(),
970-
val speculativelyNotNullAddresses: PersistentList<UtAddrExpression> = persistentListOf()
976+
val speculativelyNotNullAddresses: PersistentList<UtAddrExpression> = persistentListOf(),
977+
val symbolicEnumValues: PersistentList<ObjectValue> = persistentListOf()
971978
) {
972979
operator fun plus(other: MemoryUpdate) =
973980
this.copy(
@@ -986,7 +993,11 @@ data class MemoryUpdate(
986993
classIdToClearStatics = other.classIdToClearStatics,
987994
instanceFieldReads = instanceFieldReads.addAll(other.instanceFieldReads),
988995
speculativelyNotNullAddresses = speculativelyNotNullAddresses.addAll(other.speculativelyNotNullAddresses),
996+
symbolicEnumValues = symbolicEnumValues.addAll(other.symbolicEnumValues),
989997
)
998+
999+
fun getSymbolicEnumValues(classId: ClassId): Collection<ObjectValue> =
1000+
symbolicEnumValues.filter { it.type.classId == classId }
9901001
}
9911002

9921003
// array - Java Array

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ class Resolver(
615615
val modeledNumDimensions = holder.eval(numDimensionsArray.select(addrExpression)).intValue()
616616

617617
val classRef = classRefByName(modeledType, modeledNumDimensions)
618-
val model = UtClassRefModel(CLASS_REF_CLASS_ID, classRef)
618+
val model = UtClassRefModel(addr, CLASS_REF_CLASS_ID, classRef)
619619
addConstructedModel(addr, model)
620620

621621
return model
@@ -640,7 +640,7 @@ class Resolver(
640640
clazz.enumConstants.indices.random()
641641
}
642642
val value = clazz.enumConstants[index] as Enum<*>
643-
val model = UtEnumConstantModel(clazz.id, value)
643+
val model = UtEnumConstantModel(addr, clazz.id, value)
644644
addConstructedModel(addr, model)
645645

646646
return model

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

+62-16
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ class Traverser(
497497
val declaringClass = field.declaringClass
498498

499499
val updates = if (declaringClass.isEnum) {
500-
makeConcreteUpdatesForEnums(fieldId, declaringClass, stmt)
500+
makeConcreteUpdatesForEnumsWithStmt(fieldId, declaringClass, stmt)
501501
} else {
502502
makeConcreteUpdatesForNonEnumStaticField(field, fieldId, declaringClass, stmt)
503503
}
@@ -518,13 +518,10 @@ class Traverser(
518518
return true
519519
}
520520

521-
@Suppress("UnnecessaryVariable")
522-
private fun makeConcreteUpdatesForEnums(
523-
fieldId: FieldId,
524-
declaringClass: SootClass,
525-
stmt: Stmt
526-
): SymbolicStateUpdate {
527-
val type = declaringClass.type
521+
private fun makeConcreteUpdatesForEnum(
522+
type: RefType,
523+
fieldId: FieldId? = null
524+
): Pair<SymbolicStateUpdate, SymbolicValue?> {
528525
val jClass = type.id.jClass
529526

530527
// symbolic value for enum class itself
@@ -545,7 +542,7 @@ class Traverser(
545542

546543
val (staticFieldUpdates, curFieldSymbolicValueForLocalVariable) = makeEnumStaticFieldsUpdates(
547544
staticFields,
548-
declaringClass,
545+
type.sootClass,
549546
enumConstantSymbolicResultsByName,
550547
enumConstantSymbolicValues,
551548
enumClassValue,
@@ -564,14 +561,25 @@ class Traverser(
564561

565562
val initializedStaticFieldsMemoryUpdate = MemoryUpdate(
566563
initializedStaticFields = staticFields.associate { it.first.fieldId to it.second.single() }.toPersistentMap(),
567-
meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet()
564+
meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet(),
565+
symbolicEnumValues = enumConstantSymbolicValues.toPersistentList()
568566
)
569567

570-
val allUpdates = staticFieldUpdates +
571-
nonStaticFieldsUpdates +
572-
initializedStaticFieldsMemoryUpdate +
573-
createConcreteLocalValueUpdate(stmt, curFieldSymbolicValueForLocalVariable)
568+
return Pair(
569+
staticFieldUpdates + nonStaticFieldsUpdates + initializedStaticFieldsMemoryUpdate,
570+
curFieldSymbolicValueForLocalVariable
571+
)
572+
}
574573

574+
@Suppress("UnnecessaryVariable")
575+
private fun makeConcreteUpdatesForEnumsWithStmt(
576+
fieldId: FieldId,
577+
declaringClass: SootClass,
578+
stmt: Stmt
579+
): SymbolicStateUpdate {
580+
val (enumUpdates, curFieldSymbolicValueForLocalVariable) =
581+
makeConcreteUpdatesForEnum(declaringClass.type, fieldId)
582+
val allUpdates = enumUpdates + createConcreteLocalValueUpdate(stmt, curFieldSymbolicValueForLocalVariable)
575583
return allUpdates
576584
}
577585

@@ -1373,6 +1381,39 @@ class Traverser(
13731381
queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = MemoryUpdate())
13741382
}
13751383

1384+
/**
1385+
* Return a symbolic value of the ordinal corresponding to the enum value with the given address.
1386+
*/
1387+
private fun findEnumOrdinal(type: RefType, addr: UtAddrExpression): PrimitiveValue {
1388+
val array = memory.findArray(MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v()))
1389+
return array.select(addr).toIntValue()
1390+
}
1391+
1392+
/**
1393+
* Initialize enum class: create symbolic values for static enum values and generate constraints
1394+
* that restrict the new instance to match one of enum values.
1395+
*/
1396+
private fun initEnum(type: RefType, addr: UtAddrExpression, ordinal: PrimitiveValue) {
1397+
val classId = type.id
1398+
var predefinedEnumValues = memory.getSymbolicEnumValues(classId)
1399+
if (predefinedEnumValues.isEmpty()) {
1400+
val (enumValuesUpdate, _) = makeConcreteUpdatesForEnum(type)
1401+
queuedSymbolicStateUpdates += enumValuesUpdate
1402+
predefinedEnumValues = enumValuesUpdate.memoryUpdates.getSymbolicEnumValues(classId)
1403+
}
1404+
1405+
val enumValueConstraints = mkOr(
1406+
listOf(addrEq(addr, nullObjectAddr)) + predefinedEnumValues.map {
1407+
mkAnd(
1408+
addrEq(addr, it.addr),
1409+
mkEq(ordinal, findEnumOrdinal(it.type, it.addr))
1410+
)
1411+
}
1412+
)
1413+
1414+
queuedSymbolicStateUpdates += mkOr(enumValueConstraints).asHardConstraint()
1415+
}
1416+
13761417
private fun arrayInstanceOf(value: ArrayValue, checkType: Type): PrimitiveValue {
13771418
val notNullConstraint = mkNot(addrEq(value.addr, nullObjectAddr))
13781419

@@ -1530,6 +1571,11 @@ class Traverser(
15301571
queuedSymbolicStateUpdates += typeConstraint.asHardConstraint()
15311572
queuedSymbolicStateUpdates += typeRegistry.zeroDimensionConstraint(objectValue.addr).asHardConstraint()
15321573

1574+
// If we are casting to an enum class, we should initialize enum values and add value equality constraints
1575+
if (typeAfterCast.sootClass?.isEnum == true) {
1576+
initEnum(typeAfterCast, castedObject.addr, findEnumOrdinal(typeAfterCast, castedObject.addr))
1577+
}
1578+
15331579
// TODO add memory constraints JIRA:1523
15341580
return castedObject
15351581
}
@@ -1978,13 +2024,13 @@ class Traverser(
19782024

19792025
queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint()
19802026

1981-
val array = memory.findArray(MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v()))
1982-
val ordinal = array.select(addr).toIntValue()
2027+
val ordinal = findEnumOrdinal(type, addr)
19832028
val enumSize = classLoader.loadClass(type.sootClass.name).enumConstants.size
19842029

19852030
queuedSymbolicStateUpdates += mkOr(Ge(ordinal, 0), addrEq(addr, nullObjectAddr)).asHardConstraint()
19862031
queuedSymbolicStateUpdates += mkOr(Lt(ordinal, enumSize), addrEq(addr, nullObjectAddr)).asHardConstraint()
19872032

2033+
initEnum(type, addr, ordinal)
19882034
touchAddress(addr)
19892035

19902036
return ObjectValue(typeStorage, addr)

utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicStateUpdate.kt

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import org.utbot.engine.MemoryUpdate
55
import org.utbot.engine.pc.UtBoolExpression
66
import kotlinx.collections.immutable.PersistentSet
77
import kotlinx.collections.immutable.toPersistentSet
8+
import org.utbot.engine.StaticFieldMemoryUpdateInfo
9+
import org.utbot.engine.UtNamedStore
10+
import org.utbot.framework.plugin.api.ClassId
11+
import org.utbot.framework.plugin.api.FieldId
12+
import org.utbot.framework.plugin.api.classId
813

914
/**
1015
* Represents appendable-only immutable constraints.

utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fun Traverser.makeEnumStaticFieldsUpdates(
6868
enumConstantSymbolicResultsByName: Map<String, MethodResult>,
6969
enumConstantSymbolicValues: List<ObjectValue>,
7070
enumClassValue: ObjectValue,
71-
fieldId: FieldId
71+
fieldId: FieldId?
7272
): Pair<SymbolicStateUpdate, SymbolicValue?> {
7373
var staticFieldsUpdates = SymbolicStateUpdate()
7474
var symbolicValueForLocal: SymbolicValue? = null
@@ -101,7 +101,7 @@ fun Traverser.makeEnumStaticFieldsUpdates(
101101
}
102102

103103
// save value to associate it with local if required
104-
if (sootStaticField.name == fieldId.name) {
104+
if (fieldId != null && sootStaticField.name == fieldId.name) {
105105
symbolicValueForLocal = fieldSymbolicValue
106106
}
107107
}

utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt

+4-2
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ class UtModelFactory(
6767
elements.toMutableMap()
6868
)
6969

70-
fun produceClassRefModel(clazz: Class<String>): UtModel = UtClassRefModel(
71-
classIdForType(clazz), clazz
70+
fun produceClassRefModel(clazz: Class<*>) = UtClassRefModel(
71+
modelIdCounter.incrementAndGet(),
72+
classIdForType(clazz),
73+
clazz
7274
)
7375
}
7476

0 commit comments

Comments
 (0)