From 04ffab86a222cdb2234ad9e25b54c65ab15c0a64 Mon Sep 17 00:00:00 2001 From: Miguel Branco Date: Wed, 11 Sep 2024 21:48:27 +0200 Subject: [PATCH] Enforce classloader on engine creation. --- .../rawlabs/compiler/CompilerService.scala | 23 ++++++++++-- .../snapi/compiler/SnapiCompilerService.scala | 36 ++++++++++++------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/compiler/src/main/scala/com/rawlabs/compiler/CompilerService.scala b/compiler/src/main/scala/com/rawlabs/compiler/CompilerService.scala index 24a2ebd38..84b65e52e 100644 --- a/compiler/src/main/scala/com/rawlabs/compiler/CompilerService.scala +++ b/compiler/src/main/scala/com/rawlabs/compiler/CompilerService.scala @@ -40,8 +40,7 @@ object CompilerService { private val enginesLock = new Object private val enginesCache = mutable.HashMap[RawSettings, Engine]() - // Return engine and a flag indicating if the engine was created. - def getEngine()(implicit settings: RawSettings): (Engine, Boolean) = { + def getEngine(maybeClassLoader: Option[ClassLoader])(implicit settings: RawSettings): (Engine, Boolean) = { enginesLock.synchronized { enginesCache.get(settings) match { case Some(engine) => @@ -60,13 +59,31 @@ object CompilerService { // options.put("engine.CompilationFailureAction", "Diagnose") // options.put("compiler.LogInlinedTargets", "true") } - val engine = Engine.newBuilder().allowExperimentalOptions(true).options(options).build() + val engine = maybeClassLoader match { + case Some(classLoader) => + // If a class loader is provided, use it to create the engine. + val currentClassLoader = Thread.currentThread().getContextClassLoader + Thread.currentThread().setContextClassLoader(classLoader) + try { + Engine.newBuilder().allowExperimentalOptions(true).options(options).build() + } finally { + Thread.currentThread().setContextClassLoader(currentClassLoader) + } + case None => + // If no class loader is provided, create the engine without it. + Engine.newBuilder().allowExperimentalOptions(true).options(options).build() + } enginesCache.put(settings, engine) (engine, true) } } } + // Return engine and a flag indicating if the engine was created. + def getEngine()(implicit settings: RawSettings): (Engine, Boolean) = { + getEngine(None) + } + def releaseEngine()(implicit settings: RawSettings): Unit = { enginesLock.synchronized { enginesCache.remove(settings).foreach(engine => engine.close(true)) diff --git a/snapi-compiler/src/main/scala/com/rawlabs/snapi/compiler/SnapiCompilerService.scala b/snapi-compiler/src/main/scala/com/rawlabs/snapi/compiler/SnapiCompilerService.scala index d38337e20..3b6097fa5 100644 --- a/snapi-compiler/src/main/scala/com/rawlabs/snapi/compiler/SnapiCompilerService.scala +++ b/snapi-compiler/src/main/scala/com/rawlabs/snapi/compiler/SnapiCompilerService.scala @@ -54,6 +54,7 @@ import com.rawlabs.compiler.{ ValidateResponse } import com.rawlabs.compiler.writers.{PolyglotBinaryWriter, PolyglotTextWriter} +import com.rawlabs.snapi.compiler.SnapiCompilerService.getTruffleClassLoader import com.rawlabs.snapi.compiler.writers.{SnapiCsvWriter, SnapiJsonWriter} import com.rawlabs.utils.core.{RawSettings, RawUid, RawUtils} import org.bitbucket.inkytonik.kiama.relation.LeaveAlone @@ -75,25 +76,34 @@ import java.io.{IOException, OutputStream} import scala.collection.mutable import scala.util.control.NonFatal -object SnapiCompilerService { +object SnapiCompilerService extends CustomClassAndModuleLoader { val LANGUAGE: Set[String] = Set("snapi") - val JARS_PATH = "raw.snapi.compiler.jars-path" + private val JARS_PATH = "raw.snapi.compiler.jars-path" + + private var maybeTruffleClassLoader: Option[ClassLoader] = _ + private val maybeTruffleClassLoaderLock = new Object + + def getTruffleClassLoader(implicit settings: RawSettings): Option[ClassLoader] = { + maybeTruffleClassLoaderLock.synchronized { + if (maybeTruffleClassLoader == null) { + // If defined, contains the path used to create a classloader for the Truffle language runtime. + val maybeJarsPath = settings.getStringOpt(JARS_PATH) + maybeJarsPath match { + case Some(jarsPath) => maybeTruffleClassLoader = Some(createCustomClassAndModuleLoader(jarsPath)) + case None => maybeTruffleClassLoader = None + } + } + maybeTruffleClassLoader + } + } + } class SnapiCompilerService(engineDefinition: (Engine, Boolean))(implicit protected val settings: RawSettings) extends CompilerService - with CustomClassAndModuleLoader with SnapiTypeUtils { - private val maybeTruffleClassLoader: Option[ClassLoader] = { - // If defined, contains the path used to create a classloader for the Truffle language runtime. - val maybeJarsPath = settings.getStringOpt(SnapiCompilerService.JARS_PATH) - - // If the jars path is defined, create a custom class loader. - maybeJarsPath.map(jarsPath => createCustomClassAndModuleLoader(jarsPath)) - } - private val (engine, initedEngine) = engineDefinition // The default constructor allows an Engine to be specified, plus a flag to indicate whether it was created here @@ -104,7 +114,7 @@ class SnapiCompilerService(engineDefinition: (Engine, Boolean))(implicit protect // Refer to SnapiTruffleCompilerServiceTestContext to see the engine being created and released from the test // framework, so that every test suite instance has a fresh engine. def this()(implicit settings: RawSettings) = { - this(CompilerService.getEngine) + this(CompilerService.getEngine(SnapiCompilerService.getTruffleClassLoader)) } override def language: Set[String] = SnapiCompilerService.LANGUAGE @@ -683,7 +693,7 @@ class SnapiCompilerService(engineDefinition: (Engine, Boolean))(implicit protect } ctxBuilder.option("snapi.settings", settings.renderAsString) // If the jars path is defined, create a custom class loader and set it as the host class loader. - maybeTruffleClassLoader.map { classLoader => + getTruffleClassLoader.map { classLoader => // Set the module class loader as the Truffle runtime classloader. // This enables the Truffle language runtime to be fully isolated from the rest of the application. ctxBuilder.hostClassLoader(classLoader)