Skip to content

Commit 4d41da9

Browse files
som-snyttszeiger
authored andcommitted
[backport] -Yrelease and related ctsym/jrt tweaks
1 parent d76f5d7 commit 4d41da9

File tree

10 files changed

+133
-56
lines changed

10 files changed

+133
-56
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,21 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
129129

130130
type ThisPlatform = JavaPlatform { val global: Global.this.type }
131131
lazy val platform: ThisPlatform = new GlobalPlatform
132-
/* A hook for the REPL to add a classpath entry containing products of previous runs to inliner's bytecode repository*/
133-
// Fixes scala/bug#8779
134-
def optimizerClassPath(base: ClassPath): ClassPath = base
132+
133+
/** The classpath used by inliner's bytecode repository.
134+
* If --release is used, swap the ctsym for jrt.
135+
* REPL adds a classpath entry containing products of previous runs. (scala/bug#8779)
136+
* @param base the class path to augment, nominally `this.classPath` or `platform.classPath`
137+
*/
138+
def optimizerClassPath(base: ClassPath): ClassPath =
139+
base match {
140+
case AggregateClassPath(entries) if entries.head.isInstanceOf[CtSymClassPath] =>
141+
JrtClassPath(release = None, unsafe = None, closeableRegistry) match {
142+
case jrt :: _ => AggregateClassPath(jrt +: entries.drop(1))
143+
case _ => base
144+
}
145+
case _ => base
146+
}
135147

136148
def classPath: ClassPath = platform.classPath
137149

src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,18 @@
1313
package scala.tools.nsc.classpath
1414

1515
import java.io.{Closeable, File}
16-
import java.net.URL
17-
import java.util
16+
import java.net.{URI, URL}
17+
import java.nio.file._
1818

19-
import scala.reflect.io.{AbstractFile, PlainFile, PlainNioFile}
20-
import scala.tools.nsc.util.{ClassPath, ClassRepresentation, EfficientClassPath}
21-
import FileUtils._
2219
import scala.collection.JavaConverters._
2320
import scala.reflect.internal.JDK9Reflectors
21+
import scala.reflect.io.{AbstractFile, PlainFile, PlainNioFile}
2422
import scala.tools.nsc.CloseableRegistry
2523
import scala.tools.nsc.classpath.PackageNameUtils.{packageContains, separatePkgAndClassNames}
24+
import scala.tools.nsc.util.{ClassPath, ClassRepresentation, EfficientClassPath}
25+
import scala.util.Properties.{isJavaAtLeast, javaHome}
26+
import scala.util.control.NonFatal
27+
import FileUtils._
2628

2729
/**
2830
* A trait allowing to look for classpath entries in directories. It provides common logic for
@@ -71,7 +73,7 @@ trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends EfficientCla
7173
case None => emptyFiles
7274
case Some(directory) => listChildren(directory, Some(isMatchingFile))
7375
}
74-
files.map(f => createFileEntry(toAbstractFile(f)))
76+
files.iterator.map(f => createFileEntry(toAbstractFile(f))).toSeq
7577
}
7678

7779
override private[nsc] def list(inPackage: PackageName, onPackageEntry: PackageEntry => Unit, onClassesAndSources: ClassRepresentation => Unit): Unit = {
@@ -95,7 +97,7 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
9597
protected def emptyFiles: Array[File] = Array.empty
9698
protected def getSubDir(packageDirName: String): Option[File] = {
9799
val packageDir = new File(dir, packageDirName)
98-
if (packageDir.exists && packageDir.isDirectory) Some(packageDir)
100+
if (packageDir.exists && packageDir.isDirectory && packageDir.canRead) Some(packageDir)
99101
else None
100102
}
101103
protected def listChildren(dir: File, filter: Option[File => Boolean]): Array[File] = {
@@ -114,7 +116,7 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
114116
//
115117
// Note this behaviour can be enabled in javac with `javac -XDsortfiles`, but that's only
116118
// intended to improve determinism of the compiler for compiler hackers.
117-
util.Arrays.sort(listing, (o1: File, o2: File) => o1.getName.compareTo(o2.getName))
119+
java.util.Arrays.sort(listing, (o1: File, o2: File) => o1.getName.compareTo(o2.getName))
118120
listing
119121
}
120122
protected def getName(f: File): String = f.getName
@@ -128,44 +130,65 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
128130
}
129131

130132
object JrtClassPath {
131-
import java.nio.file._, java.net.URI
132133
private val jrtClassPathCache = new FileBasedCache[Unit, JrtClassPath]()
133134
private val ctSymClassPathCache = new FileBasedCache[String, CtSymClassPath]()
134-
def apply(release: Option[String], closeableRegistry: CloseableRegistry): Option[ClassPath] = {
135-
import scala.util.Properties._
136-
if (!isJavaAtLeast("9")) None
135+
def apply(release: Option[String], unsafe: Option[List[String]], closeableRegistry: CloseableRegistry): List[ClassPath] =
136+
if (!isJavaAtLeast("9")) Nil
137137
else {
138138
// TODO escalate errors once we're sure they are fatal
139139
// I'm hesitant to do this immediately, because -release will still work for multi-release JARs
140140
// even if we're running on a JRE or a non OpenJDK JDK where ct.sym is unavailable.
141141
//
142142
// Longer term we'd like an official API for this in the JDK
143-
// Discussion: http://mail.openjdk.java.net/pipermail/compiler-dev/2018-March/thread.html#11738
143+
// Discussion: https://mail.openjdk.java.net/pipermail/compiler-dev/2018-March/thread.html#11738
144144

145145
val currentMajorVersion: Int = JDK9Reflectors.runtimeVersionMajor(JDK9Reflectors.runtimeVersion()).intValue()
146146
release match {
147-
case Some(v) if v.toInt < currentMajorVersion =>
148-
try {
149-
val ctSym = Paths.get(javaHome).resolve("lib").resolve("ct.sym")
150-
if (Files.notExists(ctSym)) None
151-
else {
152-
val classPath = ctSymClassPathCache.getOrCreate(v, ctSym :: Nil, () => new CtSymClassPath(ctSym, v.toInt), closeableRegistry, true)
153-
Some(classPath)
154-
}
155-
} catch {
156-
case _: Throwable => None
147+
case Some(version) if version.toInt < currentMajorVersion =>
148+
val ct = createCt(version, closeableRegistry)
149+
unsafe match {
150+
case Some(pkgs) if pkgs.nonEmpty =>
151+
createJrt(closeableRegistry) match {
152+
case Nil => ct
153+
case jrt :: _ => ct :+ new FilteringJrtClassPath(jrt, pkgs: _*)
154+
}
155+
case _ => ct
157156
}
158157
case _ =>
159-
try {
160-
val fs = FileSystems.getFileSystem(URI.create("jrt:/"))
161-
val classPath = jrtClassPathCache.getOrCreate((), Nil, () => new JrtClassPath(fs), closeableRegistry, false)
162-
Some(classPath)
163-
} catch {
164-
case _: ProviderNotFoundException | _: FileSystemNotFoundException => None
165-
}
158+
createJrt(closeableRegistry)
166159
}
167160
}
168-
}
161+
private def createCt(v: String, closeableRegistry: CloseableRegistry): List[ClassPath] =
162+
try {
163+
val ctSym = Paths.get(javaHome).resolve("lib").resolve("ct.sym")
164+
if (Files.notExists(ctSym)) Nil
165+
else List(
166+
ctSymClassPathCache.getOrCreate(v, ctSym :: Nil, () => new CtSymClassPath(ctSym, v.toInt), closeableRegistry, checkStamps = true)
167+
)
168+
} catch {
169+
case NonFatal(_) => Nil
170+
}
171+
private def createJrt(closeableRegistry: CloseableRegistry): List[JrtClassPath] =
172+
try {
173+
val fs = FileSystems.getFileSystem(URI.create("jrt:/"))
174+
val classPath = jrtClassPathCache.getOrCreate((), Nil, () => new JrtClassPath(fs), closeableRegistry, checkStamps = false)
175+
List(classPath)
176+
} catch {
177+
case _: ProviderNotFoundException | _: FileSystemNotFoundException => Nil
178+
}
179+
}
180+
181+
final class FilteringJrtClassPath(delegate: JrtClassPath, allowed: String*) extends ClassPath with NoSourcePaths {
182+
private val allowedPackages = allowed
183+
private def packagePrefix(p: String, q: String) = p.startsWith(q) && (p.length == q.length || p.charAt(q.length) == '.')
184+
private def ok(pkg: PackageName) = pkg.dottedString.isEmpty || allowedPackages.exists(packagePrefix(_, pkg.dottedString))
185+
def asClassPathStrings: Seq[String] = delegate.asClassPathStrings
186+
def asURLs: Seq[java.net.URL] = delegate.asURLs
187+
private[nsc] def classes(inPackage: PackageName) = if (ok(inPackage)) delegate.classes(inPackage) else Nil
188+
def findClassFile(className: String) = if (ok(PackageName(separatePkgAndClassNames(className)._1))) delegate.findClassFile(className) else None
189+
private[nsc] def hasPackage(pkg: PackageName) = ok(pkg) && delegate.hasPackage(pkg)
190+
private[nsc] def list(inPackage: PackageName) = if (ok(inPackage)) delegate.list(inPackage) else ClassPathEntries(Nil, Nil)
191+
private[nsc] def packages(inPackage: PackageName) = if (ok(inPackage)) delegate.packages(inPackage) else Nil
169192
}
170193

171194
/**
@@ -176,16 +199,15 @@ object JrtClassPath {
176199
*
177200
* The implementation assumes that no classes exist in the empty package.
178201
*/
179-
final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with NoSourcePaths {
180-
import java.nio.file.Path, java.nio.file._
202+
final class JrtClassPath(fs: FileSystem) extends ClassPath with NoSourcePaths {
181203
type F = Path
182204
private val dir: Path = fs.getPath("/packages")
183205

184206
// e.g. "java.lang" -> Seq("/modules/java.base")
185207
private val packageToModuleBases: Map[String, Seq[Path]] = {
186-
val ps = Files.newDirectoryStream(dir).iterator().asScala
208+
val ps = Files.newDirectoryStream(dir).iterator.asScala
187209
def lookup(pack: Path): Seq[Path] = {
188-
Files.list(pack).iterator().asScala.map(l => if (Files.isSymbolicLink(l)) Files.readSymbolicLink(l) else l).toList
210+
Files.list(pack).iterator.asScala.map(l => if (Files.isSymbolicLink(l)) Files.readSymbolicLink(l) else l).toList
189211
}
190212
ps.map(p => (p.toString.stripPrefix("/packages/"), lookup(p))).toMap
191213
}
@@ -199,7 +221,7 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
199221
if (inPackage.isRoot) Nil
200222
else {
201223
packageToModuleBases.getOrElse(inPackage.dottedString, Nil).flatMap(x =>
202-
Files.list(x.resolve(inPackage.dirPathTrailingSlash)).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x =>
224+
Files.list(x.resolve(inPackage.dirPathTrailingSlash)).iterator.asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x =>
203225
ClassFileEntryImpl(new PlainNioFile(x))).toVector
204226
}
205227
}
@@ -208,7 +230,7 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
208230
if (inPackage.isRoot) ClassPathEntries(packages(inPackage), Nil)
209231
else ClassPathEntries(packages(inPackage), classes(inPackage))
210232

211-
def asURLs: Seq[URL] = Seq(new URL("jrt:/"))
233+
def asURLs: Seq[URL] = Seq(new URI("jrt:/").toURL)
212234
// We don't yet have a scheme to represent the JDK modules in our `-classpath`.
213235
// java models them as entries in the new "module path", we'll probably need to follow this.
214236
def asClassPathStrings: Seq[String] = Nil
@@ -226,26 +248,26 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
226248
}
227249

228250
/**
229-
* Implementation `ClassPath` based on the \$JAVA_HOME/lib/ct.sym backing http://openjdk.java.net/jeps/247
251+
* Implementation `ClassPath` based on the \$JAVA_HOME/lib/ct.sym backing https://openjdk.java.net/jeps/247
230252
*/
231253
final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends ClassPath with NoSourcePaths with Closeable {
232254
import java.nio.file.Path, java.nio.file._
233255

234256
private val fileSystem: FileSystem = FileSystems.newFileSystem(ctSym, null: ClassLoader)
235-
private val root: Path = fileSystem.getRootDirectories.iterator().next
236-
private val roots = Files.newDirectoryStream(root).iterator().asScala.toList
257+
private val root: Path = fileSystem.getRootDirectories.iterator.next
258+
private val roots = Files.newDirectoryStream(root).iterator.asScala.toList
237259

238-
// http://mail.openjdk.java.net/pipermail/compiler-dev/2018-March/011737.html
260+
// https://mail.openjdk.java.net/pipermail/compiler-dev/2018-March/011737.html
239261
private def codeFor(major: Int): String = if (major < 10) major.toString else ('A' + (major - 10)).toChar.toString
240262

241263
private val releaseCode: String = codeFor(release)
242264
private def fileNameMatchesRelease(fileName: String) = !fileName.contains("-") && fileName.contains(releaseCode) // exclude `9-modules`
243265
private val rootsForRelease: List[Path] = roots.filter(root => fileNameMatchesRelease(root.getFileName.toString))
244266

245267
// e.g. "java.lang" -> Seq(/876/java/lang, /87/java/lang, /8/java/lang))
246-
private val packageIndex: scala.collection.Map[String, Seq[Path]] = {
268+
private val packageIndex: scala.collection.Map[String, scala.collection.Seq[Path]] = {
247269
val index = collection.mutable.AnyRefMap[String, collection.mutable.ListBuffer[Path]]()
248-
val isJava12OrHigher = scala.util.Properties.isJavaAtLeast("12")
270+
val isJava12OrHigher = isJavaAtLeast("12")
249271
rootsForRelease.foreach(root => Files.walk(root).iterator().asScala.filter(Files.isDirectory(_)).foreach { p =>
250272
val moduleNamePathElementCount = if (isJava12OrHigher) 1 else 0
251273
if (p.getNameCount > root.getNameCount + moduleNamePathElementCount) {
@@ -265,7 +287,7 @@ final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends Clas
265287
if (inPackage.isRoot) Nil
266288
else {
267289
val sigFiles = packageIndex.getOrElse(inPackage.dottedString, Nil).iterator.flatMap(p =>
268-
Files.list(p).iterator().asScala.filter(_.getFileName.toString.endsWith(".sig")))
290+
Files.list(p).iterator.asScala.filter(_.getFileName.toString.endsWith(".sig")))
269291
sigFiles.map(f => ClassFileEntryImpl(new PlainNioFile(f))).toVector
270292
}
271293
}

src/compiler/scala/tools/nsc/settings/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ trait ScalaSettings extends StandardScalaSettings with Warnings { _: MutableSett
244244
val Yvirtpatmat = BooleanSetting ("-Yvirtpatmat", "Enable pattern matcher virtualization")
245245
val Youtline = BooleanSetting ("-Youtline", "Don't compile method bodies. Use together with `-Ystop-afer:pickler to generate the pickled signatures for all source files.").internalOnly()
246246

247+
val unsafe = MultiStringSetting("-Yrelease", "packages", "Expose platform packages hidden under --release")
247248
val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
248249
val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method", "method-ref"), "method")
249250
val YmacroClasspath = PathSetting ("-Ymacro-classpath", "The classpath used to reflectively load macro implementations, default is the compilation classpath.", "")

src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ trait StandardScalaSettings { _: MutableSettings =>
6363
if (!isJavaAtLeast("9") && current > 8) errorFn.apply("-release is only supported on JVM 9 and higher")
6464
if (target.valueSetByUser.map(_.toInt > current).getOrElse(false)) errorFn("-release cannot be less than -target")
6565
}
66+
.withAbbreviation("--release")
67+
.withAbbreviation("-java-output-version")
6668
def releaseValue: Option[String] = release.valueSetByUser
6769
val target =
6870
ChoiceSetting("-target", "target", "Target platform for object files. All JVM 1.5 - 1.7 targets are deprecated.", AllTargetVersions, DefaultTargetVersion)

src/compiler/scala/tools/util/PathResolver.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,9 @@ final class PathResolver(settings: Settings, closeableRegistry: CloseableRegistr
260260

261261
// Assemble the elements!
262262
def basis = List[Traversable[ClassPath]](
263-
jrt, // 0. The Java 9+ classpath (backed by the ct.sym or jrt:/ virtual system, if available)
263+
if (settings.javabootclasspath.isSetByUser) // respect explicit `-javabootclasspath rt.jar`
264+
Nil
265+
else jrt, // 0. The Java 9+ classpath (backed by the ct.sym or jrt:/ virtual system, if available)
264266
classesInPath(javaBootClassPath), // 1. The Java bootstrap class path.
265267
contentsOfDirsInPath(javaExtDirs), // 2. The Java extension class path.
266268
classesInExpandedPath(javaUserClassPath), // 3. The Java application class path.
@@ -271,7 +273,7 @@ final class PathResolver(settings: Settings, closeableRegistry: CloseableRegistr
271273
sourcesInPath(sourcePath) // 7. The Scala source path.
272274
)
273275

274-
private def jrt: Option[ClassPath] = JrtClassPath.apply(settings.releaseValue, closeableRegistry)
276+
private def jrt: List[ClassPath] = JrtClassPath.apply(settings.releaseValue, settings.unsafe.valueSetByUser, closeableRegistry)
275277

276278
lazy val containers = basis.flatten.distinct
277279

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,15 @@ trait ReplGlobal extends Global {
8989
}
9090

9191
override def optimizerClassPath(base: ClassPath): ClassPath = {
92-
settings.outputDirs.getSingleOutput match {
93-
case None => base
94-
case Some(out) =>
95-
// Make bytecode of previous lines available to the inliner
96-
val replOutClasspath = ClassPathFactory.newClassPath(settings.outputDirs.getSingleOutput.get, settings, closeableRegistry)
97-
AggregateClassPath.createAggregate(platform.classPath, replOutClasspath)
92+
def withBase(base: ClassPath): ClassPath = {
93+
settings.outputDirs.getSingleOutput match {
94+
case None => base
95+
case Some(out) =>
96+
// Make bytecode of previous lines available to the inliner
97+
val replOutClasspath = ClassPathFactory.newClassPath(settings.outputDirs.getSingleOutput.get, settings, closeableRegistry)
98+
AggregateClassPath.createAggregate(base, replOutClasspath)
99+
}
98100
}
101+
withBase(super.optimizerClassPath(base))
99102
}
100103
}

test/files/neg/unsafe.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
unsafe.scala:9: error: value threadId is not a member of Thread
2+
def f(t: Thread) = t.threadId
3+
^
4+
one error found

test/files/neg/unsafe.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
// scalac: --release:8 -Yrelease:java.lang
3+
// javaVersion: 19+
4+
5+
// -Yrelease opens packages but does not override class definitions
6+
// because ct.sym comes first
7+
8+
class C {
9+
def f(t: Thread) = t.threadId
10+
}

test/files/pos/unsafe.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
// scalac: --release:8 -Yrelease:sun.misc
3+
4+
import sun.misc.Unsafe
5+
6+
class C {
7+
val f = classOf[Unsafe].getDeclaredField("theUnsafe")
8+
f.setAccessible(true)
9+
val unsafe = f.get(null).asInstanceOf[Unsafe]
10+
11+
val k = unsafe.allocateInstance(classOf[K]).asInstanceOf[K]
12+
assert(k.value == 0)
13+
}
14+
15+
class K {
16+
val value = 42
17+
}
18+
19+
object Test extends App {
20+
new C
21+
}

test/junit/scala/tools/nsc/classpath/JrtClassPathTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class JrtClassPathTest {
2727
val elements = new ClassPathFactory(settings, closeableRegistry).classesInPath(resolver.Calculated.javaBootClassPath)
2828
AggregateClassPath(elements)
2929
}
30-
else JrtClassPath(None, closeableRegistry).get
30+
else JrtClassPath(release = None, unsafe = None, closeableRegistry).head
3131

3232
assertEquals(Nil, cp.classes(""))
3333
assertTrue(cp.packages("java").toString, cp.packages("java").exists(_.name == "java.lang"))

0 commit comments

Comments
 (0)