Skip to content
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
5 changes: 4 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ openblasVersion=0.3.10-1.5.4
arpackNgVersion=3.7.0-1.5.4
commonsLoggingVersion=1.2
commonsIOVersion=2.11.0
springBootVersion=2.7.8

# use latest Java 8 compaitable Spring and Spring Boot versions
springVersion=5.3.28
springBootVersion=2.7.13

# configuration for build server
#
Expand Down
33 changes: 33 additions & 0 deletions utbot-core/src/main/kotlin/org/utbot/common/AnnotationUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.utbot.common

import java.lang.reflect.InvocationHandler
import java.lang.reflect.Proxy

/**
* Assigns [newValue] to specified [property] of [annotation].
*
* NOTE! [annotation] instance is expected to be a [Proxy]
* using [sun.reflect.annotation.AnnotationInvocationHandler]
* making this function depend on JDK vendor and version.
*
* Example: `@ImportResource -> @ImportResource(value = "classpath:shark-config.xml")`
*/
fun patchAnnotation(
annotation: Annotation,
property: String,
newValue: Any?
) {
val proxyClass = Proxy::class.java
val hField = proxyClass.getDeclaredField("h")
hField.isAccessible = true

val invocationHandler = hField[annotation] as InvocationHandler

val annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler")
val memberValuesField = annotationInvocationHandlerClass.getDeclaredField("memberValues")
memberValuesField.isAccessible = true

@Suppress("UNCHECKED_CAST") // unavoidable because of reflection
val memberValues = memberValuesField[invocationHandler] as MutableMap<String, Any?>
memberValues[property] = newValue
}
1 change: 0 additions & 1 deletion utbot-instrumentation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ val kotlinLoggingVersion: String by rootProject
val rdVersion: String by rootProject
val mockitoVersion: String by rootProject
val mockitoInlineVersion: String by rootProject
val springBootVersion: String by rootProject

plugins {
id("com.github.johnrengelman.shadow") version "7.1.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package org.utbot.instrumentation.instrumentation.execution

import org.utbot.common.JarUtils
import com.jetbrains.rd.util.getLogger
import com.jetbrains.rd.util.info
import org.utbot.common.JarUtils
import org.utbot.common.hasOnClasspath
import org.utbot.framework.plugin.api.BeanDefinitionData
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.SpringRepositoryId
import org.utbot.framework.plugin.api.util.jClass
import org.utbot.instrumentation.instrumentation.ArgumentList
import org.utbot.instrumentation.instrumentation.Instrumentation
import org.utbot.instrumentation.instrumentation.execution.mock.SpringInstrumentationContext
import org.utbot.instrumentation.instrumentation.execution.context.SpringInstrumentationContext
import org.utbot.instrumentation.instrumentation.execution.phases.ExecutionPhaseFailingOnAnyException
import org.utbot.instrumentation.process.HandlerClassesLoader
import org.utbot.spring.api.context.ContextWrapper
import org.utbot.spring.api.repositoryWrapper.RepositoryInteraction
import org.utbot.spring.api.SpringApi
import java.net.URL
import java.net.URLClassLoader
import java.security.ProtectionDomain
Expand All @@ -32,7 +33,10 @@ class SpringUtExecutionInstrumentation(

private val relatedBeansCache = mutableMapOf<Class<*>, Set<String>>()

private val springContext: ContextWrapper get() = instrumentationContext.springContext
private val springApi: SpringApi get() = instrumentationContext.springApi

private object SpringBeforeTestMethodPhase : ExecutionPhaseFailingOnAnyException()
private object SpringAfterTestMethodPhase : ExecutionPhaseFailingOnAnyException()

companion object {
private val logger = getLogger<SpringUtExecutionInstrumentation>()
Expand All @@ -50,10 +54,11 @@ class SpringUtExecutionInstrumentation(
)
)

instrumentationContext = SpringInstrumentationContext(springConfig)
userSourcesClassLoader = URLClassLoader(buildDirs, null)
instrumentationContext = SpringInstrumentationContext(springConfig, delegateInstrumentation.instrumentationContext)
delegateInstrumentation.instrumentationContext = instrumentationContext
delegateInstrumentation.init(pathsToUserClasses)
springApi.beforeTestClass()
}

override fun invoke(
Expand All @@ -62,42 +67,36 @@ class SpringUtExecutionInstrumentation(
arguments: ArgumentList,
parameters: Any?
): UtConcreteExecutionResult {
RepositoryInteraction.recordedInteractions.clear()

val beanNamesToReset: Set<String> = getRelevantBeanNames(clazz)
val repositoryDefinitions = springContext.resolveRepositories(beanNamesToReset, userSourcesClassLoader)

beanNamesToReset.forEach { beanName -> springContext.resetBean(beanName) }
val jdbcTemplate = getBean("jdbcTemplate")

for (repositoryDefinition in repositoryDefinitions) {
val truncateTableCommand = "TRUNCATE TABLE ${repositoryDefinition.tableName}"
jdbcTemplate::class.java
.getMethod("execute", truncateTableCommand::class.java)
.invoke(jdbcTemplate, truncateTableCommand)

val restartIdCommand = "ALTER TABLE ${repositoryDefinition.tableName} ALTER COLUMN id RESTART WITH 1"
jdbcTemplate::class.java
.getMethod("execute", restartIdCommand::class.java)
.invoke(jdbcTemplate, restartIdCommand)
getRelevantBeans(clazz).forEach { beanName -> springApi.resetBean(beanName) }

return delegateInstrumentation.invoke(clazz, methodSignature, arguments, parameters) { invokeBasePhases ->
// NB! beforeTestMethod() and afterTestMethod() are intentionally called inside phases,
// so they are executed in one thread with method under test
executePhaseInTimeout(SpringBeforeTestMethodPhase) { springApi.beforeTestMethod() }
try {
invokeBasePhases()
} finally {
executePhaseInTimeout(SpringAfterTestMethodPhase) { springApi.afterTestMethod() }
}
}

return delegateInstrumentation.invoke(clazz, methodSignature, arguments, parameters)
}

private fun getRelevantBeanNames(clazz: Class<*>): Set<String> = relatedBeansCache.getOrPut(clazz) {
private fun getRelevantBeans(clazz: Class<*>): Set<String> = relatedBeansCache.getOrPut(clazz) {
beanDefinitions
.filter { it.beanTypeFqn == clazz.name }
.flatMap { springContext.getDependenciesForBean(it.beanName, userSourcesClassLoader) }
// forces `getBean()` to load Spring classes,
// otherwise execution of method under test may fail with timeout
.onEach { springApi.getBean(it.beanName) }
.flatMap { springApi.getDependenciesForBean(it.beanName, userSourcesClassLoader) }
.toSet()
.also { logger.info { "Detected relevant beans for class ${clazz.name}: $it" } }
}

fun getBean(beanName: String): Any = springContext.getBean(beanName)
fun getBean(beanName: String): Any = springApi.getBean(beanName)

fun getRepositoryDescriptions(classId: ClassId): Set<SpringRepositoryId> {
val relevantBeanNames = getRelevantBeanNames(classId.jClass)
val repositoryDescriptions = springContext.resolveRepositories(relevantBeanNames.toSet(), userSourcesClassLoader)
val relevantBeanNames = getRelevantBeans(classId.jClass)
val repositoryDescriptions = springApi.resolveRepositories(relevantBeanNames.toSet(), userSourcesClassLoader)
return repositoryDescriptions.map { repositoryDescription ->
SpringRepositoryId(
repositoryDescription.beanName,
Expand All @@ -114,19 +113,14 @@ class SpringUtExecutionInstrumentation(
protectionDomain: ProtectionDomain,
classfileBuffer: ByteArray
): ByteArray? =
// TODO: automatically detect which libraries we don't want to transform (by total transformation time)
if (listOf(
"org/springframework",
"com/fasterxml",
"org/hibernate",
"org/apache",
"org/h2",
"javax/",
"ch/qos",
).any { className.startsWith(it) }
) {
null
} else {
// we do not transform Spring classes as it takes too much time

// maybe we should still transform classes related to data validation
// (e.g. from packages "javax/persistence" and "jakarta/persistence"),
// since traces from such classes can be particularly useful for feedback to fuzzer
if (userSourcesClassLoader.hasOnClasspath(className.replace("/", "."))) {
delegateInstrumentation.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer)
} else {
null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import org.utbot.instrumentation.instrumentation.InvokeInstrumentation
import org.utbot.instrumentation.instrumentation.et.TraceHandler
import org.utbot.instrumentation.instrumentation.execution.constructors.ConstructOnlyUserClassesOrCachedObjectsStrategy
import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor
import org.utbot.instrumentation.instrumentation.execution.mock.InstrumentationContext
import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext
import org.utbot.instrumentation.instrumentation.execution.context.SimpleInstrumentationContext
import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicClassVisitor
import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicDetector
import org.utbot.instrumentation.instrumentation.execution.phases.ConstructedData
import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController
import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter
import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor
Expand Down Expand Up @@ -51,7 +51,7 @@ data class UtConcreteExecutionResult(
object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
private val delegateInstrumentation = InvokeInstrumentation()

var instrumentationContext = InstrumentationContext()
var instrumentationContext: InstrumentationContext = SimpleInstrumentationContext()

private val traceHandler = TraceHandler()
private val ndDetector = NonDeterministicDetector()
Expand All @@ -74,14 +74,14 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
arguments: ArgumentList,
parameters: Any?
): UtConcreteExecutionResult =
invoke(clazz, methodSignature, arguments, parameters, additionalPhases = { it })
invoke(clazz, methodSignature, arguments, parameters, phasesWrapper = { it() })

fun invoke(
clazz: Class<*>,
methodSignature: String,
arguments: ArgumentList,
parameters: Any?,
additionalPhases: PhasesController.(UtConcreteExecutionResult) -> UtConcreteExecutionResult
phasesWrapper: PhasesController.(invokeBasePhases: () -> UtConcreteExecutionResult) -> UtConcreteExecutionResult
): UtConcreteExecutionResult {
if (parameters !is UtConcreteExecutionData) {
throw IllegalArgumentException("Argument parameters must be of type UtConcreteExecutionData, but was: ${parameters?.javaClass}")
Expand All @@ -94,65 +94,62 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
delegateInstrumentation,
timeout
).computeConcreteExecutionResult {
try {
// some preparation actions for concrete execution
var constructedData: ConstructedData
phasesWrapper {
try {
constructedData = applyPreprocessing(parameters)
} catch (t: Throwable) {
return UtConcreteExecutionResult(MissingState, UtConcreteExecutionProcessedFailure(t), Coverage())
}

val (params, statics, cache) = constructedData

// invocation
val concreteResult = executePhaseInTimeout(invocationPhase) {
invoke(clazz, methodSignature, params.map { it.value })
}
// some preparation actions for concrete execution
val constructedData = applyPreprocessing(parameters)

// statistics collection
val (coverage, ndResults) = executePhaseInTimeout(statisticsCollectionPhase) {
getCoverage(clazz) to getNonDeterministicResults()
}
val (params, statics, cache) = constructedData

// model construction
val (executionResult, stateAfter, newInstrumentation) = executePhaseInTimeout(modelConstructionPhase) {
configureConstructor {
this.cache = cache
strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy(
pathsToUserClasses,
cache
)
// invocation
val concreteResult = executePhaseInTimeout(invocationPhase) {
invoke(clazz, methodSignature, params.map { it.value })
}

val ndStatics = constructStaticInstrumentation(ndResults.statics)
val ndNews = constructNewInstrumentation(ndResults.news, ndResults.calls)
val newInstrumentation = mergeInstrumentations(instrumentations, ndStatics, ndNews)

val returnType = clazz.singleExecutableId(methodSignature).returnType
val executionResult = convertToExecutionResult(concreteResult,returnType)
// statistics collection
val (coverage, ndResults) = executePhaseInTimeout(statisticsCollectionPhase) {
getCoverage(clazz) to getNonDeterministicResults()
}

val stateAfterParametersWithThis = constructParameters(params)
val stateAfterStatics = constructStatics(stateBefore, statics)
val (stateAfterThis, stateAfterParameters) = if (stateBefore.thisInstance == null) {
null to stateAfterParametersWithThis
} else {
stateAfterParametersWithThis.first() to stateAfterParametersWithThis.drop(1)
// model construction
val (executionResult, stateAfter, newInstrumentation) = executePhaseInTimeout(modelConstructionPhase) {
configureConstructor {
this.cache = cache
strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy(
pathsToUserClasses,
cache
)
}

val ndStatics = constructStaticInstrumentation(ndResults.statics)
val ndNews = constructNewInstrumentation(ndResults.news, ndResults.calls)
val newInstrumentation = mergeInstrumentations(instrumentations, ndStatics, ndNews)

val returnType = clazz.singleExecutableId(methodSignature).returnType
val executionResult = convertToExecutionResult(concreteResult, returnType)

val stateAfterParametersWithThis = constructParameters(params)
val stateAfterStatics = constructStatics(stateBefore, statics)
val (stateAfterThis, stateAfterParameters) = if (stateBefore.thisInstance == null) {
null to stateAfterParametersWithThis
} else {
stateAfterParametersWithThis.first() to stateAfterParametersWithThis.drop(1)
}
val stateAfter = EnvironmentModels(stateAfterThis, stateAfterParameters, stateAfterStatics)

Triple(executionResult, stateAfter, newInstrumentation)
}
val stateAfter = EnvironmentModels(stateAfterThis, stateAfterParameters, stateAfterStatics)

Triple(executionResult, stateAfter, newInstrumentation)
UtConcreteExecutionResult(
stateAfter,
executionResult,
coverage,
newInstrumentation
)
} finally {
// restoring data after concrete execution
applyPostprocessing()
}

additionalPhases(UtConcreteExecutionResult(
stateAfter,
executionResult,
coverage,
newInstrumentation
))
} finally {
// restoring data after concrete execution
applyPostprocessing()
}
}
}
Expand Down
Loading