Skip to content

Commit 09ce5c3

Browse files
committed
SI-6502 More robust REPL :require
- handle missing files gracefully (rather than NPE) - read the class name with ASM, rather than with a dummy classloader. The dummy classloader is prone to throwing `LinkageError`s, as reported in the comments of SI-6502. Manual test of the original report: ``` % qscala Welcome to Scala version 2.11.5-20150115-183424-155dbf3fdf (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25). Type in expressions to have them evaluated. Type :help for more information. scala> :require does/not/exist Cannot read: does/not/exist scala> classOf[org.junit.Test] <console>:8: error: object junit is not a member of package org classOf[org.junit.Test] ^ scala> :require /Users/jason/.m2/repository/junit/junit/4.11/junit-4.11.jar Added '/Users/jason/.m2/repository/junit/junit/4.11/junit-4.11.jar' to classpath. scala> classOf[org.junit.Test] res1: Class[org.junit.Test] = interface org.junit.Test ``` I have commited an automated test that is a minimization of this one.
1 parent 0561239 commit 09ce5c3

File tree

2 files changed

+39
-11
lines changed

2 files changed

+39
-11
lines changed

src/repl/scala/tools/nsc/interpreter/ILoop.scala

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import scala.annotation.tailrec
1212
import Predef.{ println => _, _ }
1313
import interpreter.session._
1414
import StdReplTags._
15+
import scala.tools.asm.ClassReader
1516
import scala.util.Properties.{ jdkHome, javaVersion, versionString, javaVmName }
1617
import scala.tools.nsc.util.{ ClassPath, Exceptional, stringFromWriter, stringFromStream }
1718
import scala.reflect.classTag
@@ -633,28 +634,29 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
633634
* the interpreter and replays all interpreter expressions.
634635
*/
635636
def require(arg: String): Unit = {
636-
class InfoClassLoader extends java.lang.ClassLoader {
637-
def classOf(arr: Array[Byte]): Class[_] =
638-
super.defineClass(null, arr, 0, arr.length)
639-
}
640-
641637
val f = File(arg).normalize
642638

643-
if (f.isDirectory) {
644-
echo("Adding directories to the classpath is not supported. Add a jar instead.")
639+
val jarFile = AbstractFile.getDirectory(new java.io.File(arg))
640+
if (jarFile == null) {
641+
echo(s"Cannot load '$arg'")
645642
return
646643
}
647644

648-
val jarFile = AbstractFile.getDirectory(new java.io.File(arg))
649-
650645
def flatten(f: AbstractFile): Iterator[AbstractFile] =
651646
if (f.isClassContainer) f.iterator.flatMap(flatten)
652647
else Iterator(f)
653648

654649
val entries = flatten(jarFile)
655-
val cloader = new InfoClassLoader
656650

657-
def classNameOf(classFile: AbstractFile): String = cloader.classOf(classFile.toByteArray).getName
651+
def classNameOf(classFile: AbstractFile): String = {
652+
val input = classFile.input
653+
try {
654+
val reader = new ClassReader(input)
655+
reader.getClassName.replace('/', '.')
656+
} finally {
657+
input.close()
658+
}
659+
}
658660
def alreadyDefined(clsName: String) = intp.classLoader.tryToLoadClass(clsName).isDefined
659661
val exists = entries.filter(_.hasExtension("class")).map(classNameOf).exists(alreadyDefined)
660662

test/files/run/t6502.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ object Test extends StoreReporterDirectTest {
4646
}
4747
}"""
4848

49+
def app6 = """
50+
package test6
51+
class A extends Test { println("created test6.A") }
52+
class Z extends Test { println("created test6.Z") }
53+
trait Test"""
54+
4955
def test1(): Unit = {
5056
val jar = "test1.jar"
5157
compileCode(app1, jar)
@@ -105,11 +111,31 @@ object Test extends StoreReporterDirectTest {
105111
println(s"test4 res2: $res2")
106112
}
107113

114+
def test5(): Unit = {
115+
val codeToRun = ":require /does/not/exist.jar"
116+
val output = ILoop.run(codeToRun, settings)
117+
assert(!output.contains("NullPointerException"), output)
118+
assert(output.contains("Cannot load '/does/not/exist.jar'"), output)
119+
}
120+
121+
def test6(): Unit = {
122+
// Avoid java.lang.NoClassDefFoundError triggered by the old appoach of using a Java
123+
// classloader to parse .class files in order to read their names.
124+
val jar = "test6.jar"
125+
compileCode(app6, jar)
126+
val codeToRun = toCodeInSeparateLines(s":require ${testOutput.path}/$jar", "import test6._; new A; new Z")
127+
val output = ILoop.run(codeToRun, settings)
128+
assert(output.contains("created test6.A"), output)
129+
assert(output.contains("created test6.Z"), output)
130+
}
131+
108132
def show(): Unit = {
109133
test1()
110134
test2()
111135
test3()
112136
test4()
137+
test5()
138+
test6()
113139
}
114140

115141
def toCodeInSeparateLines(lines: String*): String = lines.map(_ + "\n").mkString

0 commit comments

Comments
 (0)