From 938eab16f841fee67b2e34c983a7a2a6a5998127 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 3 Nov 2011 04:51:52 +0000 Subject: ScalaClassLoader changes. Lots of fiddling in the interests of a better classloading future. --- src/compiler/scala/tools/nsc/Global.scala | 9 +- .../tools/nsc/backend/jvm/BytecodeWriters.scala | 2 +- .../scala/tools/nsc/backend/jvm/GenJVM.scala | 1 - .../scala/tools/nsc/interpreter/ByteCode.scala | 4 +- .../scala/tools/nsc/interpreter/ILoop.scala | 4 +- .../scala/tools/nsc/interpreter/IMain.scala | 13 +- .../scala/tools/nsc/interpreter/Line.scala | 27 ++--- .../scala/tools/nsc/io/ClassAndJarInfo.scala | 15 +-- src/compiler/scala/tools/nsc/io/package.scala | 5 +- src/compiler/scala/tools/nsc/util/ClassPath.scala | 13 +- .../scala/tools/nsc/util/ScalaClassLoader.scala | 133 +++++++++++++-------- 11 files changed, 138 insertions(+), 88 deletions(-) (limited to 'src/compiler/scala/tools/nsc') diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index b798fe71ba..602ff6bf0e 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -1325,10 +1325,11 @@ object Global { // !!! The classpath isn't known until the Global is created, which is too // late, so we have to duplicate it here. Classpath is too tightly coupled, // it is a construct external to the compiler and should be treated as such. - val loader = ScalaClassLoader.fromURLs(new PathResolver(settings).result.asURLs) - val name = settings.globalClass.value - val clazz = Class.forName(name, true, loader) - val cons = clazz.getConstructor(classOf[Settings], classOf[Reporter]) + val parentLoader = settings.explicitParentLoader getOrElse getClass.getClassLoader + val loader = ScalaClassLoader.fromURLs(new PathResolver(settings).result.asURLs, parentLoader) + val name = settings.globalClass.value + val clazz = Class.forName(name, true, loader) + val cons = clazz.getConstructor(classOf[Settings], classOf[Reporter]) cons.newInstance(settings, reporter).asInstanceOf[Global] } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala index 228d4c6191..5b6e37e4fe 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala @@ -67,7 +67,7 @@ trait BytecodeWriters { def emitJavap(bytes: Array[Byte], javapFile: io.File) { val pw = javapFile.printWriter() - val javap = new Javap(ScalaClassLoader.getSystemLoader(), pw) { + val javap = new Javap(ScalaClassLoader.appLoader, pw) { override def findBytes(path: String): Array[Byte] = bytes } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 1b6b50a793..0bd7109a0d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -12,7 +12,6 @@ import java.nio.ByteBuffer import scala.collection.{ mutable, immutable } import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } import scala.tools.reflect.SigParser -import scala.tools.nsc.util.ScalaClassLoader import scala.tools.nsc.symtab._ import scala.reflect.internal.ClassfileConstants._ import ch.epfl.lamp.fjbg._ diff --git a/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala b/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala index 90d8fbb356..3059a8fb10 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala @@ -9,7 +9,7 @@ package interpreter import java.lang.reflect import java.util.concurrent.ConcurrentHashMap import util.ScalaClassLoader -import ScalaClassLoader.getSystemLoader +import ScalaClassLoader.appLoader import scala.reflect.NameTransformer._ object ByteCode { @@ -17,7 +17,7 @@ object ByteCode { * that the compiler will bootstrap, we have to use reflection. */ private lazy val DECODER: Option[AnyRef] = - for (clazz <- getSystemLoader.tryToLoadClass[AnyRef]("scala.tools.scalap.Decode$")) yield + for (clazz <- appLoader.tryToLoadClass[AnyRef]("scala.tools.scalap.Decode$")) yield clazz.getField(MODULE_INSTANCE_NAME).get(null) private def decoderMethod(name: String, args: JClass*): Option[reflect.Method] = { diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index 625bf2ae05..1f19740353 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -93,11 +93,13 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } class ILoopInterpreter extends IMain(settings, out) { + outer => + override lazy val formatting = new Formatting { def prompt = ILoop.this.prompt } override protected def createLineManager(): Line.Manager = - if (ReplPropsKludge.noThreadCreation(settings)) null else new Line.Manager { + if (ReplPropsKludge.noThreadCreation(settings)) null else new Line.Manager(outer.classLoader) { override def onRunaway(line: Line[_]): Unit = { val template = """ |// She's gone rogue, captain! Have to take her out! diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index e93d4897ca..b092574eaf 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -8,11 +8,7 @@ package interpreter import Predef.{ println => _, _ } import util.{ Set => _, _ } -import scala.collection.{ mutable, immutable } import scala.sys.BooleanProp -import Exceptional.unwrap -import ScalaClassLoader.URLClassLoader -import symtab.Flags import io.VirtualDirectory import scala.tools.nsc.io.AbstractFile import reporters._ @@ -23,7 +19,6 @@ import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional, Indenter } import ScalaClassLoader.URLClassLoader import Exceptional.unwrap import scala.collection.{ mutable, immutable } -import scala.PartialFunction.{ cond, condOpt } import scala.util.control.Exception.{ ultimately } import IMain._ @@ -257,14 +252,17 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends def setExecutionWrapper(code: String) = _executionWrapper = code def clearExecutionWrapper() = _executionWrapper = "" - lazy val lineManager = createLineManager() + private var _lineManager: Line.Manager = createLineManager() + def lineManager = _lineManager /** interpreter settings */ lazy val isettings = new ISettings(this) /** Create a line manager. Overridable. */ protected def createLineManager(): Line.Manager = - if (ReplPropsKludge.noThreadCreation(settings)) null else new Line.Manager + createLineManager(classLoader) + protected def createLineManager(loader: ClassLoader): Line.Manager = + if (ReplPropsKludge.noThreadCreation(settings)) null else new Line.Manager(loader) /** Instantiate a compiler. Overridable. */ protected def newCompiler(settings: Settings, reporter: Reporter) = { @@ -298,6 +296,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends def resetClassLoader() = { repldbg("Setting new classloader: was " + _classLoader) _classLoader = makeClassLoader() + _lineManager = createLineManager(_classLoader) } def classLoader: AbstractFileClassLoader = { if (_classLoader == null) diff --git a/src/compiler/scala/tools/nsc/interpreter/Line.scala b/src/compiler/scala/tools/nsc/interpreter/Line.scala index deaeb913d2..deec9fcc4e 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Line.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Line.scala @@ -18,7 +18,7 @@ import Line._ * waits on a condition indicating that either the line has * completed or failed. */ -class Line[+T](val code: String, body: => T) { +class Line[+T](val code: String, classLoader: ClassLoader, body: => T) { private var _state: State = Running private var _result: Any = null private var _caught: Throwable = null @@ -37,19 +37,18 @@ class Line[+T](val code: String, body: => T) { // private because it should be called by the manager. private def cancel() = if (running) setState(Cancelled) - // This is where the line thread is created and started. - private val _thread = io.daemonize { - try { - _result = body - setState(Done) - } - catch { - case x => - _caught = x - setState(Threw) - } + private def runAndSetState[T](body: => T) { + var ex: Throwable = null + + try { _result = body } + catch { case t => ex = t } + finally { setState( if (ex == null) Done else Threw ) } } + // This is where the line thread is created and started. + private val _thread: Thread = + io.newThread(_ setContextClassLoader classLoader)(runAndSetState(body)) + def state = _state def thread = _thread def alive = thread.isAlive @@ -76,7 +75,7 @@ object Line { case object Cancelled extends State case object Done extends State - class Manager { + class Manager(classLoader: ClassLoader) { /** Override to add behavior for runaway lines. This method will * be called if a line thread is still running five seconds after * it has been cancelled. @@ -91,7 +90,7 @@ object Line { _current = None } def set[T](code: String)(body: => T) = { - val line = new Line(code, body) + val line = new Line(code, classLoader, body) _current = Some(line) line } diff --git a/src/compiler/scala/tools/nsc/io/ClassAndJarInfo.scala b/src/compiler/scala/tools/nsc/io/ClassAndJarInfo.scala index 716358a653..d0a0b17494 100644 --- a/src/compiler/scala/tools/nsc/io/ClassAndJarInfo.scala +++ b/src/compiler/scala/tools/nsc/io/ClassAndJarInfo.scala @@ -6,7 +6,7 @@ package scala.tools.nsc package io -import java.net.URL +import java.net.{ URL, URLClassLoader } import java.io.IOException import collection.JavaConverters._ @@ -14,18 +14,20 @@ import collection.JavaConverters._ * a given Class object and similar common tasks. */ class ClassAndJarInfo[T: ClassManifest] { - val man = classManifest[T] - def clazz = man.erasure + val man = classManifest[T] + def clazz = man.erasure + def internalName = clazz.getName.replace('.', '/') + + def resourceURL = new URLClassLoader(Array[URL]()) getResource internalName + ".class" def baseOfPath(path: String) = path indexOf '!' match { - case -1 => path stripSuffix internalClassName + case -1 => path stripSuffix internalName + ".class" case idx => path take idx } + def simpleClassName = clazz.getName split """[$.]""" last def classUrl = clazz getResource simpleClassName + ".class" def codeSource = protectionDomain.getCodeSource() - def internalClassName = internalName + ".class" - def internalName = clazz.getName.replace('.', '/') def jarManifest = ( try new JManifest(jarManifestUrl.openStream()) catch { case _: IOException => new JManifest() } @@ -39,5 +41,4 @@ class ClassAndJarInfo[T: ClassManifest] { def rootFromLocation = Path(locationUrl.toURI.getPath()) def rootFromResource = Path(baseOfPath(classUrl.getPath) stripPrefix "file:") def rootPossibles = Iterator(rootFromResource, rootFromLocation) - def simpleClassName = clazz.getName split """[$.]""" last } diff --git a/src/compiler/scala/tools/nsc/io/package.scala b/src/compiler/scala/tools/nsc/io/package.scala index d0a1d88086..88679e6dce 100644 --- a/src/compiler/scala/tools/nsc/io/package.scala +++ b/src/compiler/scala/tools/nsc/io/package.scala @@ -25,9 +25,10 @@ package object io { def spawnFn[T](f: () => T): Future[T] = spawn(f()) // Create, start, and return a daemon thread - def daemonize(body: => Unit): Thread = { + def daemonize(body: => Unit): Thread = newThread(_ setDaemon true)(body) + def newThread(f: Thread => Unit)(body: => Unit): Thread = { val thread = new Thread(runnable(body)) - thread setDaemon true + f(thread) thread.start thread } diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index 1487b42843..1820bd190f 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -464,5 +464,16 @@ extends ClassPath[T] { class JavaClassPath( containers: IndexedSeq[ClassPath[AbstractFile]], context: JavaContext) -extends MergedClassPath[AbstractFile](containers, context) { +extends MergedClassPath[AbstractFile](containers, context) { } + +object JavaClassPath { + def fromURLs(urls: Seq[URL], context: JavaContext): JavaClassPath = { + val containers = { + for (url <- urls ; f = AbstractFile getURL url ; if f != null) yield + new DirectoryClassPath(f, context) + } + new JavaClassPath(containers.toIndexedSeq, context) + } + def fromURLs(urls: Seq[URL]): JavaClassPath = + fromURLs(urls, ClassPath.DefaultJavaContext) } diff --git a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala index 07fe254410..d048b75599 100644 --- a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala +++ b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala @@ -6,26 +6,34 @@ package scala.tools.nsc package util -import java.lang.{ ClassLoader => JavaClassLoader } +import java.lang.{ ClassLoader => JClassLoader } import java.lang.reflect.{ Constructor, Modifier, Method } +import java.io.{ File => JFile } +import java.net.{ URLClassLoader => JURLClassLoader } import java.net.URL +import scala.reflect.unwrapForHandler import ScalaClassLoader._ import scala.util.control.Exception.{ catching } +// import Exceptional.unwrap -trait ScalaClassLoader extends JavaClassLoader { +trait HasClassPath { + def classPathURLs: Seq[URL] +} + +/** A wrapper around java.lang.ClassLoader to lower the annoyance + * of java reflection. + */ +trait ScalaClassLoader extends JClassLoader { /** Override to see classloader activity traced */ protected def trace: Boolean = false /** Executing an action with this classloader as context classloader */ def asContext[T](action: => T): T = { - val oldLoader = getContextLoader - try { - setContextLoader(this) - action - } - finally setContextLoader(oldLoader) + val saved = contextLoader + try { setContext(this) ; action } + finally setContext(saved) } - def setAsContext() { setContextLoader(this) } + def setAsContext() { setContext(this) } /** Load and link a class with this classloader */ def tryToLoadClass[T <: AnyRef](path: String): Option[Class[T]] = tryClass(path, false) @@ -70,68 +78,97 @@ trait ScalaClassLoader extends JavaClassLoader { val clsToRun = tryToInitializeClass(objectName) getOrElse ( throw new ClassNotFoundException(objectName) ) - val method = clsToRun.getMethod("main", classOf[Array[String]]) if (!Modifier.isStatic(method.getModifiers)) throw new NoSuchMethodException(objectName + ".main is not static") - asContext(method.invoke(null, Array(arguments.toArray: AnyRef): _*)) // !!! : AnyRef shouldn't be necessary + try asContext(method.invoke(null, Array(arguments.toArray: AnyRef): _*)) // !!! : AnyRef shouldn't be necessary + catch unwrapForHandler({ case ex => throw ex }) } + + /** A list comprised of this classloader followed by all its + * (non-null) parent classloaders, if any. + */ + def loaderChain: List[ScalaClassLoader] = this :: (getParent match { + case null => Nil + case p => p.loaderChain + }) } +/** Methods for obtaining various classloaders. + * appLoader: the application classloader. (Also called the java system classloader.) + * extLoader: the extension classloader. + * bootLoader: the boot classloader. + * contextLoader: the context classloader. + */ object ScalaClassLoader { - implicit def apply(cl: JavaClassLoader): ScalaClassLoader = { - val loader = if (cl == null) JavaClassLoader.getSystemClassLoader() else cl - new JavaClassLoader(loader) with ScalaClassLoader + /** Returns loaders which are already ScalaClassLoaders unaltered, + * and translates java.net.URLClassLoaders into scala URLClassLoaders. + * Otherwise creates a new wrapper. + */ + implicit def apply(cl: JClassLoader): ScalaClassLoader = cl match { + case cl: ScalaClassLoader => cl + case cl: JURLClassLoader => new URLClassLoader(cl.getURLs.toSeq, cl.getParent) + case _ => new JClassLoader(cl) with ScalaClassLoader + } + def contextLoader = apply(Thread.currentThread.getContextClassLoader) + def appLoader = apply(JClassLoader.getSystemClassLoader) + def extLoader = apply(appLoader.getParent) + def bootLoader = apply(null) + def contextChain = loaderChain(contextLoader) + + def pathToErasure[T: ClassManifest] = pathToClass(classManifest[T].erasure) + def pathToClass(clazz: Class[_]) = clazz.getName.replace('.', JFile.separatorChar) + ".class" + def locate[T: ClassManifest] = contextLoader getResource pathToErasure[T] + + /** Tries to guess the classpath by type matching the context classloader + * and its parents, looking for any classloaders which will reveal their + * classpath elements as urls. It it can't find any, creates a classpath + * from the supplied string. + */ + def guessClassPathString(default: String = ""): String = { + val classpathURLs = contextChain flatMap { + case x: HasClassPath => x.classPathURLs + case x: JURLClassLoader => x.getURLs.toSeq + case _ => Nil + } + if (classpathURLs.isEmpty) default + else JavaClassPath.fromURLs(classpathURLs).asClasspathString } - class URLClassLoader(urls: Seq[URL], parent: JavaClassLoader) - extends java.net.URLClassLoader(urls.toArray, parent) - with ScalaClassLoader { + def loaderChain(head: JClassLoader) = { + def loop(cl: JClassLoader): List[JClassLoader] = + if (cl == null) Nil else cl :: loop(cl.getParent) - private var classloaderURLs = urls.toList + loop(head) + } + def setContext(cl: JClassLoader) = + Thread.currentThread.setContextClassLoader(cl) + + class URLClassLoader(urls: Seq[URL], parent: JClassLoader) + extends JURLClassLoader(urls.toArray, parent) + with ScalaClassLoader + with HasClassPath { + + private var classloaderURLs: Seq[URL] = urls private def classpathString = ClassPath.fromURLs(urls: _*) + def classPathURLs: Seq[URL] = classloaderURLs + def classPath: ClassPath[_] = JavaClassPath fromURLs classPathURLs /** Override to widen to public */ override def addURL(url: URL) = { - classloaderURLs +:= url + classloaderURLs :+= url super.addURL(url) } - override def run(objectName: String, arguments: Seq[String]) { - try super.run(objectName, arguments) - catch { case x: ClassNotFoundException => - throw new ClassNotFoundException(objectName + - " (args = %s, classpath = %s)".format(arguments mkString ", ", classpathString)) - } - } override def toString = urls.mkString("URLClassLoader(\n ", "\n ", "\n)\n") } - def setContextLoader(cl: JavaClassLoader) = Thread.currentThread.setContextClassLoader(cl) - def getContextLoader() = Thread.currentThread.getContextClassLoader() - def getSystemLoader(): ScalaClassLoader = ScalaClassLoader(null) - def defaultParentClassLoader() = findExtClassLoader() - - def fromURLs(urls: Seq[URL], parent: ClassLoader = defaultParentClassLoader()): URLClassLoader = - new URLClassLoader(urls.toList, parent) + def fromURLs(urls: Seq[URL], parent: ClassLoader = null): URLClassLoader = + new URLClassLoader(urls, parent) /** True if supplied class exists in supplied path */ def classExists(urls: Seq[URL], name: String): Boolean = - (fromURLs(urls) tryToLoadClass name).isDefined - - // we cannot use the app classloader here or we get what looks to - // be classloader deadlock, but if we pass null we bypass the extension - // classloader and our extensions, so we search the hierarchy to find - // the classloader whose parent is null. Resolves bug #857. - def findExtClassLoader(): JavaClassLoader = { - def search(cl: JavaClassLoader): JavaClassLoader = { - if (cl == null) null - else if (cl.getParent == null) cl - else search(cl.getParent) - } - - search(getContextLoader()) - } + fromURLs(urls) tryToLoadClass name isDefined /** Finding what jar a clazz or instance came from */ def origin(x: Any): Option[URL] = originOfClass(x.getClass) -- cgit v1.2.3