diff --git a/utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt index e375e2ee0b..f412f84c09 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt @@ -76,4 +76,10 @@ enum class WorkaroundReason { * construct `toArray` invocation (because streams cannot be consumed twice). */ CONSUME_DIRTY_STREAMS, + + /** + * During analyzing Spring projects, we process all static initializers in enums concretely because they are used + * very widely and are too big and complicated. + */ + PROCESS_CONCRETELY_STATIC_INITIALIZERS_IN_ENUMS_FOR_SPRING } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 38c7e71d65..c3e09f2a9c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -106,7 +106,6 @@ import org.utbot.engine.types.NUMBER_OF_PREFERRED_TYPES import org.utbot.engine.types.OBJECT_TYPE import org.utbot.engine.types.SECURITY_FIELD_SIGNATURE import org.utbot.engine.util.statics.concrete.associateEnumSootFieldsWithConcreteValues -import org.utbot.engine.util.statics.concrete.isEnumAffectingExternalStatics import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName import org.utbot.engine.util.statics.concrete.makeEnumNonStaticFieldsUpdates import org.utbot.engine.util.statics.concrete.makeEnumStaticFieldsUpdates @@ -118,6 +117,7 @@ import org.utbot.framework.UtSettings.substituteStaticsWithSymbolicVariable import org.utbot.framework.isFromTrustedLibrary import org.utbot.framework.context.ApplicationContext import org.utbot.framework.context.NonNullSpeculator +import org.utbot.framework.context.StaticInitializerConcreteProcessor import org.utbot.framework.context.TypeReplacer import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId @@ -243,6 +243,7 @@ class Traverser( private val mocker: Mocker, private val typeReplacer: TypeReplacer, private val nonNullSpeculator: NonNullSpeculator, + private val staticInitializerConcreteProcessor: StaticInitializerConcreteProcessor, private val taintContext: TaintContext, ) : UtContextInitializer() { @@ -498,7 +499,7 @@ class Traverser( // This order of processing options is important. // First, we should process classes that // cannot be analyzed without clinit sections, e.g., enums - if (shouldProcessStaticFieldConcretely(fieldRef)) { + if (staticInitializerConcreteProcessor.shouldProcessStaticFieldConcretely(fieldRef, typeResolver)) { return processStaticFieldConcretely(fieldRef, stmt) } @@ -544,52 +545,6 @@ class Traverser( return true } - /** - * Decides should we read this static field concretely or not. - */ - private fun shouldProcessStaticFieldConcretely(fieldRef: StaticFieldRef): Boolean { - workaround(HACK) { - val className = fieldRef.field.declaringClass.name - - // We should process clinit sections for classes from these packages. - // Note that this list is not exhaustive, so it may be supplemented in the future. - val packagesToProcessConcretely = javaPackagesToProcessConcretely + sunPackagesToProcessConcretely - - val declaringClass = fieldRef.field.declaringClass - - val isFromPackageToProcessConcretely = packagesToProcessConcretely.any { className.startsWith(it) } - // it is required to remove classes we override, since - // we could accidentally initialize their final fields - // with values that will later affect our overridden classes - && fieldRef.field.declaringClass.type !in classToWrapper.keys - // because of the same reason we should not use - // concrete information from clinit sections for enums - && !fieldRef.field.declaringClass.isEnum - //hardcoded string for class name is used cause class is not public - //this is a hack to avoid crashing on code with Math.random() - && !className.endsWith("RandomNumberGeneratorHolder") - - // we can process concretely only enums that does not affect the external system - val isEnumNotAffectingExternalStatics = declaringClass.let { - it.isEnum && !it.isEnumAffectingExternalStatics(typeResolver) - } - - return isEnumNotAffectingExternalStatics || isFromPackageToProcessConcretely - } - } - - private val javaPackagesToProcessConcretely = listOf( - "applet", "awt", "beans", "io", "lang", "math", "net", - "nio", "rmi", "security", "sql", "text", "time", "util" - ).map { "java.$it" } - - private val sunPackagesToProcessConcretely = listOf( - "applet", "audio", "awt", "corba", "font", "instrument", - "invoke", "io", "java2d", "launcher", "management", "misc", - "net", "nio", "print", "reflect", "rmi", "security", - "swing", "text", "tools.jar", "tracing", "util" - ).map { "sun.$it" } - /** * Checks if field was processed (read) already. * Otherwise offers to path selector the same statement, but with memory and constraints updates for this field. diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 46d16f234d..86cadbf5e8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -176,6 +176,7 @@ class UtBotSymbolicEngine( mocker, applicationContext.typeReplacer, applicationContext.nonNullSpeculator, + applicationContext.staticInitializerConcreteProcessor, taintContext, ) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/ApplicationContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/ApplicationContext.kt index 8a17675ee5..5f7f9e6190 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/context/ApplicationContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/ApplicationContext.kt @@ -7,6 +7,7 @@ interface ApplicationContext { val mockerContext: MockerContext val typeReplacer: TypeReplacer val nonNullSpeculator: NonNullSpeculator + val staticInitializerConcreteProcessor: StaticInitializerConcreteProcessor fun createConcreteExecutionContext( fullClasspath: String, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/StaticInitializerConcreteProcessor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/StaticInitializerConcreteProcessor.kt new file mode 100644 index 0000000000..b359f641aa --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/StaticInitializerConcreteProcessor.kt @@ -0,0 +1,11 @@ +package org.utbot.framework.context + +import org.utbot.engine.types.TypeResolver +import soot.jimple.StaticFieldRef + +interface StaticInitializerConcreteProcessor { + /** + * Decides should we read this static field concretely or not. + */ + fun shouldProcessStaticFieldConcretely(fieldRef: StaticFieldRef, typeResolver: TypeResolver): Boolean +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt index e3f18b5cb7..2e3f391709 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt @@ -7,6 +7,7 @@ import org.utbot.framework.context.ApplicationContext import org.utbot.framework.context.ConcreteExecutionContext import org.utbot.framework.context.MockerContext import org.utbot.framework.context.NonNullSpeculator +import org.utbot.framework.context.StaticInitializerConcreteProcessor import org.utbot.framework.context.TypeReplacer /** @@ -15,7 +16,8 @@ import org.utbot.framework.context.TypeReplacer class SimpleApplicationContext( override val mockerContext: MockerContext, override val typeReplacer: TypeReplacer = SimpleTypeReplacer(), - override val nonNullSpeculator: NonNullSpeculator = SimpleNonNullSpeculator() + override val nonNullSpeculator: NonNullSpeculator = SimpleNonNullSpeculator(), + override val staticInitializerConcreteProcessor: StaticInitializerConcreteProcessor = SimpleStaticInitializerConcreteProcessor ) : ApplicationContext { override fun createConcreteExecutionContext( fullClasspath: String, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleStaticInitializerConcreteProcessor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleStaticInitializerConcreteProcessor.kt new file mode 100644 index 0000000000..ae9d2388a8 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleStaticInitializerConcreteProcessor.kt @@ -0,0 +1,53 @@ +package org.utbot.framework.context.simple + +import org.utbot.common.WorkaroundReason +import org.utbot.common.workaround +import org.utbot.engine.classToWrapper +import org.utbot.engine.types.TypeResolver +import org.utbot.engine.util.statics.concrete.isEnumAffectingExternalStatics +import org.utbot.framework.context.StaticInitializerConcreteProcessor +import soot.jimple.StaticFieldRef + +object SimpleStaticInitializerConcreteProcessor : StaticInitializerConcreteProcessor { + override fun shouldProcessStaticFieldConcretely(fieldRef: StaticFieldRef, typeResolver: TypeResolver): Boolean = + workaround(WorkaroundReason.HACK) { + val className = fieldRef.field.declaringClass.name + + // We should process clinit sections for classes from these packages. + // Note that this list is not exhaustive, so it may be supplemented in the future. + val packagesToProcessConcretely = javaPackagesToProcessConcretely + sunPackagesToProcessConcretely + + val declaringClass = fieldRef.field.declaringClass + + val isFromPackageToProcessConcretely = packagesToProcessConcretely.any { className.startsWith(it) } + // it is required to remove classes we override, since + // we could accidentally initialize their final fields + // with values that will later affect our overridden classes + && fieldRef.field.declaringClass.type !in classToWrapper.keys + // because of the same reason we should not use + // concrete information from clinit sections for enums + && !fieldRef.field.declaringClass.isEnum + //hardcoded string for class name is used cause class is not public + //this is a hack to avoid crashing on code with Math.random() + && !className.endsWith("RandomNumberGeneratorHolder") + + // we can process concretely only enums that does not affect the external system + val isEnumNotAffectingExternalStatics = declaringClass.let { + it.isEnum && !it.isEnumAffectingExternalStatics(typeResolver) + } + + return isEnumNotAffectingExternalStatics || isFromPackageToProcessConcretely + } + + private val javaPackagesToProcessConcretely: List = listOf( + "applet", "awt", "beans", "io", "lang", "math", "net", + "nio", "rmi", "security", "sql", "text", "time", "util" + ).map { "java.$it" } + + private val sunPackagesToProcessConcretely: List = listOf( + "applet", "audio", "awt", "corba", "font", "instrument", + "invoke", "io", "java2d", "launcher", "management", "misc", + "net", "nio", "print", "reflect", "rmi", "security", + "swing", "text", "tools.jar", "tracing", "util" + ).map { "sun.$it" } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt index 4e9bf11553..0dd7d46778 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt @@ -9,6 +9,7 @@ import org.utbot.framework.codegen.generator.SpringCodeGenerator import org.utbot.framework.context.ApplicationContext import org.utbot.framework.context.ConcreteExecutionContext import org.utbot.framework.context.NonNullSpeculator +import org.utbot.framework.context.StaticInitializerConcreteProcessor import org.utbot.framework.context.TypeReplacer import org.utbot.framework.context.custom.CoverageFilteringConcreteExecutionContext import org.utbot.framework.plugin.api.BeanDefinitionData @@ -34,6 +35,7 @@ class SpringApplicationContextImpl( override val typeReplacer: TypeReplacer = SpringTypeReplacer(delegateContext.typeReplacer, this) override val nonNullSpeculator: NonNullSpeculator = SpringNonNullSpeculator(delegateContext.nonNullSpeculator, this) + override val staticInitializerConcreteProcessor: StaticInitializerConcreteProcessor = SpringStaticInitializerConcreteProcessor override var concreteContextLoadingResult: ConcreteContextLoadingResult? = null diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringStaticInitializerConcreteProcessor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringStaticInitializerConcreteProcessor.kt new file mode 100644 index 0000000000..f52404fd44 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringStaticInitializerConcreteProcessor.kt @@ -0,0 +1,23 @@ +package org.utbot.framework.context.spring + +import org.utbot.common.WorkaroundReason +import org.utbot.common.workaround +import org.utbot.engine.types.TypeResolver +import org.utbot.framework.context.StaticInitializerConcreteProcessor +import org.utbot.framework.context.simple.SimpleStaticInitializerConcreteProcessor +import soot.jimple.StaticFieldRef + +object SpringStaticInitializerConcreteProcessor : StaticInitializerConcreteProcessor { + override fun shouldProcessStaticFieldConcretely(fieldRef: StaticFieldRef, typeResolver: TypeResolver): Boolean = + workaround(WorkaroundReason.PROCESS_CONCRETELY_STATIC_INITIALIZERS_IN_ENUMS_FOR_SPRING) { + val declaringClass = fieldRef.field.declaringClass + + if (declaringClass.isEnum) { + // Since Spring projects have a lot of complicated enums, we cannot waste resources for theirs analysis, + // so always process theirs clinit sections concretely + return true + } + + return SimpleStaticInitializerConcreteProcessor.shouldProcessStaticFieldConcretely(fieldRef, typeResolver) + } +}