Skip to content

Commit

Permalink
Improve UtModel construction (#2517)
Browse files Browse the repository at this point in the history
* Construct assemble models for dynamic proxies

* Avoid failing casts to `LinkedHashMap` and other collection types

* Make model constructors use `add` and `put` methods from `Collection` and `Map` interfaces

* Fix `classId` in `resultMatchersViewMethodId`
  • Loading branch information
IlyaMuravjov authored Aug 17, 2023
1 parent 9311c26 commit 8ef6bd5
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ val mapClassId = java.util.Map::class.id
val collectionClassId = java.util.Collection::class.id

val listClassId = List::class.id
val setClassId = Set::class.id

val baseStreamClassId = java.util.stream.BaseStream::class.id
val streamClassId = java.util.stream.Stream::class.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ object SpringModelUtils {
)

val resultMatchersViewMethodId = MethodId(
classId = contentResultMatchersClassId,
classId = mockMvcResultMatchersClassId,
name = "view",
parameters = listOf(),
returnType = viewResultMatchersClassId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import org.utbot.framework.plugin.api.UtAssembleModel
import org.utbot.framework.plugin.api.UtExecutableCallModel
import org.utbot.framework.plugin.api.UtStatementModel
import org.utbot.framework.plugin.api.util.booleanClassId
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.collectionClassId
import org.utbot.framework.plugin.api.util.mapClassId
import org.utbot.framework.plugin.api.util.objectClassId

internal class CollectionConstructor : UtAssembleModelConstructorBase() {
Expand All @@ -22,9 +23,7 @@ internal class CollectionConstructor : UtAssembleModelConstructorBase() {
// This value will be constructed as UtCompositeModel.
val models = value.map { internalConstructor.construct(it, valueToClassId(it)) }

val classId = value::class.java.id

val addMethodId = MethodId(classId, "add", booleanClassId, listOf(objectClassId))
val addMethodId = MethodId(collectionClassId, "add", booleanClassId, listOf(objectClassId))

return models.map { UtExecutableCallModel(this, addMethodId, listOf(it)) }
}
Expand Down Expand Up @@ -63,7 +62,7 @@ internal class MapConstructor : UtAssembleModelConstructorBase() {
internalConstructor.run { construct(key, valueToClassId(key)) to construct(value, valueToClassId(value)) }
}

val putMethodId = MethodId(classId, "put", objectClassId, listOf(objectClassId, objectClassId))
val putMethodId = MethodId(mapClassId, "put", objectClassId, listOf(objectClassId, objectClassId))

return keyToValueModels.map { (key, value) ->
UtExecutableCallModel(this, putMethodId, listOf(key, value))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.utbot.framework.plugin.api.*
import org.utbot.framework.plugin.api.util.*
import org.utbot.framework.plugin.api.visible.UtStreamConsumingException
import java.lang.reflect.Modifier
import java.lang.reflect.Proxy
import java.util.*
import java.util.stream.BaseStream

Expand Down Expand Up @@ -85,6 +86,52 @@ class UtModelConstructor(
return UtLambdaModel.createFake(handleId(value), classId, baseClass)
}

private fun isProxy(value: Any?): Boolean =
value != null && Proxy.isProxyClass(value::class.java)

/**
* Using `UtAssembleModel` for dynamic proxies helps to avoid exceptions like
* `java.lang.ClassNotFoundException: jdk.proxy3.$Proxy184` during code generation.
*/
private fun constructProxy(value: Any, classId: ClassId): UtAssembleModel {
val newProxyInstanceExecutableId = java.lang.reflect.Proxy::newProxyInstance.executableId

// we don't want to construct deep models for invocationHandlers, since they can be quite large
val argsRemainingDepth = 0L

val classLoader = UtAssembleModel(
id = computeUnusedIdAndUpdate(),
classId = newProxyInstanceExecutableId.parameters[0],
modelName = "systemClassLoader",
instantiationCall = UtExecutableCallModel(
instance = null,
executable = ClassLoader::getSystemClassLoader.executableId,
params = emptyList()
)
)
val interfaces = construct(
value::class.java.interfaces,
newProxyInstanceExecutableId.parameters[1],
remainingDepth = argsRemainingDepth
)
val invocationHandler = construct(
Proxy.getInvocationHandler(value),
newProxyInstanceExecutableId.parameters[2],
remainingDepth = argsRemainingDepth
)

return UtAssembleModel(
id = handleId(value),
classId = classId,
modelName = "dynamicProxy",
instantiationCall = UtExecutableCallModel(
instance = null,
executable = newProxyInstanceExecutableId,
params = listOf(classLoader, interfaces, invocationHandler)
)
)
}

/**
* Constructs a UtModel from a concrete [value] with a specific [classId]. The result can be a [UtAssembleModel]
* as well.
Expand All @@ -103,6 +150,9 @@ class UtModelConstructor(
if (isProxyLambda(value)) {
return constructFakeLambda(value!!, classId)
}
if (isProxy(value)) {
return constructProxy(value!!, classId)
}
return when (value) {
null -> UtNullModel(classId)
is Unit -> UtVoidModel
Expand Down Expand Up @@ -294,15 +344,19 @@ class UtModelConstructor(
constructedObjects.getOrElse(value) {
tryConstructCustomModel(value, remainingDepth)
?: findEqualValueOfWellKnownType(value)
?.takeIf { classId.jClass.isInstance(it) }
?.let { tryConstructCustomModel(it, remainingDepth) }
?.takeIf { (_, replacementClassId) -> replacementClassId isSubtypeOf classId }
?.let { (replacement, replacementClassId) ->
// right now replacements only work with `UtAssembleModel`
(tryConstructCustomModel(replacement, remainingDepth) as? UtAssembleModel)
?.copy(classId = replacementClassId)
}
?: constructCompositeModel(value, remainingDepth)
}

private fun findEqualValueOfWellKnownType(value: Any): Any? = when (value) {
is List<*> -> ArrayList(value)
is Set<*> -> LinkedHashSet(value)
is Map<*, *> -> LinkedHashMap(value)
private fun findEqualValueOfWellKnownType(value: Any): Pair<Any, ClassId>? = when (value) {
is List<*> -> ArrayList(value) to listClassId
is Set<*> -> LinkedHashSet(value) to setClassId
is Map<*, *> -> LinkedHashMap(value) to mapClassId
else -> null
}

Expand Down

0 comments on commit 8ef6bd5

Please sign in to comment.