diff --git a/utbot-core/src/main/kotlin/org/utbot/common/FallbackClassLoader.kt b/utbot-core/src/main/kotlin/org/utbot/common/FallbackClassLoader.kt new file mode 100644 index 0000000000..044bdb93e2 --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/FallbackClassLoader.kt @@ -0,0 +1,99 @@ +package org.utbot.common + +import java.io.IOException +import java.net.URL +import java.net.URLClassLoader +import java.util.* + +/** + * [ClassLoader] implementation, that + * - first, attempts to load class/resource with [commonParent] class loader + * - next, attempts to load class/resource from `urls` + * - finally, attempts to load class/resource with `fallback` class loader + * + * More details can be found in [this post](https://medium.com/@isuru89/java-a-child-first-class-loader-cbd9c3d0305). + */ +class FallbackClassLoader( + urls: Array, + fallback: ClassLoader, + private val commonParent: ClassLoader = fallback.parent, +) : URLClassLoader(urls, fallback) { + + @Throws(ClassNotFoundException::class) + override fun loadClass(name: String, resolve: Boolean): Class<*>? { + // has the class loaded already? + var loadedClass = findLoadedClass(name) + if (loadedClass == null) { + try { + loadedClass = commonParent.loadClass(name) + } catch (ex: ClassNotFoundException) { + // class not found in common parent loader... silently skipping + } + try { + // find the class from given jar urls as in first constructor parameter. + if (loadedClass == null) { + loadedClass = findClass(name) + } + } catch (e: ClassNotFoundException) { + // class is not found in the given urls. + // Let's try it in fallback classloader. + // If class is still not found, then this method will throw class not found ex. + loadedClass = super.loadClass(name, resolve) + } + } + if (resolve) { // marked to resolve + resolveClass(loadedClass) + } + return loadedClass + } + + @Throws(IOException::class) + override fun getResources(name: String): Enumeration { + val allRes: MutableList = LinkedList() + + // load resources from common parent loader + val commonParentResources: Enumeration? = commonParent.getResources(name) + if (commonParentResources != null) { + while (commonParentResources.hasMoreElements()) { + allRes.add(commonParentResources.nextElement()) + } + } + + // load resource from this classloader + val thisRes: Enumeration? = findResources(name) + if (thisRes != null) { + while (thisRes.hasMoreElements()) { + allRes.add(thisRes.nextElement()) + } + } + + // then try finding resources from fallback classloaders + val parentRes: Enumeration? = super.findResources(name) + if (parentRes != null) { + while (parentRes.hasMoreElements()) { + allRes.add(parentRes.nextElement()) + } + } + return object : Enumeration { + var it: Iterator = allRes.iterator() + override fun hasMoreElements(): Boolean { + return it.hasNext() + } + + override fun nextElement(): URL { + return it.next() + } + } + } + + override fun getResource(name: String): URL? { + var res: URL? = commonParent.getResource(name) + if (res === null) { + res = findResource(name) + } + if (res === null) { + res = super.getResource(name) + } + return res + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt index 56db31e1ee..6cde1f9cb8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt @@ -43,7 +43,6 @@ import org.utbot.spring.process.SpringAnalyzerProcess import org.utbot.summary.summarizeAll import org.utbot.taint.TaintConfigurationProviderUserRules import java.io.File -import java.net.URLClassLoader import java.nio.file.Paths import kotlin.reflect.jvm.kotlinFunction import kotlin.time.Duration.Companion.seconds @@ -75,13 +74,21 @@ private var idCounter: Long = 0 private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatchdog, realProtocol: IProtocol) { val model = this watchdog.measureTimeForActiveCall(setupUtContext, "UtContext setup") { params -> - // - We use `ClassLoader.getSystemClassLoader().parent` as parent to let - // classes like `javax.sql.DataSource` load from URLs like `jrt:/java.sql`. - // - We do not use `ClassLoader.getSystemClassLoader()` itself to avoid utbot dependencies like Jackson - // being used instead of user's dependencies, which is important, since they may have different versions. - UtContext.setUtContext(UtContext(URLClassLoader(params.classpathForUrlsClassloader.map { - File(it).toURI().toURL() - }.toTypedArray(), ClassLoader.getSystemClassLoader().parent))) + val urls = params.classpathForUrlsClassloader.map { File(it).toURI().toURL() }.toTypedArray() + // - First, we try to load class/resource with platform class loader `ClassLoader.getSystemClassLoader().parent` + // - at this step we load classes like + // - `java.util.ArrayList` (from boostrap classloader) + // - `javax.sql.DataSource` (from platform classloader) + // - Next, we try to load class/resource from user class path + // - at this step we load classes like class under test and other classes from user project and its dependencies + // - Finally, if all else fails we try to load class/resource from UtBot classpath + // - at this step we load classes from UtBot project and its dependencies (e.g. Mockito if user doesn't have it, see #2545) + val classLoader = FallbackClassLoader( + urls = urls, + fallback = ClassLoader.getSystemClassLoader(), + commonParent = ClassLoader.getSystemClassLoader().parent, + ) + UtContext.setUtContext(UtContext(classLoader)) } watchdog.measureTimeForActiveCall(getSpringBeanDefinitions, "Getting Spring bean definitions") { params -> try {