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

-Yrelease supplements -release, allows access to additional JVM packages #10543

Merged
merged 1 commit into from
Jan 24, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/compiler/scala/tools/nsc/Global.scala
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,10 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
def optimizerClassPath(base: ClassPath): ClassPath =
base match {
case AggregateClassPath(entries) if entries.head.isInstanceOf[CtSymClassPath] =>
JrtClassPath(release = None, closeableRegistry)
.map(jrt => AggregateClassPath(entries.drop(1).prepended(jrt)))
.getOrElse(base)
JrtClassPath(release = None, unsafe = None, closeableRegistry) match {
case jrt :: Nil => AggregateClassPath(entries.drop(1).prepended(jrt))
case _ => base
}
case _ => base
}

Expand Down
80 changes: 52 additions & 28 deletions src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ package scala.tools.nsc.classpath

import java.io.{Closeable, File}
import java.net.{URI, URL}
import java.nio.file._

import scala.reflect.io.{AbstractFile, PlainFile, PlainNioFile}
import scala.tools.nsc.util.{ClassPath, ClassRepresentation, EfficientClassPath}
import FileUtils._
import scala.jdk.CollectionConverters._
import scala.reflect.internal.JDK9Reflectors
import scala.reflect.io.{AbstractFile, PlainFile, PlainNioFile}
import scala.tools.nsc.CloseableRegistry
import scala.tools.nsc.classpath.PackageNameUtils.{packageContains, separatePkgAndClassNames}
import scala.tools.nsc.util.{ClassPath, ClassRepresentation, EfficientClassPath}
import scala.util.Properties.{isJavaAtLeast, javaHome}
import scala.util.control.NonFatal
import FileUtils._

/**
* A trait allowing to look for classpath entries in directories. It provides common logic for
Expand Down Expand Up @@ -129,12 +132,10 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
}

object JrtClassPath {
import java.nio.file._, java.net.URI
private val jrtClassPathCache = new FileBasedCache[Unit, JrtClassPath]()
private val ctSymClassPathCache = new FileBasedCache[String, CtSymClassPath]()
def apply(release: Option[String], closeableRegistry: CloseableRegistry): Option[ClassPath] = {
import scala.util.Properties._
if (!isJavaAtLeast("9")) None
def apply(release: Option[String], unsafe: Option[List[String]], closeableRegistry: CloseableRegistry): List[ClassPath] =
if (!isJavaAtLeast("9")) Nil
else {
// TODO escalate errors once we're sure they are fatal
// I'm hesitant to do this immediately, because -release will still work for multi-release JARs
Expand All @@ -145,28 +146,52 @@ object JrtClassPath {

val currentMajorVersion: Int = JDK9Reflectors.runtimeVersionMajor(JDK9Reflectors.runtimeVersion()).intValue()
release match {
case Some(v) if v.toInt < currentMajorVersion =>
try {
val ctSym = Paths.get(javaHome).resolve("lib").resolve("ct.sym")
if (Files.notExists(ctSym)) None
else {
val classPath = ctSymClassPathCache.getOrCreate(v, ctSym :: Nil, () => new CtSymClassPath(ctSym, v.toInt), closeableRegistry, checkStamps = true)
Some(classPath)
}
} catch {
case _: Throwable => None
case Some(version) if version.toInt < currentMajorVersion =>
val ct = createCt(version, closeableRegistry)
unsafe match {
case Some(pkgs) if pkgs.nonEmpty =>
createJrt(closeableRegistry) match {
case Nil => ct
case jrts => ct.appended(new FilteringJrtClassPath(jrts.head, pkgs: _*))
}
case _ => ct
}
case _ =>
try {
val fs = FileSystems.getFileSystem(URI.create("jrt:/"))
val classPath = jrtClassPathCache.getOrCreate((), Nil, () => new JrtClassPath(fs), closeableRegistry, checkStamps = false)
Some(classPath)
} catch {
case _: ProviderNotFoundException | _: FileSystemNotFoundException => None
}
createJrt(closeableRegistry)
}
}
}
private def createCt(v: String, closeableRegistry: CloseableRegistry): List[ClassPath] =
try {
val ctSym = Paths.get(javaHome).resolve("lib").resolve("ct.sym")
if (Files.notExists(ctSym)) Nil
else {
val classPath = ctSymClassPathCache.getOrCreate(v, ctSym :: Nil, () => new CtSymClassPath(ctSym, v.toInt), closeableRegistry, checkStamps = true)
List(classPath)
}
} catch {
case NonFatal(_) => Nil
}
private def createJrt(closeableRegistry: CloseableRegistry): List[JrtClassPath] =
try {
val fs = FileSystems.getFileSystem(URI.create("jrt:/"))
val classPath = jrtClassPathCache.getOrCreate((), Nil, () => new JrtClassPath(fs), closeableRegistry, checkStamps = false)
List(classPath)
} catch {
case _: ProviderNotFoundException | _: FileSystemNotFoundException => Nil
}
}

final class FilteringJrtClassPath(delegate: JrtClassPath, allowed: String*) extends ClassPath with NoSourcePaths {
private val allowedPackages = allowed
private def packagePrefix(p: String, q: String) = p.startsWith(q) && (p.length == q.length || p.charAt(q.length) == '.')
private def ok(pkg: PackageName) = pkg.dottedString.isEmpty || allowedPackages.exists(packagePrefix(_, pkg.dottedString))
def asClassPathStrings: Seq[String] = delegate.asClassPathStrings
def asURLs: Seq[java.net.URL] = delegate.asURLs
private[nsc] def classes(inPackage: PackageName) = if (ok(inPackage)) delegate.classes(inPackage) else Nil
def findClassFile(className: String) = if (ok(PackageName(separatePkgAndClassNames(className)._1))) delegate.findClassFile(className) else None
private[nsc] def hasPackage(pkg: PackageName) = ok(pkg) && delegate.hasPackage(pkg)
private[nsc] def list(inPackage: PackageName) = if (ok(inPackage)) delegate.list(inPackage) else ClassPathEntries(Nil, Nil)
private[nsc] def packages(inPackage: PackageName) = if (ok(inPackage)) delegate.packages(inPackage) else Nil
}

/**
Expand All @@ -177,8 +202,7 @@ object JrtClassPath {
*
* The implementation assumes that no classes exist in the empty package.
*/
final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with NoSourcePaths {
import java.nio.file.Path, java.nio.file._
final class JrtClassPath(fs: FileSystem) extends ClassPath with NoSourcePaths {
type F = Path
private val dir: Path = fs.getPath("/packages")

Expand Down Expand Up @@ -246,7 +270,7 @@ final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends Clas
// e.g. "java.lang" -> Seq(/876/java/lang, /87/java/lang, /8/java/lang))
private val packageIndex: scala.collection.Map[String, scala.collection.Seq[Path]] = {
val index = collection.mutable.AnyRefMap[String, collection.mutable.ListBuffer[Path]]()
val isJava12OrHigher = scala.util.Properties.isJavaAtLeast("12")
val isJava12OrHigher = isJavaAtLeast("12")
rootsForRelease.foreach(root => Files.walk(root).iterator().asScala.filter(Files.isDirectory(_)).foreach { p =>
val moduleNamePathElementCount = if (isJava12OrHigher) 1 else 0
if (p.getNameCount > root.getNameCount + moduleNamePathElementCount) {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ trait ScalaSettings extends StandardScalaSettings with Warnings { _: MutableSett

val Youtline = BooleanSetting ("-Youtline", "Don't compile method bodies. Use together with `-Ystop-after:pickler` to generate the pickled signatures for all source files.").internalOnly()

val unsafe = MultiStringSetting("-Yrelease", "packages", "Expose platform packages hidden under --release")
val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "method")

Expand Down
7 changes: 4 additions & 3 deletions src/compiler/scala/tools/util/PathResolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,9 @@ final class PathResolver(settings: Settings, closeableRegistry: CloseableRegistr

// Assemble the elements!
def basis = List[Iterable[ClassPath]](
jrt // 0. The Java 9+ classpath (backed by the ct.sym or jrt:/ virtual system, if available)
.filter(_ => !settings.javabootclasspath.isSetByUser), // respect explicit `-javabootclasspath rt.jar`
if (settings.javabootclasspath.isSetByUser) // respect explicit `-javabootclasspath rt.jar`
Nil
else jrt, // 0. The Java 9+ classpath (backed by the ct.sym or jrt:/ virtual system, if available)
classesInPath(javaBootClassPath), // 1. The Java bootstrap class path.
contentsOfDirsInPath(javaExtDirs), // 2. The Java extension class path.
classesInExpandedPath(javaUserClassPath), // 3. The Java application class path.
Expand All @@ -269,7 +270,7 @@ final class PathResolver(settings: Settings, closeableRegistry: CloseableRegistr
sourcesInPath(sourcePath) // 7. The Scala source path.
)

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

lazy val containers = basis.flatten.distinct

Expand Down
4 changes: 4 additions & 0 deletions test/files/neg/unsafe.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
unsafe.scala:9: error: value threadId is not a member of Thread
def f(t: Thread) = t.threadId
^
1 error
10 changes: 10 additions & 0 deletions test/files/neg/unsafe.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

// scalac: --release:8 -Yrelease:java.lang
// javaVersion: 19+

// -Yrelease opens packages but does not override class definitions
// because ct.sym comes first

class C {
def f(t: Thread) = t.threadId
}
21 changes: 21 additions & 0 deletions test/files/pos/unsafe.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

// scalac: --release:8 -Yrelease:sun.misc

import sun.misc.Unsafe

class C {
val f = classOf[Unsafe].getDeclaredField("theUnsafe")
f.setAccessible(true)
val unsafe = f.get(null).asInstanceOf[Unsafe]

val k = unsafe.allocateInstance(classOf[K]).asInstanceOf[K]
assert(k.value == 0)
}

class K {
val value = 42
}

object Test extends App {
new C
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class JrtClassPathTest {
val elements = new ClassPathFactory(settings, closeableRegistry).classesInPath(resolver.Calculated.javaBootClassPath)
AggregateClassPath(elements)
}
else JrtClassPath(None, closeableRegistry).get
else JrtClassPath(None, None, closeableRegistry).head

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