Skip to content

Commit e6bb7f7

Browse files
authored
Сoncrete values for enums (#19)
1 parent 365b36e commit e6bb7f7

18 files changed

Lines changed: 1157 additions & 368 deletions

File tree

utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ fun Method.invokeCatching(obj: Any?, args: List<Any?>) = try {
2828
Result.success(invoke(obj, *args.toTypedArray()))
2929
} catch (e: InvocationTargetException) {
3030
Result.failure<Nothing>(e.targetException)
31-
}
31+
}

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ internal class SettingDelegate<T>(val initializer: () -> T) {
3131
/**
3232
* Default concrete execution timeout (in milliseconds).
3333
*/
34-
const val DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS = 100L
34+
const val DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS = 1000L
3535

3636
object UtSettings {
3737
private val properties = Properties().also { props ->

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import org.utbot.engine.pc.mkLong
2525
import org.utbot.engine.pc.mkShort
2626
import org.utbot.engine.pc.select
2727
import org.utbot.engine.pc.store
28+
import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName
2829
import org.utbot.engine.symbolic.asHardConstraint
2930
import org.utbot.engine.z3.intValue
3031
import org.utbot.engine.z3.value
@@ -82,6 +83,7 @@ import soot.PrimType
8283
import soot.RefType
8384
import soot.Scene
8485
import soot.ShortType
86+
import soot.SootClass
8587
import soot.SootField
8688
import soot.Type
8789
import soot.VoidType
@@ -1068,7 +1070,7 @@ fun UtBotSymbolicEngine.toMethodResult(value: Any?, sootType: Type): MethodResul
10681070
arrayToMethodResult(value.size, elementType) {
10691071
if (value[it] == null) return@arrayToMethodResult nullObjectAddr
10701072

1071-
val addr = UtAddrExpression(mkBVConst("staticVariable${value.hashCode()}", UtInt32Sort))
1073+
val addr = UtAddrExpression(mkBVConst("staticVariable${value.hashCode()}_$it", UtInt32Sort))
10721074

10731075
val createdElement = if (elementType is RefType) {
10741076
val className = value[it]!!.javaClass.id.name
@@ -1141,6 +1143,30 @@ private fun UtBotSymbolicEngine.arrayToMethodResult(
11411143
)
11421144
}
11431145

1146+
fun UtBotSymbolicEngine.constructEnumStaticFieldResult(
1147+
fieldName: String,
1148+
fieldType: Type,
1149+
declaringClass: SootClass,
1150+
enumConstantSymbolicResultsByName: Map<String, MethodResult>,
1151+
staticFieldConcreteValue: Any?,
1152+
enumConstantSymbolicValues: List<ObjectValue>
1153+
): MethodResult =
1154+
if (isEnumValuesFieldName(fieldName)) {
1155+
// special case to fill $VALUES array with already created symbolic values for enum constants runtime values
1156+
arrayToMethodResult(enumConstantSymbolicValues.size, declaringClass.type) { i ->
1157+
enumConstantSymbolicValues[i].addr
1158+
}
1159+
} else {
1160+
if (fieldName in enumConstantSymbolicResultsByName) {
1161+
// it is field to store enum constant so we use already created symbolic value from runtime enum constant
1162+
enumConstantSymbolicResultsByName.getValue(fieldName)
1163+
} else {
1164+
// otherwise, it is a common static field,
1165+
// and we have to create new symbolic value for it using its concrete value
1166+
toMethodResult(staticFieldConcreteValue, fieldType)
1167+
}
1168+
}
1169+
11441170
private val Type.unsigned: Boolean
11451171
get() = this is CharType
11461172

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import soot.Scene
1818
import soot.SootField
1919
import soot.Type
2020
import soot.VoidType
21-
import sun.reflect.Reflection
2221

2322
class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy: Hierarchy) {
2423

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

Lines changed: 125 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ import org.utbot.engine.symbolic.SymbolicStateUpdate
7575
import org.utbot.engine.symbolic.asHardConstraint
7676
import org.utbot.engine.symbolic.asSoftConstraint
7777
import org.utbot.engine.symbolic.asUpdate
78+
import org.utbot.engine.util.statics.concrete.associateEnumSootFieldsWithConcreteValues
79+
import org.utbot.engine.util.statics.concrete.isEnumAffectingExternalStatics
80+
import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName
81+
import org.utbot.engine.util.statics.concrete.makeEnumNonStaticFieldsUpdates
82+
import org.utbot.engine.util.statics.concrete.makeEnumStaticFieldsUpdates
83+
import org.utbot.engine.util.statics.concrete.makeSymbolicValuesFromEnumConcreteValues
7884
import org.utbot.framework.PathSelectorType
7985
import org.utbot.framework.UtSettings
8086
import org.utbot.framework.UtSettings.checkSolverTimeoutMillis
@@ -152,6 +158,7 @@ import kotlinx.collections.immutable.persistentHashMapOf
152158
import kotlinx.collections.immutable.persistentListOf
153159
import kotlinx.collections.immutable.persistentSetOf
154160
import kotlinx.collections.immutable.toPersistentList
161+
import kotlinx.collections.immutable.toPersistentMap
155162
import kotlinx.collections.immutable.toPersistentSet
156163
import kotlinx.coroutines.CancellationException
157164
import kotlinx.coroutines.Job
@@ -196,7 +203,6 @@ import soot.jimple.Expr
196203
import soot.jimple.FieldRef
197204
import soot.jimple.FloatConstant
198205
import soot.jimple.IdentityRef
199-
import soot.jimple.InstanceFieldRef
200206
import soot.jimple.IntConstant
201207
import soot.jimple.InvokeExpr
202208
import soot.jimple.LongConstant
@@ -876,7 +882,7 @@ class UtBotSymbolicEngine(
876882
stmt: Stmt
877883
): Boolean {
878884
if (shouldProcessStaticFieldConcretely(fieldRef)) {
879-
return processStaticFieldConcretely(fieldRef)
885+
return processStaticFieldConcretely(fieldRef, stmt)
880886
}
881887

882888
val field = fieldRef.field
@@ -922,7 +928,9 @@ class UtBotSymbolicEngine(
922928
// Note that this list is not exhaustive, so it may be supplemented in the future.
923929
val packagesToProcessConcretely = javaPackagesToProcessConcretely + sunPackagesToProcessConcretely
924930

925-
return packagesToProcessConcretely.any { className.startsWith(it) }
931+
val declaringClass = fieldRef.field.declaringClass
932+
933+
val isFromPackageToProcessConcretely = packagesToProcessConcretely.any { className.startsWith(it) }
926934
// it is required to remove classes we override, since
927935
// we could accidentally initialize their final fields
928936
// with values that will later affect our overridden classes
@@ -933,6 +941,13 @@ class UtBotSymbolicEngine(
933941
//hardcoded string for class name is used cause class is not public
934942
//this is a hack to avoid crashing on code with Math.random()
935943
&& !className.endsWith("RandomNumberGeneratorHolder")
944+
945+
// we can process concretely only enums that does not affect the external system
946+
val isEnumNotAffectingExternalStatics = declaringClass.let {
947+
it.isEnum && !it.isEnumAffectingExternalStatics(typeResolver)
948+
}
949+
950+
return isEnumNotAffectingExternalStatics || isFromPackageToProcessConcretely
936951
}
937952
}
938953

@@ -954,7 +969,7 @@ class UtBotSymbolicEngine(
954969
*
955970
* Returns true if processing takes place and Engine should end traversal of current statement.
956971
*/
957-
private fun processStaticFieldConcretely(fieldRef: StaticFieldRef): Boolean {
972+
private fun processStaticFieldConcretely(fieldRef: StaticFieldRef, stmt: Stmt): Boolean {
958973
val field = fieldRef.field
959974
val fieldId = field.fieldId
960975
if (memory.isInitialized(fieldId)) {
@@ -963,26 +978,123 @@ class UtBotSymbolicEngine(
963978

964979
// Gets concrete value, converts to symbolic value
965980
val declaringClass = field.declaringClass
981+
982+
val (edge, updates) = if (declaringClass.isEnum) {
983+
makeConcreteUpdatesForEnums(fieldId, declaringClass, stmt)
984+
} else {
985+
makeConcreteUpdatesForNonEnumStaticField(field, fieldId, declaringClass)
986+
}
987+
988+
val newState = environment.state.updateQueued(edge, updates)
989+
pathSelector.offer(newState)
990+
991+
return true
992+
}
993+
994+
private fun makeConcreteUpdatesForEnums(
995+
fieldId: FieldId,
996+
declaringClass: SootClass,
997+
stmt: Stmt
998+
): Pair<Edge, SymbolicStateUpdate> {
999+
val type = declaringClass.type
1000+
val jClass = type.id.jClass
1001+
1002+
// symbolic value for enum class itself
1003+
val enumClassValue = findOrCreateStaticObject(type)
1004+
1005+
// values for enum constants
1006+
val enumConstantConcreteValues = jClass.enumConstants.filterIsInstance<Enum<*>>()
1007+
1008+
val (enumConstantSymbolicValues, enumConstantSymbolicResultsByName) =
1009+
makeSymbolicValuesFromEnumConcreteValues(type, enumConstantConcreteValues)
1010+
1011+
val enumFields = typeResolver.findFields(type)
1012+
1013+
val sootFieldsWithRuntimeValues =
1014+
associateEnumSootFieldsWithConcreteValues(enumFields, enumConstantConcreteValues)
1015+
1016+
val (staticFields, nonStaticFields) = sootFieldsWithRuntimeValues.partition { it.first.isStatic }
1017+
1018+
val (staticFieldUpdates, curFieldSymbolicValueForLocalVariable) = makeEnumStaticFieldsUpdates(
1019+
staticFields,
1020+
declaringClass,
1021+
enumConstantSymbolicResultsByName,
1022+
enumConstantSymbolicValues,
1023+
enumClassValue,
1024+
fieldId
1025+
)
1026+
1027+
val nonStaticFieldsUpdates = makeEnumNonStaticFieldsUpdates(enumConstantSymbolicValues, nonStaticFields)
1028+
1029+
// we do not mark static fields for enum constants and $VALUES as meaningful
1030+
// because we should not set them in generated code
1031+
val meaningfulStaticFields = staticFields.filterNot {
1032+
val name = it.first.name
1033+
1034+
name in enumConstantSymbolicResultsByName.keys || isEnumValuesFieldName(name)
1035+
}
1036+
1037+
val initializedStaticFieldsMemoryUpdate = MemoryUpdate(
1038+
initializedStaticFields = staticFields.associate { it.first.fieldId to it.second.single() }.toPersistentMap(),
1039+
meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet()
1040+
)
1041+
1042+
var allUpdates = staticFieldUpdates + nonStaticFieldsUpdates + initializedStaticFieldsMemoryUpdate
1043+
1044+
// we need to make locals update if it is an assignment statement
1045+
// for enums we have only two types for assignment with enums — enum constant or $VALUES field
1046+
// for example, a jimple body for Enum::values method starts with the following lines:
1047+
// public static ClassWithEnum$StatusEnum[] values()
1048+
// {
1049+
// ClassWithEnum$StatusEnum[] $r0, $r2;
1050+
// java.lang.Object $r1;
1051+
// $r0 = <ClassWithEnum$StatusEnum: ClassWithEnum$StatusEnum[] $VALUES>;
1052+
// $r1 = virtualinvoke $r0.<java.lang.Object: java.lang.Object clone()>();
1053+
1054+
// so, we have to make an update for the local $r0
1055+
if (stmt is JAssignStmt) {
1056+
val local = stmt.leftOp as JimpleLocal
1057+
val localUpdate = localMemoryUpdate(
1058+
local.variable to curFieldSymbolicValueForLocalVariable
1059+
)
1060+
1061+
allUpdates += localUpdate
1062+
}
1063+
1064+
// enum static initializer can be the first statement in method so there will be no last edge
1065+
// for example, as it is during Enum::values method analysis:
1066+
// public static ClassWithEnum$StatusEnum[] values()
1067+
// {
1068+
// ClassWithEnum$StatusEnum[] $r0, $r2;
1069+
// java.lang.Object $r1;
1070+
1071+
// $r0 = <ClassWithEnum$StatusEnum: ClassWithEnum$StatusEnum[] $VALUES>;
1072+
val edge = environment.state.lastEdge ?: globalGraph.succ(stmt)
1073+
1074+
return edge to allUpdates
1075+
}
1076+
1077+
private fun makeConcreteUpdatesForNonEnumStaticField(
1078+
field: SootField,
1079+
fieldId: FieldId,
1080+
declaringClass: SootClass
1081+
): Pair<Edge, SymbolicStateUpdate> {
9661082
val concreteValue = extractConcreteValue(field, declaringClass)
9671083
val (symbolicResult, symbolicStateUpdate) = toMethodResult(concreteValue, field.type)
9681084
val symbolicValue = (symbolicResult as SymbolicSuccess).value
9691085

9701086
// Collects memory updates
9711087
val initializedFieldUpdate =
9721088
MemoryUpdate(initializedStaticFields = persistentHashMapOf(fieldId to concreteValue))
1089+
9731090
val objectUpdate = objectUpdate(
9741091
instance = findOrCreateStaticObject(declaringClass.type),
9751092
field = field,
9761093
value = valueToExpression(symbolicValue, field.type)
9771094
)
9781095
val allUpdates = symbolicStateUpdate + initializedFieldUpdate + objectUpdate
979-
pathSelector.offer(
980-
environment.state.updateQueued(
981-
edge = environment.state.lastEdge!!,
982-
allUpdates
983-
)
984-
)
985-
return true
1096+
1097+
return environment.state.lastEdge!! to allUpdates
9861098
}
9871099

9881100
// Some fields are inaccessible with reflection, so we have to instantiate it by ourselves.
@@ -1200,7 +1312,7 @@ class UtBotSymbolicEngine(
12001312
/**
12011313
* Converts value to expression with cast to target type for primitives.
12021314
*/
1203-
private fun valueToExpression(value: SymbolicValue, type: Type): UtExpression = when (value) {
1315+
fun valueToExpression(value: SymbolicValue, type: Type): UtExpression = when (value) {
12041316
is ReferenceValue -> value.addr
12051317
// TODO: shall we add additional constraint that aligned expression still equals original?
12061318
// BitVector can lose valuable bites during extraction
@@ -2028,6 +2140,7 @@ class UtBotSymbolicEngine(
20282140
val touchedStaticFields = persistentListOf(staticFieldMemoryUpdate)
20292141
queuedSymbolicStateUpdates += MemoryUpdate(staticFieldsUpdates = touchedStaticFields)
20302142

2143+
// TODO filter enum constant static fields JIRA:1681
20312144
if (!environment.method.isStaticInitializer && !fieldId.isSynthetic) {
20322145
queuedSymbolicStateUpdates += MemoryUpdate(meaningfulStaticFields = persistentSetOf(fieldId))
20332146
}

0 commit comments

Comments
 (0)