Skip to content

Commit 48264f3

Browse files
authored
Let engine utContext.classLoader fallback to UtBot own class path #2523 (#2556)
* Let engine `utContext.classLoader` fallback to UtBot own class path * Address minor comments from #2556
1 parent a505cae commit 48264f3

File tree

2 files changed

+114
-8
lines changed

2 files changed

+114
-8
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package org.utbot.common
2+
3+
import java.io.IOException
4+
import java.net.URL
5+
import java.net.URLClassLoader
6+
import java.util.*
7+
8+
/**
9+
* [ClassLoader] implementation, that
10+
* - first, attempts to load class/resource with [commonParent] class loader
11+
* - next, attempts to load class/resource from `urls`
12+
* - finally, attempts to load class/resource with `fallback` class loader
13+
*
14+
* More details can be found in [this post](https://medium.com/@isuru89/java-a-child-first-class-loader-cbd9c3d0305).
15+
*/
16+
class FallbackClassLoader(
17+
urls: Array<URL>,
18+
fallback: ClassLoader,
19+
private val commonParent: ClassLoader = fallback.parent,
20+
) : URLClassLoader(urls, fallback) {
21+
22+
@Throws(ClassNotFoundException::class)
23+
override fun loadClass(name: String, resolve: Boolean): Class<*>? {
24+
// has the class loaded already?
25+
var loadedClass = findLoadedClass(name)
26+
if (loadedClass == null) {
27+
try {
28+
loadedClass = commonParent.loadClass(name)
29+
} catch (ex: ClassNotFoundException) {
30+
// class not found in common parent loader... silently skipping
31+
}
32+
try {
33+
// find the class from given jar urls as in first constructor parameter.
34+
if (loadedClass == null) {
35+
loadedClass = findClass(name)
36+
}
37+
} catch (e: ClassNotFoundException) {
38+
// class is not found in the given urls.
39+
// Let's try it in fallback classloader.
40+
// If class is still not found, then this method will throw class not found ex.
41+
loadedClass = super.loadClass(name, resolve)
42+
}
43+
}
44+
if (resolve) { // marked to resolve
45+
resolveClass(loadedClass)
46+
}
47+
return loadedClass
48+
}
49+
50+
@Throws(IOException::class)
51+
override fun getResources(name: String): Enumeration<URL> {
52+
val allRes: MutableList<URL> = LinkedList<URL>()
53+
54+
// load resources from common parent loader
55+
val commonParentResources: Enumeration<URL>? = commonParent.getResources(name)
56+
if (commonParentResources != null) {
57+
while (commonParentResources.hasMoreElements()) {
58+
allRes.add(commonParentResources.nextElement())
59+
}
60+
}
61+
62+
// load resource from this classloader
63+
val thisRes: Enumeration<URL>? = findResources(name)
64+
if (thisRes != null) {
65+
while (thisRes.hasMoreElements()) {
66+
allRes.add(thisRes.nextElement())
67+
}
68+
}
69+
70+
// then try finding resources from fallback classloaders
71+
val parentRes: Enumeration<URL>? = super.findResources(name)
72+
if (parentRes != null) {
73+
while (parentRes.hasMoreElements()) {
74+
allRes.add(parentRes.nextElement())
75+
}
76+
}
77+
return object : Enumeration<URL> {
78+
var it: Iterator<URL> = allRes.iterator()
79+
override fun hasMoreElements(): Boolean {
80+
return it.hasNext()
81+
}
82+
83+
override fun nextElement(): URL {
84+
return it.next()
85+
}
86+
}
87+
}
88+
89+
override fun getResource(name: String): URL? {
90+
var res: URL? = commonParent.getResource(name)
91+
if (res === null) {
92+
res = findResource(name)
93+
}
94+
if (res === null) {
95+
res = super.getResource(name)
96+
}
97+
return res
98+
}
99+
}

utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import org.utbot.spring.process.SpringAnalyzerProcess
4343
import org.utbot.summary.summarizeAll
4444
import org.utbot.taint.TaintConfigurationProviderUserRules
4545
import java.io.File
46-
import java.net.URLClassLoader
4746
import java.nio.file.Paths
4847
import kotlin.reflect.jvm.kotlinFunction
4948
import kotlin.time.Duration.Companion.seconds
@@ -75,13 +74,21 @@ private var idCounter: Long = 0
7574
private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatchdog, realProtocol: IProtocol) {
7675
val model = this
7776
watchdog.measureTimeForActiveCall(setupUtContext, "UtContext setup") { params ->
78-
// - We use `ClassLoader.getSystemClassLoader().parent` as parent to let
79-
// classes like `javax.sql.DataSource` load from URLs like `jrt:/java.sql`.
80-
// - We do not use `ClassLoader.getSystemClassLoader()` itself to avoid utbot dependencies like Jackson
81-
// being used instead of user's dependencies, which is important, since they may have different versions.
82-
UtContext.setUtContext(UtContext(URLClassLoader(params.classpathForUrlsClassloader.map {
83-
File(it).toURI().toURL()
84-
}.toTypedArray(), ClassLoader.getSystemClassLoader().parent)))
77+
val urls = params.classpathForUrlsClassloader.map { File(it).toURI().toURL() }.toTypedArray()
78+
// - First, we try to load class/resource with platform class loader `ClassLoader.getSystemClassLoader().parent`
79+
// - at this step we load classes like
80+
// - `java.util.ArrayList` (from boostrap classloader)
81+
// - `javax.sql.DataSource` (from platform classloader)
82+
// - Next, we try to load class/resource from user class path
83+
// - at this step we load classes like class under test and other classes from user project and its dependencies
84+
// - Finally, if all else fails we try to load class/resource from UtBot classpath
85+
// - at this step we load classes from UtBot project and its dependencies (e.g. Mockito if user doesn't have it, see #2545)
86+
val classLoader = FallbackClassLoader(
87+
urls = urls,
88+
fallback = ClassLoader.getSystemClassLoader(),
89+
commonParent = ClassLoader.getSystemClassLoader().parent,
90+
)
91+
UtContext.setUtContext(UtContext(classLoader))
8592
}
8693
watchdog.measureTimeForActiveCall(getSpringBeanDefinitions, "Getting Spring bean definitions") { params ->
8794
try {

0 commit comments

Comments
 (0)