Skip to content

Fix #7852: avoid reading stale .tasty files from jars #7918

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 2 commits into from
Jan 8, 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
51 changes: 29 additions & 22 deletions compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -774,35 +774,42 @@ class ClassfileParser(
val attrLen = in.nextInt
val bytes = in.nextBytes(attrLen)
if (attrLen == 16) { // A tasty attribute with that has only a UUID (16 bytes) implies the existence of the .tasty file
val tastyBytes: Array[Byte] = classfile.underlyingSource match { // TODO: simplify when #3552 is fixed
case None =>
ctx.error("Could not load TASTY from .tasty for virtual file " + classfile)
Array.empty
case Some(jar: ZipArchive) => // We are in a jar
val cl = new URLClassLoader(Array(jar.jpath.toUri.toURL), /*parent =*/ null)
val path = classfile.path.stripSuffix(".class") + ".tasty"
val stream = cl.getResourceAsStream(path)
if (stream != null) {
val tastyOutStream = new ByteArrayOutputStream()
val buffer = new Array[Byte](1024)
var read = stream.read(buffer, 0, buffer.length)
while (read != -1) {
tastyOutStream.write(buffer, 0, read)
read = stream.read(buffer, 0, buffer.length)
val tastyBytes: Array[Byte] = classfile match { // TODO: simplify when #3552 is fixed
case classfile: io.ZipArchive#Entry => // We are in a jar
val path = classfile.parent.lookupName(
classfile.name.stripSuffix(".class") + ".tasty", directory = false
)
if (path != null) {
val stream = path.input
try {
val tastyOutStream = new ByteArrayOutputStream()
val buffer = new Array[Byte](1024)
var read = stream.read(buffer, 0, buffer.length)
while (read != -1) {
tastyOutStream.write(buffer, 0, read)
read = stream.read(buffer, 0, buffer.length)
}
tastyOutStream.flush()
tastyOutStream.toByteArray
} finally {
stream.close()
}
tastyOutStream.flush()
tastyOutStream.toByteArray
}
else {
ctx.error(s"Could not find $path in $jar")
ctx.error(s"Could not find $path in ${classfile.underlyingSource}")
Array.empty
}
case _ =>
val plainFile = new PlainFile(io.File(classfile.jpath).changeExtension("tasty"))
if (plainFile.exists) plainFile.toByteArray
else {
ctx.error("Could not find " + plainFile)
if (classfile.jpath == null) {
ctx.error("Could not load TASTY from .tasty for virtual file " + classfile)
Array.empty
} else {
val plainFile = new PlainFile(io.File(classfile.jpath).changeExtension("tasty"))
if (plainFile.exists) plainFile.toByteArray
else {
ctx.error("Could not find " + plainFile)
Array.empty
}
}
}
if (tastyBytes.nonEmpty) {
Expand Down
27 changes: 15 additions & 12 deletions compiler/src/dotty/tools/io/ZipArchive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ abstract class ZipArchive(override val jpath: JPath) extends AbstractFile with E
def absolute: AbstractFile = unsupported()

/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
sealed abstract class Entry(path: String) extends VirtualFile(baseName(path), path) {
sealed abstract class Entry(path: String, val parent: Entry) extends VirtualFile(baseName(path), path) {
// have to keep this name for compat with sbt's compiler-interface
def getArchive: ZipFile = null
override def underlyingSource: Option[ZipArchive] = Some(self)
override def toString: String = self.path + "(" + path + ")"
}

/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
class DirEntry(path: String) extends Entry(path) {
class DirEntry(path: String, parent: Entry) extends Entry(path, parent) {
val entries: mutable.HashMap[String, Entry] = mutable.HashMap()

override def isDirectory: Boolean = true
Expand All @@ -98,7 +98,7 @@ abstract class ZipArchive(override val jpath: JPath) extends AbstractFile with E
case Some(v) => v
case None =>
val parent = ensureDir(dirs, dirName(path), null)
val dir = new DirEntry(path)
val dir = new DirEntry(path, parent)
parent.entries(baseName(path)) = dir
dirs(path) = dir
dir
Expand All @@ -120,8 +120,9 @@ final class FileZipArchive(jpath: JPath) extends ZipArchive(jpath) {
private class LazyEntry(
name: String,
time: Long,
size: Int
) extends Entry(name) {
size: Int,
parent: DirEntry
) extends Entry(name, parent) {
override def lastModified: Long = time // could be stale
override def input: InputStream = {
val zipFile = openZipFile()
Expand All @@ -140,15 +141,16 @@ final class FileZipArchive(jpath: JPath) extends ZipArchive(jpath) {
// faster than LazyEntry.
private class LeakyEntry(
zipFile: ZipFile,
zipEntry: ZipEntry
) extends Entry(zipEntry.getName) {
zipEntry: ZipEntry,
parent: DirEntry
) extends Entry(zipEntry.getName, parent) {
override def lastModified: Long = zipEntry.getTime
override def input: InputStream = zipFile.getInputStream(zipEntry)
override def sizeOption: Option[Int] = Some(zipEntry.getSize.toInt)
}

@volatile lazy val (root, allDirs): (DirEntry, collection.Map[String, DirEntry]) = {
val root = new DirEntry("/")
val root = new DirEntry("/", null)
val dirs = mutable.HashMap[String, DirEntry]("/" -> root)
val zipFile = openZipFile()
val entries = zipFile.entries()
Expand All @@ -163,10 +165,11 @@ final class FileZipArchive(jpath: JPath) extends ZipArchive(jpath) {
new LazyEntry(
zipEntry.getName(),
zipEntry.getTime(),
zipEntry.getSize().toInt
zipEntry.getSize().toInt,
dir
)
else
new LeakyEntry(zipFile, zipEntry)
new LeakyEntry(zipFile, zipEntry, dir)

dir.entries(f.name) = f
}
Expand Down Expand Up @@ -195,15 +198,15 @@ final class FileZipArchive(jpath: JPath) extends ZipArchive(jpath) {

final class ManifestResources(val url: URL) extends ZipArchive(null) {
def iterator(): Iterator[AbstractFile] = {
val root = new DirEntry("/")
val root = new DirEntry("/", null)
val dirs = mutable.HashMap[String, DirEntry]("/" -> root)
val manifest = new Manifest(input)
val iter = manifest.getEntries().keySet().iterator().asScala.filter(_.endsWith(".class")).map(new ZipEntry(_))

for (zipEntry <- iter) {
val dir = getDir(dirs, zipEntry)
if (!zipEntry.isDirectory) {
val f = new Entry(zipEntry.getName) {
val f = new Entry(zipEntry.getName, dir) {
override def lastModified = zipEntry.getTime()
override def input = resourceInputStream(path)
override def sizeOption = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
class A
4 changes: 4 additions & 0 deletions sbt-dotty/sbt-test/source-dependencies/export-jars2/b/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class B {
val x = new A
}

10 changes: 10 additions & 0 deletions sbt-dotty/sbt-test/source-dependencies/export-jars2/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
lazy val a = Project("a", file("a"))
.settings(
exportJars := true
)

lazy val b = Project("b", file("b"))
.dependsOn(a)
.settings(
exportJars := true
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

class A
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

class B {
val x = new A
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import sbt._
import Keys._

object DottyInjectedPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements

override val projectSettings = Seq(
scalaVersion := sys.props("plugin.scalaVersion"),
scalacOptions += "-language:Scala2Compat"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version"))
7 changes: 7 additions & 0 deletions sbt-dotty/sbt-test/source-dependencies/export-jars2/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
> compile
$ copy-file changes/A1.scala a/A.scala
$ copy-file changes/B1.scala b/B.scala
# This used to fail with "Tasty UUID (...) file did not correspond the tasty UUID (...) declared in the classfile"
# because we were somehow reading the .tasty file from the previous compilation run, even though it does not exist
# on disk anymore (#7852)
> compile