Skip to content

Fix engine to properly handle hidden fields #647 #627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
package org.utbot.common

import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method


val Class<*>.packageName: String get() = `package`?.name?:""

fun Class<*>.findField(name: String): Field =
findFieldOrNull(name) ?: error("Can't find field $name in $this")

fun Class<*>.findFieldOrNull(name: String): Field? = generateSequence(this) { it.superclass }
.mapNotNull {
try {
it.getField(name)
} catch (e: NoSuchFieldException) {
try {
it.getDeclaredField(name)
} catch (e: NoSuchFieldException) {
null
}
}
}
.firstOrNull()

fun Method.invokeCatching(obj: Any?, args: List<Any?>) = try {
Result.success(invoke(obj, *args.toTypedArray()))
} catch (e: InvocationTargetException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import org.utbot.framework.plugin.api.util.charClassId
import org.utbot.framework.plugin.api.util.constructor
import org.utbot.framework.plugin.api.util.doubleClassId
import org.utbot.framework.plugin.api.util.executableId
import org.utbot.framework.plugin.api.util.findFieldOrNull
import org.utbot.framework.plugin.api.util.floatClassId
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.intClassId
Expand All @@ -30,6 +29,7 @@ import org.utbot.framework.plugin.api.util.jClass
import org.utbot.framework.plugin.api.util.longClassId
import org.utbot.framework.plugin.api.util.method
import org.utbot.framework.plugin.api.util.primitiveTypeJvmNameOrNull
import org.utbot.framework.plugin.api.util.safeJField
import org.utbot.framework.plugin.api.util.shortClassId
import org.utbot.framework.plugin.api.util.toReferenceTypeBytecodeSignature
import org.utbot.framework.plugin.api.util.voidClassId
Expand Down Expand Up @@ -366,7 +366,7 @@ data class UtCompositeModel(
if (fields.isNotEmpty()) {
append(" ")
append(fields.entries.joinToString(", ", "{", "}") { (field, value) ->
if (value.classId != classId || value.isNull()) "${field.name}: $value" else "${field.name}: not evaluated"
if (value.classId != classId || value.isNull()) "(${field.declaringClass}) ${field.name}: $value" else "${field.name}: not evaluated"
}) // TODO: here we can get an infinite recursion if we have cyclic dependencies.
}
if (mocks.isNotEmpty()) {
Expand Down Expand Up @@ -876,7 +876,7 @@ open class FieldId(val declaringClass: ClassId, val name: String) {
return result
}

override fun toString() = declaringClass.findFieldOrNull(name).toString()
override fun toString() = safeJField.toString()
}

inline fun <T> withReflection(block: () -> T): T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.utbot.framework.plugin.api.impl
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.classId
import org.utbot.framework.plugin.api.util.field
import org.utbot.framework.plugin.api.util.jField
import org.utbot.framework.plugin.api.util.id
import java.lang.reflect.Modifier
import soot.Scene
Expand Down Expand Up @@ -32,25 +32,25 @@ interface FieldIdStrategy {
class FieldIdReflectionStrategy(val fieldId: FieldId) : FieldIdStrategy {

override val isPublic: Boolean
get() = Modifier.isPublic(fieldId.field.modifiers)
get() = Modifier.isPublic(fieldId.jField.modifiers)

override val isProtected: Boolean
get() = Modifier.isProtected(fieldId.field.modifiers)
get() = Modifier.isProtected(fieldId.jField.modifiers)

override val isPrivate: Boolean
get() = Modifier.isPrivate(fieldId.field.modifiers)
get() = Modifier.isPrivate(fieldId.jField.modifiers)

override val isFinal: Boolean
get() = Modifier.isFinal(fieldId.field.modifiers)
get() = Modifier.isFinal(fieldId.jField.modifiers)

override val isStatic: Boolean
get() = Modifier.isStatic(fieldId.field.modifiers)
get() = Modifier.isStatic(fieldId.jField.modifiers)

override val isSynthetic: Boolean
get() = fieldId.field.isSynthetic
get() = fieldId.jField.isSynthetic

override val type: ClassId
get() = fieldId.field.type.id
get() = fieldId.jField.type.id
}

class FieldIdSootStrategy(val declaringClass: ClassId, val fieldId: FieldId) : FieldIdStrategy {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.utbot.framework.plugin.api.util

import org.utbot.common.findFieldOrNull
import org.utbot.framework.plugin.api.BuiltinClassId
import org.utbot.framework.plugin.api.BuiltinMethodId
import org.utbot.framework.plugin.api.ClassId
Expand Down Expand Up @@ -286,9 +285,17 @@ val ClassId.isMap: Boolean
val ClassId.isIterableOrMap: Boolean
get() = isIterable || isMap

fun ClassId.findFieldOrNull(fieldName: String): Field? = jClass.findFieldOrNull(fieldName)
fun ClassId.findFieldByIdOrNull(fieldId: FieldId): Field? {
if (isNotSubtypeOf(fieldId.declaringClass)) {
return null
}

return fieldId.safeJField
}

fun ClassId.hasField(fieldName: String): Boolean = findFieldOrNull(fieldName) != null
fun ClassId.hasField(fieldId: FieldId): Boolean {
return findFieldByIdOrNull(fieldId) != null
}

fun ClassId.defaultValueModel(): UtModel = when (this) {
intClassId -> UtPrimitiveModel(0)
Expand All @@ -303,11 +310,12 @@ fun ClassId.defaultValueModel(): UtModel = when (this) {
}

// FieldId utils
val FieldId.safeJField: Field?
get() = declaringClass.jClass.declaredFields.firstOrNull { it.name == name }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JFYI: theoretically it is possible to have ClassId but not jClass, and to get ClassNotFoundException. We currently have this problem in other jClass-related code (e.g., ClassId.canonicalName, ClassId.simpleName), and it would be a very rare case (at least while lambdas are filtered out), so I think it's OK.


// TODO: maybe cache it somehow in the future
val FieldId.field: Field
get() = declaringClass.jClass.declaredFields.firstOrNull { it.name == name }
?: error("Field $name is not found in class ${declaringClass.jClass.name}")
val FieldId.jField: Field
get() = safeJField ?: error("Field $name is not declared in class ${declaringClass.jClass.name}")

// https://docstore.mik.ua/orelly/java-ent/jnut/ch03_13.htm
val FieldId.isInnerClassEnclosingClassReference: Boolean
Expand Down
8 changes: 5 additions & 3 deletions utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ class Hierarchy(private val typeRegistry: TypeRegistry) {
type as? RefType ?: error("$type is not a refType")

val realType = typeRegistry.findRealType(type) as RefType
val realFieldDeclaringType = typeRegistry.findRealType(field.declaringClass.type) as RefType

val ancestorType = ancestors(realType.sootClass.id).lastOrNull { it.declaresField(field.subSignature) }?.type
?: error("No such field ${field.subSignature} found in ${realType.sootClass.name}")
return ChunkId("$ancestorType", field.name)
if (realFieldDeclaringType.sootClass !in ancestors(realType.sootClass.id)) {
error("No such field ${field.subSignature} found in ${realType.sootClass.name}")
}
return ChunkId("$realFieldDeclaringType", field.name)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ sealed class UtAbstractStringBuilderWrapper(className: String) : BaseOverriddenW

val arrayValuesChunkId = typeRegistry.arrayChunkId(charArrayType)

val valuesFieldChunkId = hierarchy.chunkIdForField(utStringClass.type, overriddenClass.valueField)
val valuesFieldChunkId = hierarchy.chunkIdForField(overriddenClass.type, overriddenClass.valueField)
val valuesArrayAddrDescriptor = MemoryChunkDescriptor(valuesFieldChunkId, wrapper.type, charType)
val valuesArrayAddr = findArray(valuesArrayAddrDescriptor, MemoryState.CURRENT).select(wrapper.addr)

Expand Down
18 changes: 13 additions & 5 deletions utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import kotlinx.collections.immutable.toPersistentMap
import kotlinx.collections.immutable.toPersistentSet
import org.utbot.common.WorkaroundReason.HACK
import org.utbot.common.WorkaroundReason.REMOVE_ANONYMOUS_CLASSES
import org.utbot.common.findField
import org.utbot.common.unreachableBranch
import org.utbot.common.withAccessibility
import org.utbot.common.workaround
Expand Down Expand Up @@ -90,8 +89,9 @@ import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.UtMethod
import org.utbot.framework.plugin.api.classId
import org.utbot.framework.plugin.api.id
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.jField
import org.utbot.framework.plugin.api.util.jClass
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.signature
import org.utbot.framework.plugin.api.util.utContext
import org.utbot.framework.util.executableId
Expand Down Expand Up @@ -582,7 +582,7 @@ class Traverser(
declaringClass: SootClass,
stmt: Stmt
): SymbolicStateUpdate {
val concreteValue = extractConcreteValue(field, declaringClass)
val concreteValue = extractConcreteValue(field)
val (symbolicResult, symbolicStateUpdate) = toMethodResult(concreteValue, field.type)
val symbolicValue = (symbolicResult as SymbolicSuccess).value

Expand Down Expand Up @@ -634,12 +634,20 @@ class Traverser(
// Some fields are inaccessible with reflection, so we have to instantiate it by ourselves.
// Otherwise, extract it from the class.
// TODO JIRA:1593
private fun extractConcreteValue(field: SootField, declaringClass: SootClass): Any? =
private fun extractConcreteValue(field: SootField): Any? =
when (field.signature) {
SECURITY_FIELD_SIGNATURE -> SecurityManager()
FIELD_FILTER_MAP_FIELD_SIGNATURE -> mapOf(Reflection::class to arrayOf("fieldFilterMap", "methodFilterMap"))
METHOD_FILTER_MAP_FIELD_SIGNATURE -> emptyMap<Class<*>, Array<String>>()
else -> declaringClass.id.jClass.findField(field.name).let { it.withAccessibility { it.get(null) } }
else -> {
val fieldId = field.fieldId
val jField = fieldId.jField
jField.let {
it.withAccessibility {
it.get(null)
}
}
}
}

private fun isStaticInstanceInMethodResult(id: ClassId, methodResult: MethodResult?) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy
hierarchy
.ancestors(type.sootClass.id)
.flatMap { it.fields }
.asReversed() // to take fields of the farthest parent in the distinctBy
.distinctBy { it.name } // TODO we lose hidden fields here JIRA:315
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.utbot.engine

import org.utbot.common.findField
import org.utbot.common.findFieldOrNull
import org.utbot.common.invokeCatching
import org.utbot.common.withAccessibility
import org.utbot.framework.plugin.api.ClassId
Expand Down Expand Up @@ -37,6 +35,7 @@ import org.utbot.framework.plugin.api.UtValueExecutionState
import org.utbot.framework.plugin.api.UtVoidModel
import org.utbot.framework.plugin.api.isMockModel
import org.utbot.framework.plugin.api.util.constructor
import org.utbot.framework.plugin.api.util.jField
import org.utbot.framework.plugin.api.util.jClass
import org.utbot.framework.plugin.api.util.method
import org.utbot.framework.plugin.api.util.utContext
Expand Down Expand Up @@ -212,9 +211,8 @@ class ValueConstructor {
val classInstance = javaClass.anyInstance
constructedObjects[model] = classInstance

model.fields.forEach { (field, fieldModel) ->
val declaredField =
javaClass.findFieldOrNull(field.name) ?: error("Can't find field: $field for $javaClass")
model.fields.forEach { (fieldId, fieldModel) ->
val declaredField = fieldId.jField
val accessible = declaredField.isAccessible

try {
Expand All @@ -228,7 +226,7 @@ class ValueConstructor {
fieldModel.classId.name,
model.classId.name,
UtConcreteValue(classInstance),
field.name
fieldId.name
)
}
val value = construct(fieldModel, target).value
Expand Down Expand Up @@ -360,7 +358,7 @@ class ValueConstructor {
val instanceClassId = instanceModel.classId
val fieldModel = directSetterModel.fieldModel

val field = instance::class.java.findField(directSetterModel.fieldId.name)
val field = directSetterModel.fieldId.jField
val isAccessible = field.isAccessible

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.utbot.engine.pc.select
import org.utbot.engine.symbolic.SymbolicStateUpdate
import org.utbot.engine.symbolic.asHardConstraint
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.util.field
import org.utbot.framework.plugin.api.util.jField
import soot.SootClass
import soot.SootField
import soot.SootMethod
Expand Down Expand Up @@ -43,7 +43,7 @@ fun associateEnumSootFieldsWithConcreteValues(
enumConstants: List<Enum<*>>
): List<Pair<SootField, List<Any>>> =
enumFields.map { enumSootField ->
val enumField = enumSootField.fieldId.field
val enumField = enumSootField.fieldId.jField

val fieldValues = if (enumSootField.isStatic) {
val staticFieldValue = enumField.withAccessibility { enumField.get(null) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ internal class CgFieldStateManagerImpl(val context: CgContext)
}

// if previous field has type that does not have current field, this field is inaccessible
if (index > 0 && !path[index - 1].type.hasField(fieldPathElement.field.name)) {
if (index > 0 && !path[index - 1].type.hasField(fieldPathElement.field)) {
lastAccessibleIndex = index - 1
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ import org.utbot.framework.plugin.api.util.doubleArrayClassId
import org.utbot.framework.plugin.api.util.doubleClassId
import org.utbot.framework.plugin.api.util.doubleWrapperClassId
import org.utbot.framework.plugin.api.util.executable
import org.utbot.framework.plugin.api.util.field
import org.utbot.framework.plugin.api.util.jField
import org.utbot.framework.plugin.api.util.floatArrayClassId
import org.utbot.framework.plugin.api.util.floatClassId
import org.utbot.framework.plugin.api.util.floatWrapperClassId
Expand Down Expand Up @@ -873,8 +873,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
private fun FieldId.getAccessExpression(variable: CgVariable): CgExpression =
// Can directly access field only if it is declared in variable class (or in its ancestors)
// and is accessible from current package
if (variable.type.hasField(name) && isAccessibleFrom(testClassPackageName)) {
if (field.isStatic) CgStaticFieldAccess(this) else CgFieldAccess(variable, this)
if (variable.type.hasField(this) && isAccessibleFrom(testClassPackageName)) {
if (jField.isStatic) CgStaticFieldAccess(this) else CgFieldAccess(variable, this)
} else {
testClassThisInstance[getFieldValue](variable, stringLiteral(name))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel
import org.utbot.framework.plugin.api.UtReferenceModel
import org.utbot.framework.plugin.api.UtVoidModel
import org.utbot.framework.plugin.api.util.defaultValueModel
import org.utbot.framework.plugin.api.util.field
import org.utbot.framework.plugin.api.util.findFieldOrNull
import org.utbot.framework.plugin.api.util.jField
import org.utbot.framework.plugin.api.util.findFieldByIdOrNull
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.intClassId
import org.utbot.framework.plugin.api.util.isArray
Expand Down Expand Up @@ -128,9 +128,9 @@ internal class CgVariableConstructor(val context: CgContext) :
}

for ((fieldId, fieldModel) in model.fields) {
val field = fieldId.field
val field = fieldId.jField
val variableForField = getOrCreateVariable(fieldModel)
val fieldFromVariableSpecifiedType = obj.type.findFieldOrNull(field.name)
val fieldFromVariableSpecifiedType = obj.type.findFieldByIdOrNull(fieldId)

// we cannot set field directly if variable declared type does not have such field
// or we cannot directly create variable for field with the specified type (it is private, for example)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.utbot.framework.concrete

import org.utbot.common.findField
import org.utbot.common.findFieldOrNull
import org.utbot.common.invokeCatching
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ConstructorId
Expand Down Expand Up @@ -33,6 +31,7 @@ import org.utbot.framework.plugin.api.UtVoidModel
import org.utbot.framework.plugin.api.isMockModel
import org.utbot.framework.plugin.api.util.constructor
import org.utbot.framework.plugin.api.util.executableId
import org.utbot.framework.plugin.api.util.jField
import org.utbot.framework.plugin.api.util.jClass
import org.utbot.framework.plugin.api.util.method
import org.utbot.framework.plugin.api.util.utContext
Expand Down Expand Up @@ -169,17 +168,16 @@ class MockValueConstructor(
mockInstance
}

model.fields.forEach { (field, fieldModel) ->
val declaredField =
javaClass.findFieldOrNull(field.name) ?: error("Can't find field: $field for $javaClass")
model.fields.forEach { (fieldId, fieldModel) ->
val declaredField = fieldId.jField
val accessible = declaredField.isAccessible
declaredField.isAccessible = true

val modifiersField = Field::class.java.getDeclaredField("modifiers")
modifiersField.isAccessible = true

val target = mockTarget(fieldModel) {
FieldMockTarget(fieldModel.classId.name, model.classId.name, UtConcreteValue(classInstance), field.name)
FieldMockTarget(fieldModel.classId.name, model.classId.name, UtConcreteValue(classInstance), fieldId.name)
}
val value = construct(fieldModel, target).value
val instance = if (Modifier.isStatic(declaredField.modifiers)) null else classInstance
Expand Down Expand Up @@ -394,7 +392,7 @@ class MockValueConstructor(
val instanceClassId = instanceModel.classId
val fieldModel = directSetterModel.fieldModel

val field = instance::class.java.findField(directSetterModel.fieldId.name)
val field = directSetterModel.fieldId.jField
val isAccessible = field.isAccessible

try {
Expand Down
Loading