Skip to content

Commit

Permalink
Fix writing zip64's offset
Browse files Browse the repository at this point in the history
ZipFileSystem's `sync` writes all the zip file entries, followed by the
central directory ("cen") entries, counting the all `written` bytes,
which it passes to write the end ("end") entry.  ZipCentralDir's `dump`
skips writing the file entries and only writes the cen entries, which
means that its `written` is missing all the bytes of the file entries.
Fortunately that missing value is recorded in the `end` as the offset
where the cen entries start, so we fix `written` with `cenoff`.  This
bug only impacted writing zip64 jars, through ZipCentralDir.
  • Loading branch information
dwijnand committed Jun 17, 2023
1 parent 66fcbeb commit 8618e21
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public void dump(OutputStream os) throws IOException {
}
end.centot = elist.size();
end.cenlen = written;
end.write(os, written);
end.write(os, written + end.cenoff);
}

private List<Entry> readEntries() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package sbt.internal.inc
package classfile

import java.nio.file._
import java.nio.file.spi.FileSystemProvider
import scala.collection.JavaConverters._

class ZipMerge extends UnitSpec {
private val LargeNum = (1 << 16) + 10
private val random = new java.security.SecureRandom
private val tmpDir = System.getProperty("java.io.tmpdir")
private lazy val zipFsProvider =
FileSystemProvider.installedProviders().asScala
.find(_.getScheme == "jar").get

it should "create S jars" in assertSize(createJar(5), 5)
it should "merged S jars" in assertMerge(3, 2)
it should "create L jars" in assertSize(createJar(LargeNum + 10), LargeNum + 10)
it should "merged L jars" in assertMerge(LargeNum, 10)

private def assertMerge(size1: Int, size2: Int) = {
val a = createJar(size1)
val b = createJar(size2, size1)
safely(IndexBasedZipFsOps.mergeArchives(a, b))
assertSize(a, size1 + size2)
}

private def assertSize(p: Path, size: Int) = {
val cen = safely(IndexBasedZipFsOps.readCentralDir(p.toFile))
assert(cen.getHeaders.size() == size)
}

private def createJar(n: Int, from: Int = 0) = {
val id = nextId()
val out = Path.of(tmpDir, s"repro-$id.jar")
val zipfs = zipFsProvider.newFileSystem(out, Map("create" -> "true").asJava)
val root = zipfs.getRootDirectories.iterator().next()
for (i <- from until (from + n))
Files.write(root.resolve(f"emptyID${id}NUM$i%032d.class"), Array[Byte]())
zipfs.close()
out
}

private def nextId() = String.format("%032d", random.nextInt(): Integer)
private def safely[A](op: => A) =
try op
catch { case ex: java.util.zip.ZipError => throw new ZipException(ex) }
}

// Avoid java.util.zip.ZipError, which is a VirtualMachineError!!?!
final class ZipException(val cause: Throwable) extends Exception
with scala.util.control.NoStackTrace {
override def toString: String = cause.toString
override def getCause: Throwable = cause.getCause
override def getMessage: String = cause.getMessage
override def getStackTrace: Array[StackTraceElement] = cause.getStackTrace
override def printStackTrace(s: java.io.PrintStream): Unit = cause.printStackTrace(s)
override def printStackTrace(s: java.io.PrintWriter): Unit = cause.printStackTrace(s)
}

0 comments on commit 8618e21

Please sign in to comment.