diff --git a/repl/src/dotty/tools/repl/AbstractFileClassLoader.scala b/repl/src/dotty/tools/repl/AbstractFileClassLoader.scala index 1bde97621897..95050cad82c5 100644 --- a/repl/src/dotty/tools/repl/AbstractFileClassLoader.scala +++ b/repl/src/dotty/tools/repl/AbstractFileClassLoader.scala @@ -15,30 +15,37 @@ package repl import scala.language.unsafeNulls +import dotty.tools.dotc.config.ScalaSettings + import io.AbstractFile import java.net.{URL, URLConnection, URLStreamHandler} import java.util.Collections -class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader, interruptInstrumentation: String) extends ClassLoader(parent): - private def findAbstractFile(name: String) = root.lookupPath(name.split('/').toIndexedSeq, directory = false) - - // on JDK 20 the URL constructor we're using is deprecated, - // but the recommended replacement, URL.of, doesn't exist on JDK 8 - @annotation.nowarn("cat=deprecation") - override protected def findResource(name: String): URL | Null = - findAbstractFile(name) match - case null => null - case file => new URL(null, s"memory:${file.path}", new URLStreamHandler { - override def openConnection(url: URL): URLConnection = new URLConnection(url) { - override def connect() = () - override def getInputStream = file.input - } - }) - override protected def findResources(name: String): java.util.Enumeration[URL] = - findResource(name) match - case null => Collections.enumeration(Collections.emptyList[URL]) //Collections.emptyEnumeration[URL] - case url => Collections.enumeration(Collections.singleton(url)) +import AbstractFileClassLoader.InterruptInstrumentation + + +object AbstractFileClassLoader: + enum InterruptInstrumentation(val stringValue: String): + case Disabled extends InterruptInstrumentation("false") + case Enabled extends InterruptInstrumentation("true") + case Local extends InterruptInstrumentation("local") + + def is(value: InterruptInstrumentation): Boolean = this == value + def isOneOf(others: InterruptInstrumentation*): Boolean = others.contains(this) + + object InterruptInstrumentation: + def fromString(string: String): InterruptInstrumentation = string match { + case "false" => Disabled + case "true" => Enabled + case "local" => Local + case _ => throw new IllegalArgumentException(s"Invalid interrupt instrumentation value: $string") + } + +class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader, interruptInstrumentation: InterruptInstrumentation) + extends io.AbstractFileClassLoader(root, parent): + + def this(root: AbstractFile, parent: ClassLoader) = this(root, parent, InterruptInstrumentation.fromString(ScalaSettings.XreplInterruptInstrumentation.default)) override def findClass(name: String): Class[?] = { var file: AbstractFile | Null = root @@ -52,23 +59,23 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader, inter val bytes = file.toByteArray - if interruptInstrumentation != "false" then defineClassInstrumented(name, bytes) + if !interruptInstrumentation.is(InterruptInstrumentation.Enabled) then defineClassInstrumented(name, bytes) else defineClass(name, bytes, 0, bytes.length) } - def defineClassInstrumented(name: String, originalBytes: Array[Byte]) = { + private def defineClassInstrumented(name: String, originalBytes: Array[Byte]) = { val instrumentedBytes = ReplBytecodeInstrumentation.instrument(originalBytes) defineClass(name, instrumentedBytes, 0, instrumentedBytes.length) } override def loadClass(name: String): Class[?] = - if interruptInstrumentation == "false" || interruptInstrumentation == "local" - then return super.loadClass(name) + if interruptInstrumentation.isOneOf(InterruptInstrumentation.Disabled, InterruptInstrumentation.Local) then + return super.loadClass(name) val loaded = findLoadedClass(name) // Check if already loaded if loaded != null then return loaded - name match { + name match { // Don't instrument JDK classes or StopRepl. These are often restricted to load from a single classloader // due to the JDK module system, and so instrumenting them and loading the modified copy of the class // results in runtime exceptions diff --git a/repl/src/dotty/tools/repl/DependencyResolver.scala b/repl/src/dotty/tools/repl/DependencyResolver.scala index a830ce6007f3..e67a4caeeacb 100644 --- a/repl/src/dotty/tools/repl/DependencyResolver.scala +++ b/repl/src/dotty/tools/repl/DependencyResolver.scala @@ -7,6 +7,8 @@ import java.net.{URL, URLClassLoader} import scala.jdk.CollectionConverters.* import scala.util.control.NonFatal +import dotty.tools.repl.AbstractFileClassLoader + import coursierapi.{Repository, Dependency, MavenRepository} import com.virtuslab.using_directives.UsingDirectivesProcessor import com.virtuslab.using_directives.custom.model.{Path, StringValue, Value} @@ -90,7 +92,7 @@ object DependencyResolver: import dotty.tools.dotc.classpath.ClassPathFactory import dotty.tools.dotc.core.SymbolLoaders import dotty.tools.dotc.core.Symbols.defn - import dotty.tools.io.* + import dotty.tools.io.{AbstractFile, ClassPath} import dotty.tools.repl.ScalaClassLoader.fromURLsParallelCapable // Create a classloader with all the resolved JAR files @@ -106,10 +108,10 @@ object DependencyResolver: SymbolLoaders.mergeNewEntries(defn.RootClass, ClassPath.RootPackage, jarClassPath, ctx.platform.classPath) // Create new classloader with previous output dir and resolved dependencies - new dotty.tools.repl.AbstractFileClassLoader( + new AbstractFileClassLoader( prevOutputDir, depsClassLoader, - ctx.settings.XreplInterruptInstrumentation.value + AbstractFileClassLoader.InterruptInstrumentation.fromString(ctx.settings.XreplInterruptInstrumentation.value) ) end DependencyResolver diff --git a/repl/src/dotty/tools/repl/Rendering.scala b/repl/src/dotty/tools/repl/Rendering.scala index fde300bcf39d..3af59e547fbd 100644 --- a/repl/src/dotty/tools/repl/Rendering.scala +++ b/repl/src/dotty/tools/repl/Rendering.scala @@ -86,7 +86,7 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None): myClassLoader = new AbstractFileClassLoader( ctx.settings.outputDir.value, parent, - ctx.settings.XreplInterruptInstrumentation.value + AbstractFileClassLoader.InterruptInstrumentation.fromString(ctx.settings.XreplInterruptInstrumentation.value) ) myClassLoader } diff --git a/repl/src/dotty/tools/repl/ReplBytecodeInstrumentation.scala b/repl/src/dotty/tools/repl/ReplBytecodeInstrumentation.scala index 57ff5b02a0a5..2e033fdd0177 100644 --- a/repl/src/dotty/tools/repl/ReplBytecodeInstrumentation.scala +++ b/repl/src/dotty/tools/repl/ReplBytecodeInstrumentation.scala @@ -6,9 +6,9 @@ import scala.language.unsafeNulls import scala.tools.asm.* import scala.tools.asm.Opcodes.* import scala.tools.asm.tree.* -import scala.collection.JavaConverters.* +import scala.jdk.CollectionConverters.* import java.util.concurrent.atomic.AtomicBoolean - + object ReplBytecodeInstrumentation: /** Instrument bytecode to add checks to throw an exception if the REPL command is cancelled */ diff --git a/repl/src/dotty/tools/repl/ReplDriver.scala b/repl/src/dotty/tools/repl/ReplDriver.scala index 00a12bd1b8bc..ff6b63df3ae3 100644 --- a/repl/src/dotty/tools/repl/ReplDriver.scala +++ b/repl/src/dotty/tools/repl/ReplDriver.scala @@ -607,7 +607,7 @@ class ReplDriver(settings: Array[String], rendering.myClassLoader = new AbstractFileClassLoader( prevOutputDir, jarClassLoader, - ctx.settings.XreplInterruptInstrumentation.value + AbstractFileClassLoader.InterruptInstrumentation.fromString(ctx.settings.XreplInterruptInstrumentation.value) ) out.println(s"Added '$path' to classpath.") diff --git a/repl/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala b/repl/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala index 30ddc07a9b1d..8d7a9d0402e7 100644 --- a/repl/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala +++ b/repl/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala @@ -51,13 +51,13 @@ class AbstractFileClassLoaderTest: @Test def afclGetsParent(): Unit = val p = new URLClassLoader(Array.empty[URL]) val d = new VirtualDirectory("vd", None) - val x = new AbstractFileClassLoader(d, p, "false") + val x = new AbstractFileClassLoader(d, p) assertSame(p, x.getParent) @Test def afclGetsResource(): Unit = val (fuzz, booz) = fuzzBuzzBooz booz.writeContent("hello, world") - val sut = new AbstractFileClassLoader(fuzz, NoClassLoader, "false") + val sut = new AbstractFileClassLoader(fuzz, NoClassLoader) val res = sut.getResource("buzz/booz.class") assertNotNull("Find buzz/booz.class", res) assertEquals("hello, world", slurp(res)) @@ -67,8 +67,8 @@ class AbstractFileClassLoaderTest: val (fuzz_, booz_) = fuzzBuzzBooz booz.writeContent("hello, world") booz_.writeContent("hello, world_") - val p = new AbstractFileClassLoader(fuzz, NoClassLoader, "false") - val sut = new AbstractFileClassLoader(fuzz_, p, "false") + val p = new AbstractFileClassLoader(fuzz, NoClassLoader) + val sut = new AbstractFileClassLoader(fuzz_, p) val res = sut.getResource("buzz/booz.class") assertNotNull("Find buzz/booz.class", res) assertEquals("hello, world", slurp(res)) @@ -79,7 +79,7 @@ class AbstractFileClassLoaderTest: val bass = fuzz.fileNamed("bass") booz.writeContent("hello, world") bass.writeContent("lo tone") - val sut = new AbstractFileClassLoader(fuzz, NoClassLoader, "false") + val sut = new AbstractFileClassLoader(fuzz, NoClassLoader) val res = sut.getResource("booz.class") assertNotNull(res) assertEquals("hello, world", slurp(res)) @@ -89,7 +89,7 @@ class AbstractFileClassLoaderTest: @Test def afclGetsResources(): Unit = val (fuzz, booz) = fuzzBuzzBooz booz.writeContent("hello, world") - val sut = new AbstractFileClassLoader(fuzz, NoClassLoader, "false") + val sut = new AbstractFileClassLoader(fuzz, NoClassLoader) val e = sut.getResources("buzz/booz.class") assertTrue("At least one buzz/booz.class", e.hasMoreElements) assertEquals("hello, world", slurp(e.nextElement)) @@ -100,8 +100,8 @@ class AbstractFileClassLoaderTest: val (fuzz_, booz_) = fuzzBuzzBooz booz.writeContent("hello, world") booz_.writeContent("hello, world_") - val p = new AbstractFileClassLoader(fuzz, NoClassLoader, "false") - val x = new AbstractFileClassLoader(fuzz_, p, "false") + val p = new AbstractFileClassLoader(fuzz, NoClassLoader) + val x = new AbstractFileClassLoader(fuzz_, p) val e = x.getResources("buzz/booz.class") assertTrue(e.hasMoreElements) assertEquals("hello, world", slurp(e.nextElement)) @@ -112,7 +112,7 @@ class AbstractFileClassLoaderTest: @Test def afclGetsResourceAsStream(): Unit = val (fuzz, booz) = fuzzBuzzBooz booz.writeContent("hello, world") - val x = new AbstractFileClassLoader(fuzz, NoClassLoader, "false") + val x = new AbstractFileClassLoader(fuzz, NoClassLoader) val r = x.getResourceAsStream("buzz/booz.class") assertNotNull(r) assertEquals("hello, world", closing(r)(is => Source.fromInputStream(is).mkString)) @@ -120,7 +120,7 @@ class AbstractFileClassLoaderTest: @Test def afclGetsClassBytes(): Unit = val (fuzz, booz) = fuzzBuzzBooz booz.writeContent("hello, world") - val sut = new AbstractFileClassLoader(fuzz, NoClassLoader, "false") + val sut = new AbstractFileClassLoader(fuzz, NoClassLoader) val b = sut.classBytes("buzz/booz.class") assertEquals("hello, world", new String(b, UTF8.charSet)) @@ -130,8 +130,8 @@ class AbstractFileClassLoaderTest: booz.writeContent("hello, world") booz_.writeContent("hello, world_") - val p = new AbstractFileClassLoader(fuzz, NoClassLoader, "false") - val sut = new AbstractFileClassLoader(fuzz_, p, "false") + val p = new AbstractFileClassLoader(fuzz, NoClassLoader) + val sut = new AbstractFileClassLoader(fuzz_, p) val b = sut.classBytes("buzz/booz.class") assertEquals("hello, world", new String(b, UTF8.charSet)) end AbstractFileClassLoaderTest