Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce classloader on engine creation. #510

Merged
merged 1 commit into from
Sep 11, 2024
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
23 changes: 20 additions & 3 deletions compiler/src/main/scala/com/rawlabs/compiler/CompilerService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand All @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down