Skip to content

Commit

Permalink
Let engine utContext.classLoader fallback to UtBot own class path #…
Browse files Browse the repository at this point in the history
…2523 (#2556)

* Let engine `utContext.classLoader` fallback to UtBot own class path

* Address minor comments from #2556
  • Loading branch information
IlyaMuravjov authored Aug 29, 2023
1 parent a505cae commit 48264f3
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 8 deletions.
99 changes: 99 additions & 0 deletions utbot-core/src/main/kotlin/org/utbot/common/FallbackClassLoader.kt
Original file line number Diff line number Diff line change
@@ -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<URL>,
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<URL> {
val allRes: MutableList<URL> = LinkedList<URL>()

// load resources from common parent loader
val commonParentResources: Enumeration<URL>? = commonParent.getResources(name)
if (commonParentResources != null) {
while (commonParentResources.hasMoreElements()) {
allRes.add(commonParentResources.nextElement())
}
}

// load resource from this classloader
val thisRes: Enumeration<URL>? = findResources(name)
if (thisRes != null) {
while (thisRes.hasMoreElements()) {
allRes.add(thisRes.nextElement())
}
}

// then try finding resources from fallback classloaders
val parentRes: Enumeration<URL>? = super.findResources(name)
if (parentRes != null) {
while (parentRes.hasMoreElements()) {
allRes.add(parentRes.nextElement())
}
}
return object : Enumeration<URL> {
var it: Iterator<URL> = 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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 48264f3

Please sign in to comment.