Skip to content

Commit 3de411a

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 83341fd commit 3de411a

File tree

20 files changed

+434
-63
lines changed

20 files changed

+434
-63
lines changed

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ import kotlin.reflect.full.instanceParameter
5858
import kotlin.reflect.jvm.javaConstructor
5959
import kotlin.reflect.jvm.javaType
6060

61+
const val SYMBOLIC_NULL_ADDR: Int = 0
62+
6163
data class UtMethod<R>(
6264
val callable: KCallable<R>,
6365
val clazz: KClass<*>
@@ -282,6 +284,16 @@ fun UtModel.hasDefaultValue() =
282284
*/
283285
fun UtModel.isMockModel() = this is UtCompositeModel && isMock
284286

287+
/**
288+
* Get model id (symbolic null value for UtNullModel)
289+
* or null if model has no id (e.g., a primitive model) or the id is null.
290+
*/
291+
fun UtModel.idOrNull(): Int? = when (this) {
292+
is UtNullModel -> SYMBOLIC_NULL_ADDR
293+
is UtReferenceModel -> id
294+
else -> null
295+
}
296+
285297
/**
286298
* Model for nulls.
287299
*/
@@ -323,20 +335,24 @@ object UtVoidModel : UtModel(voidClassId)
323335
* Model for enum constant
324336
*/
325337
data class UtEnumConstantModel(
338+
override val id: Int?,
326339
override val classId: ClassId,
327340
val value: Enum<*>
328-
) : UtModel(classId) {
329-
override fun toString(): String = "$value"
341+
) : UtReferenceModel(id, classId) {
342+
// Model id is included for debugging purposes
343+
override fun toString(): String = "$value@$id"
330344
}
331345

332346
/**
333347
* Model for class reference
334348
*/
335349
data class UtClassRefModel(
350+
override val id: Int?,
336351
override val classId: ClassId,
337352
val value: Class<*>
338-
) : UtModel(classId) {
339-
override fun toString(): String = "$value"
353+
) : UtReferenceModel(id, classId) {
354+
// Model id is included for debugging purposes
355+
override fun toString(): String = "$value@$id"
340356
}
341357

342358
/**

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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 2 additions & 1 deletion
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/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() =

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

Lines changed: 62 additions & 16 deletions
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 += 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)

0 commit comments

Comments
 (0)