From 8618e21af3f6073abad79e697a494c9fb3a5e165 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 17 Jun 2023 00:50:29 +0100 Subject: [PATCH] Fix writing zip64's offset 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. --- .../sbt/internal/inc/zip/ZipCentralDir.java | 2 +- .../sbt/internal/inc/classfile/ZipMerge.scala | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 internal/zinc-classfile/src/test/scala/sbt/internal/inc/classfile/ZipMerge.scala diff --git a/internal/zinc-classfile/src/main/java/sbt/internal/inc/zip/ZipCentralDir.java b/internal/zinc-classfile/src/main/java/sbt/internal/inc/zip/ZipCentralDir.java index 7d9450983f..fc040f2f80 100644 --- a/internal/zinc-classfile/src/main/java/sbt/internal/inc/zip/ZipCentralDir.java +++ b/internal/zinc-classfile/src/main/java/sbt/internal/inc/zip/ZipCentralDir.java @@ -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 readEntries() throws IOException { diff --git a/internal/zinc-classfile/src/test/scala/sbt/internal/inc/classfile/ZipMerge.scala b/internal/zinc-classfile/src/test/scala/sbt/internal/inc/classfile/ZipMerge.scala new file mode 100644 index 0000000000..b6d16a444c --- /dev/null +++ b/internal/zinc-classfile/src/test/scala/sbt/internal/inc/classfile/ZipMerge.scala @@ -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) +}