Skip to content

Fix #9642: fix bootstrapping on Windows #9644

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

Merged
merged 4 commits into from
Aug 28, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 43 additions & 28 deletions compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package dotc.classpath

import java.net.URL
import scala.collection.mutable.ArrayBuffer
import dotty.tools.io.{ AbstractFile, ClassPath, ClassRepresentation }
import scala.collection.immutable.ArraySeq

import dotty.tools.io.{ AbstractFile, ClassPath, ClassRepresentation, EfficientClassPath }

/**
* A classpath unifying multiple class- and sourcepath entries.
Expand All @@ -19,20 +21,20 @@ import dotty.tools.io.{ AbstractFile, ClassPath, ClassRepresentation }
case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {
override def findClassFile(className: String): Option[AbstractFile] = {
val (pkg, _) = PackageNameUtils.separatePkgAndClassNames(className)
aggregatesForPackage(pkg).iterator.map(_.findClassFile(className)).collectFirst {
aggregatesForPackage(PackageName(pkg)).iterator.map(_.findClassFile(className)).collectFirst {
case Some(x) => x
}
}
private val packageIndex: collection.mutable.Map[String, Seq[ClassPath]] = collection.mutable.Map()
private def aggregatesForPackage(pkg: String): Seq[ClassPath] = packageIndex.synchronized {
packageIndex.getOrElseUpdate(pkg, aggregates.filter(_.hasPackage(pkg)))
private def aggregatesForPackage(pkg: PackageName): Seq[ClassPath] = packageIndex.synchronized {
packageIndex.getOrElseUpdate(pkg.dottedString, aggregates.filter(_.hasPackage(pkg)))
}

override def findClass(className: String): Option[ClassRepresentation] = {
val (pkg, _) = PackageNameUtils.separatePkgAndClassNames(className)

def findEntry(isSource: Boolean): Option[ClassRepresentation] =
aggregatesForPackage(pkg).iterator.map(_.findClass(className)).collectFirst {
aggregatesForPackage(PackageName(pkg)).iterator.map(_.findClass(className)).collectFirst {
case Some(s: SourceFileEntry) if isSource => s
case Some(s: ClassFileEntry) if !isSource => s
}
Expand All @@ -53,31 +55,47 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {

override def asSourcePathString: String = ClassPath.join(aggregates map (_.asSourcePathString): _*)

override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = {
override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = {
val aggregatedPackages = aggregates.flatMap(_.packages(inPackage)).distinct
aggregatedPackages
}

override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] =
override private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] =
getDistinctEntries(_.classes(inPackage))

override private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] =
override private[dotty] def sources(inPackage: PackageName): Seq[SourceFileEntry] =
getDistinctEntries(_.sources(inPackage))

override private[dotty] def hasPackage(pkg: String): Boolean = aggregates.exists(_.hasPackage(pkg))
override private[dotty] def list(inPackage: String): ClassPathEntries = {
val (packages, classesAndSources) = aggregates.map { cp =>
try
cp.list(inPackage).toTuple
catch {
override private[dotty] def hasPackage(pkg: PackageName): Boolean = aggregates.exists(_.hasPackage(pkg))
override private[dotty] def list(inPackage: PackageName): ClassPathEntries = {
val packages: java.util.HashSet[PackageEntry] = new java.util.HashSet[PackageEntry]()
val classesAndSourcesBuffer = collection.mutable.ArrayBuffer[ClassRepresentation]()
val onPackage: PackageEntry => Unit = packages.add(_)
val onClassesAndSources: ClassRepresentation => Unit = classesAndSourcesBuffer += _

aggregates.foreach { cp =>
try {
cp match {
case ecp: EfficientClassPath =>
ecp.list(inPackage, onPackage, onClassesAndSources)
case _ =>
val entries = cp.list(inPackage)
entries._1.foreach(entry => packages.add(entry))
classesAndSourcesBuffer ++= entries._2
}
} catch {
case ex: java.io.IOException =>
val e = new FatalError(ex.getMessage)
val e = FatalError(ex.getMessage)
e.initCause(ex)
throw e
}
}.unzip
val distinctPackages = packages.flatten.distinct
val distinctClassesAndSources = mergeClassesAndSources(classesAndSources: _*)
}

val distinctPackages: Seq[PackageEntry] = {
val arr = packages.toArray(new Array[PackageEntry](packages.size()))
ArraySeq.unsafeWrapArray(arr)
}
val distinctClassesAndSources = mergeClassesAndSources(classesAndSourcesBuffer)
ClassPathEntries(distinctPackages, distinctClassesAndSources)
}

Expand All @@ -86,19 +104,16 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {
* creates an entry containing both of them. If there would be more than one class or source
* entries for the same class it always would use the first entry of each type found on a classpath.
*/
private def mergeClassesAndSources(entries: scala.collection.Seq[ClassRepresentation]*): Seq[ClassRepresentation] = {
private def mergeClassesAndSources(entries: scala.collection.Seq[ClassRepresentation]): Seq[ClassRepresentation] = {
// based on the implementation from MergedClassPath
var count = 0
val indices = collection.mutable.HashMap[String, Int]()
val mergedEntries = new ArrayBuffer[ClassRepresentation](1024)

val indices = new collection.mutable.HashMap[String, Int]()
val mergedEntries = new ArrayBuffer[ClassRepresentation](entries.size)
for {
partOfEntries <- entries
entry <- partOfEntries
}
{
entry <- entries
} {
val name = entry.name
if (indices contains name) {
if (indices.contains(name)) {
val index = indices(name)
val existing = mergedEntries(index)

Expand All @@ -113,7 +128,7 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {
count += 1
}
}
mergedEntries.toIndexedSeq
if (mergedEntries.isEmpty) Nil else mergedEntries.toIndexedSeq
}

private def getDistinctEntries[EntryType <: ClassRepresentation](getEntries: ClassPath => Seq[EntryType]): Seq[EntryType] = {
Expand Down
30 changes: 28 additions & 2 deletions compiler/src/dotty/tools/dotc/classpath/ClassPath.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ case class ClassPathEntries(packages: scala.collection.Seq[PackageEntry], classe
def toTuple: (scala.collection.Seq[PackageEntry], scala.collection.Seq[ClassRepresentation]) = (packages, classesAndSources)
}

object ClassPathEntries {
val empty = ClassPathEntries(Seq.empty, Seq.empty)
}

trait ClassFileEntry extends ClassRepresentation {
def file: AbstractFile
}
Expand All @@ -18,6 +22,28 @@ trait SourceFileEntry extends ClassRepresentation {
def file: AbstractFile
}

case class PackageName(dottedString: String) {
val dirPathTrailingSlashJar: String = FileUtils.dirPathInJar(dottedString) + "/"

val dirPathTrailingSlash: String =
if (java.io.File.separatorChar == '/')
dirPathTrailingSlashJar
else
FileUtils.dirPath(dottedString) + java.io.File.separator
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is too cautious. One justification for the duplication is to save hours of debugging for future maintainers.

WDYT @sjrd ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this file name is used for lookup only, then it can be / on all platforms, including Windows. Java can find files that you specify with / as if they were \.

Where it breaks down is when listing files, then comparing their paths to some other data where you always have /, or if you split('/'). Because Windows will give you \, which won't be compared/split correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is the worry I have. String paths like a/b/c on windows are mines: if they are accidentally spliced to form a bigger string path, errors are guaranteed. Currently (before this commit), they already leak to DirectoryPath, and works accidentally.


def isRoot: Boolean = dottedString.isEmpty

def entryName(entry: String): String = {
if (isRoot) entry else {
val builder = new java.lang.StringBuilder(dottedString.length + 1 + entry.length)
builder.append(dottedString)
builder.append('.')
builder.append(entry)
builder.toString
}
}
}

trait PackageEntry {
def name: String
}
Expand Down Expand Up @@ -50,10 +76,10 @@ private[dotty] case class PackageEntryImpl(name: String) extends PackageEntry

private[dotty] trait NoSourcePaths {
def asSourcePathString: String = ""
private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty
private[dotty] def sources(inPackage: PackageName): Seq[SourceFileEntry] = Seq.empty
}

private[dotty] trait NoClassPaths {
def findClassFile(className: String): Option[AbstractFile] = None
private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty
private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = Seq.empty
}
73 changes: 35 additions & 38 deletions compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import java.io.{File => JFile}
import java.net.URL
import java.nio.file.{FileSystems, Files}

import dotty.tools.io.{AbstractFile, PlainFile, ClassPath, ClassRepresentation}
import dotty.tools.io.{AbstractFile, PlainFile, ClassPath, ClassRepresentation, EfficientClassPath}
import FileUtils._

import scala.collection.JavaConverters._
import scala.collection.immutable.ArraySeq

/**
* A trait allowing to look for classpath entries in directories. It provides common logic for
Expand All @@ -18,7 +20,7 @@ import scala.collection.JavaConverters._
* when we have a name of a package.
* It abstracts over the file representation to work with both JFile and AbstractFile.
*/
trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath {
trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends EfficientClassPath {
type F

val dir: F
Expand All @@ -33,27 +35,24 @@ trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath {
protected def createFileEntry(file: AbstractFile): FileEntryType
protected def isMatchingFile(f: F): Boolean

private def getDirectory(forPackage: String): Option[F] =
if (forPackage == ClassPath.RootPackage)
private def getDirectory(forPackage: PackageName): Option[F] =
if (forPackage.isRoot)
Some(dir)
else {
val packageDirName = FileUtils.dirPath(forPackage)
getSubDir(packageDirName)
}
else
getSubDir(forPackage.dirPathTrailingSlash)

override private[dotty] def hasPackage(pkg: String): Boolean = getDirectory(pkg).isDefined
override private[dotty] def hasPackage(pkg: PackageName): Boolean = getDirectory(pkg).isDefined

private[dotty] def packages(inPackage: String): Seq[PackageEntry] = {
private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = {
val dirForPackage = getDirectory(inPackage)
val nestedDirs: Array[F] = dirForPackage match {
case None => emptyFiles
case Some(directory) => listChildren(directory, Some(isPackage))
}
val prefix = PackageNameUtils.packagePrefix(inPackage)
nestedDirs.toIndexedSeq.map(f => PackageEntryImpl(prefix + getName(f)))
ArraySeq.unsafeWrapArray(nestedDirs).map(f => PackageEntryImpl(inPackage.entryName(getName(f))))
}

protected def files(inPackage: String): Seq[FileEntryType] = {
protected def files(inPackage: PackageName): Seq[FileEntryType] = {
val dirForPackage = getDirectory(inPackage)
val files: Array[F] = dirForPackage match {
case None => emptyFiles
Expand All @@ -62,21 +61,18 @@ trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath {
files.iterator.map(f => createFileEntry(toAbstractFile(f))).toSeq
}

private[dotty] def list(inPackage: String): ClassPathEntries = {
override def list(inPackage: PackageName, onPackageEntry: PackageEntry => Unit, onClassesAndSources: ClassRepresentation => Unit): Unit = {
val dirForPackage = getDirectory(inPackage)
val files: Array[F] = dirForPackage match {
case None => emptyFiles
case Some(directory) => listChildren(directory)
dirForPackage match {
case None =>
case Some(directory) =>
for (file <- listChildren(directory)) {
if (isPackage(file))
onPackageEntry(PackageEntryImpl(inPackage.entryName(getName(file))))
else if (isMatchingFile(file))
onClassesAndSources(createFileEntry(toAbstractFile(file)))
}
}
val packagePrefix = PackageNameUtils.packagePrefix(inPackage)
val packageBuf = collection.mutable.ArrayBuffer.empty[PackageEntry]
val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType]
for (file <- files)
if (isPackage(file))
packageBuf += PackageEntryImpl(packagePrefix + getName(file))
else if (isMatchingFile(file))
fileBuf += createFileEntry(toAbstractFile(file))
ClassPathEntries(packageBuf, fileBuf)
}
}

Expand Down Expand Up @@ -159,24 +155,25 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
}

/** Empty string represents root package */
override private[dotty] def hasPackage(pkg: String): Boolean = packageToModuleBases.contains(pkg)
override private[dotty] def hasPackage(pkg: PackageName): Boolean = packageToModuleBases.contains(pkg.dottedString)

override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = {
override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = {
def matches(packageDottedName: String) =
if (packageDottedName.contains("."))
packageOf(packageDottedName) == inPackage
else inPackage == ""
packageOf(packageDottedName) == inPackage.dottedString
else inPackage.isRoot
packageToModuleBases.keysIterator.filter(matches).map(PackageEntryImpl(_)).toVector
}
private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] =
if (inPackage == "") Nil

private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] =
if (inPackage.isRoot) Nil
else
packageToModuleBases.getOrElse(inPackage, Nil).flatMap(x =>
Files.list(x.resolve(FileUtils.dirPath(inPackage))).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x =>
packageToModuleBases.getOrElse(inPackage.dottedString, Nil).flatMap(x =>
Files.list(x.resolve(inPackage.dirPathTrailingSlash)).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x =>
ClassFileEntryImpl(new PlainFile(new dotty.tools.io.File(x)))).toVector

override private[dotty] def list(inPackage: String): ClassPathEntries =
if (inPackage == "") ClassPathEntries(packages(inPackage), Nil)
override private[dotty] def list(inPackage: PackageName): ClassPathEntries =
if (inPackage.isRoot) ClassPathEntries(packages(inPackage), Nil)
else ClassPathEntries(packages(inPackage), classes(inPackage))

def asURLs: Seq[URL] = Seq(new URL("jrt:/"))
Expand Down Expand Up @@ -214,7 +211,7 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFile
protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
protected def isMatchingFile(f: JFile): Boolean = f.isClass

private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage)
}

case class DirectorySourcePath(dir: JFile) extends JFileDirectoryLookup[SourceFileEntryImpl] with NoClassPaths {
Expand All @@ -238,5 +235,5 @@ case class DirectorySourcePath(dir: JFile) extends JFileDirectoryLookup[SourceFi
}
}

private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage)
private[dotty] def sources(inPackage: PackageName): Seq[SourceFileEntry] = files(inPackage)
}
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/classpath/FileUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ object FileUtils {

def dirPath(forPackage: String): String = forPackage.replace('.', JFile.separatorChar)

def dirPathInJar(forPackage: String): String = forPackage.replace('.', '/')

def endsClass(fileName: String): Boolean =
fileName.length > 6 && fileName.substring(fileName.length - 6) == ".class"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi
Option(lookupPath(dir)(relativePath.split(java.io.File.separator).toIndexedSeq, directory = false))
}

private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage)

protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
protected def isMatchingFile(f: AbstractFile): Boolean = f.isClass
Expand Down
Loading