aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Cross <ccross@android.com>2022-06-01 10:43:51 -0700
committerColin Cross <ccross@android.com>2022-06-01 10:44:12 -0700
commited997d53d2a663e7257fccbc958ee2d2e35da35f (patch)
treeb5a4e7457eb6d69f42db8178bf6cba84bdfc33e3
parent88b1fee62510ad8cc1bd1bd57bab618f1c017b32 (diff)
parentf42d03f5b18a61a3cdaf2f903e54618771c8797a (diff)
downloadturbine-ed997d53d2a663e7257fccbc958ee2d2e35da35f.tar.gz
Merge remote-tracking branch 'aosp/upstream-main'
* aosp/upstream-main: Handle `sealed` psuedo-modifier on nested classes Update ASM version Update ci.yml Use well-formed URIs for `FileObject#toURI` Follow-up to https://github.com/google/turbine/commit/9bf393cbcfc281bbac6e3d3f042b4444156567ac Support zip64 extensible data sectors Initial support for text blocks Test: m checkbuild Bug: 234108066 Bug: 234109366 Change-Id: Ib4a8b0be490af955e03e34b0180e9836e20b80db
-rw-r--r--.github/workflows/ci.yml8
-rw-r--r--METADATA12
-rw-r--r--java/com/google/turbine/parse/Parser.java5
-rw-r--r--java/com/google/turbine/parse/StreamLexer.java162
-rw-r--r--java/com/google/turbine/processing/TurbineFiler.java4
-rw-r--r--java/com/google/turbine/zip/Zip.java34
-rw-r--r--javatests/com/google/turbine/lower/LowerIntegrationTest.java8
-rw-r--r--javatests/com/google/turbine/lower/testdata/sealed_nested.test7
-rw-r--r--javatests/com/google/turbine/lower/testdata/textblock.test30
-rw-r--r--javatests/com/google/turbine/parse/LexerTest.java27
-rw-r--r--javatests/com/google/turbine/parse/ParseErrorTest.java13
-rw-r--r--javatests/com/google/turbine/processing/ProcessingIntegrationTest.java60
-rw-r--r--javatests/com/google/turbine/zip/ZipTest.java163
-rw-r--r--pom.xml2
14 files changed, 517 insertions, 18 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e12698c..c5cea7a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,19 +29,19 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
- java: [ 17, 11 ]
+ java: [ 18, 17, 11 ]
experimental: [ false ]
include:
# Only test on macos and windows with a single recent JDK to avoid a
# combinatorial explosion of test configurations.
- os: macos-latest
- java: 17
+ java: 18
experimental: false
- os: windows-latest
- java: 17
+ java: 18
experimental: false
- os: ubuntu-latest
- java: 18-ea
+ java: 19-ea
experimental: true
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
diff --git a/METADATA b/METADATA
index e166a49..ad28c65 100644
--- a/METADATA
+++ b/METADATA
@@ -1,7 +1,5 @@
name: "turbine"
-description:
- "Turbine is a header compiler for Java."
-
+description: "Turbine is a header compiler for Java."
third_party {
url {
type: HOMEPAGE
@@ -11,7 +9,11 @@ third_party {
type: GIT
value: "https://github.com/google/turbine"
}
- version: "a963d859dc98108c37a701c1f76c4494fc480198"
- last_upgrade_date { year: 2020 month: 3 day: 27 }
+ version: "f42d03f5b18a61a3cdaf2f903e54618771c8797a"
license_type: NOTICE
+ last_upgrade_date {
+ year: 2022
+ month: 6
+ day: 1
+ }
}
diff --git a/java/com/google/turbine/parse/Parser.java b/java/com/google/turbine/parse/Parser.java
index c370ad8..9417801 100644
--- a/java/com/google/turbine/parse/Parser.java
+++ b/java/com/google/turbine/parse/Parser.java
@@ -719,6 +719,11 @@ public class Parser {
case IDENT:
Ident ident = ident();
+ if (ident.value().equals("sealed")) {
+ next();
+ access.add(TurbineModifier.SEALED);
+ break;
+ }
if (ident.value().equals("non")) {
int pos = position;
next();
diff --git a/java/com/google/turbine/parse/StreamLexer.java b/java/com/google/turbine/parse/StreamLexer.java
index 2348385..3d46b90 100644
--- a/java/com/google/turbine/parse/StreamLexer.java
+++ b/java/com/google/turbine/parse/StreamLexer.java
@@ -17,8 +17,11 @@
package com.google.turbine.parse;
import static com.google.common.base.Verify.verify;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.turbine.parse.UnicodeEscapePreprocessor.ASCII_SUB;
+import static java.lang.Math.min;
+import com.google.common.collect.ImmutableList;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.diag.TurbineError.ErrorKind;
@@ -399,6 +402,15 @@ public class StreamLexer implements Lexer {
case '"':
{
eat();
+ if (ch == '"') {
+ eat();
+ if (ch != '"') {
+ saveValue("");
+ return Token.STRING_LITERAL;
+ }
+ eat();
+ return textBlock();
+ }
readFrom();
StringBuilder sb = new StringBuilder();
STRING:
@@ -436,6 +448,156 @@ public class StreamLexer implements Lexer {
}
}
+ private Token textBlock() {
+ OUTER:
+ while (true) {
+ switch (ch) {
+ case ' ':
+ case '\r':
+ case '\t':
+ eat();
+ break;
+ default:
+ break OUTER;
+ }
+ }
+ switch (ch) {
+ case '\r':
+ eat();
+ if (ch == '\n') {
+ eat();
+ }
+ break;
+ case '\n':
+ eat();
+ break;
+ default:
+ throw inputError();
+ }
+ readFrom();
+ StringBuilder sb = new StringBuilder();
+ while (true) {
+ switch (ch) {
+ case '"':
+ eat();
+ if (ch != '"') {
+ sb.append("\"");
+ continue;
+ }
+ eat();
+ if (ch != '"') {
+ sb.append("\"\"");
+ continue;
+ }
+ eat();
+ String value = sb.toString();
+ value = stripIndent(value);
+ value = translateEscapes(value);
+ saveValue(value);
+ return Token.STRING_LITERAL;
+ case ASCII_SUB:
+ if (reader.done()) {
+ return Token.EOF;
+ }
+ // falls through
+ default:
+ sb.appendCodePoint(ch);
+ eat();
+ continue;
+ }
+ }
+ }
+
+ static String stripIndent(String value) {
+ if (value.isEmpty()) {
+ return value;
+ }
+ ImmutableList<String> lines = value.lines().collect(toImmutableList());
+ // the amount of whitespace to strip from the beginning of every line
+ int strip = Integer.MAX_VALUE;
+ char last = value.charAt(value.length() - 1);
+ boolean trailingNewline = last == '\n' || last == '\r';
+ if (trailingNewline) {
+ // If the input contains a trailing newline, we have something like:
+ //
+ // |String s = """
+ // | foo
+ // |""";
+ //
+ // Because the final """ is unindented, nothing should be stripped.
+ strip = 0;
+ } else {
+ // find the longest common prefix of whitespace across all non-blank lines
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ int nonWhitespaceStart = nonWhitespaceStart(line);
+ if (nonWhitespaceStart == line.length()) {
+ continue;
+ }
+ strip = min(strip, nonWhitespaceStart);
+ }
+ }
+ StringBuilder result = new StringBuilder();
+ boolean first = true;
+ for (String line : lines) {
+ if (!first) {
+ result.append('\n');
+ }
+ int end = trailingWhitespaceStart(line);
+ if (strip <= end) {
+ result.append(line, strip, end);
+ }
+ first = false;
+ }
+ if (trailingNewline) {
+ result.append('\n');
+ }
+ return result.toString();
+ }
+
+ private static int nonWhitespaceStart(String value) {
+ int i = 0;
+ while (i < value.length() && Character.isWhitespace(value.charAt(i))) {
+ i++;
+ }
+ return i;
+ }
+
+ private static int trailingWhitespaceStart(String value) {
+ int i = value.length() - 1;
+ while (i >= 0 && Character.isWhitespace(value.charAt(i))) {
+ i--;
+ }
+ return i + 1;
+ }
+
+ private static String translateEscapes(String value) {
+ StreamLexer lexer =
+ new StreamLexer(new UnicodeEscapePreprocessor(new SourceFile(null, value + ASCII_SUB)));
+ return lexer.translateEscapes();
+ }
+
+ private String translateEscapes() {
+ readFrom();
+ StringBuilder sb = new StringBuilder();
+ OUTER:
+ while (true) {
+ switch (ch) {
+ case '\\':
+ eat();
+ sb.append(escape());
+ continue;
+ case ASCII_SUB:
+ break OUTER;
+ default:
+ sb.appendCodePoint(ch);
+ eat();
+ continue;
+ }
+ }
+ return sb.toString();
+ }
+
private char escape() {
boolean zeroToThree = false;
switch (ch) {
diff --git a/java/com/google/turbine/processing/TurbineFiler.java b/java/com/google/turbine/processing/TurbineFiler.java
index 45cdc22..f440ada 100644
--- a/java/com/google/turbine/processing/TurbineFiler.java
+++ b/java/com/google/turbine/processing/TurbineFiler.java
@@ -232,7 +232,7 @@ public class TurbineFiler implements Filer {
@Override
public URI toUri() {
- return URI.create("file://" + path);
+ return URI.create("file:///" + path);
}
@Override
@@ -309,7 +309,7 @@ public class TurbineFiler implements Filer {
@Override
public URI toUri() {
- return URI.create("file://" + name + kind.extension);
+ return URI.create("file:///" + name + kind.extension);
}
@Override
diff --git a/java/com/google/turbine/zip/Zip.java b/java/com/google/turbine/zip/Zip.java
index fa0f0e0..d732b35 100644
--- a/java/com/google/turbine/zip/Zip.java
+++ b/java/com/google/turbine/zip/Zip.java
@@ -65,7 +65,6 @@ import java.util.zip.ZipException;
* supported.
* <li>UTF-8 is the only supported encoding.
* <li>STORED and DEFLATE are the only supported compression methods.
- * <li>zip64 extensible data sectors are not supported.
* <li>Zip files larger than Integer.MAX_VALUE bytes are not supported.
* <li>The only supported ZIP64 field is ENDTOT. This implementation assumes that the ZIP64 end
* header is present only if ENDTOT in EOCD header is 0xFFFF.
@@ -74,6 +73,7 @@ import java.util.zip.ZipException;
public final class Zip {
static final int ZIP64_ENDSIG = 0x06064b50;
+ static final int ZIP64_LOCSIG = 0x07064b50;
static final int LOCHDR = 30; // LOC header size
static final int CENHDR = 46; // CEN header size
@@ -196,20 +196,44 @@ public final class Zip {
if (totalEntries == ZIP64_MAGICCOUNT) {
// Assume the zip64 EOCD has the usual size; we don't support zip64 extensible data sectors.
long zip64eocdOffset = size - ENDHDR - ZIP64_LOCHDR - ZIP64_ENDHDR;
- MappedByteBuffer zip64eocd = chan.map(MapMode.READ_ONLY, zip64eocdOffset, ZIP64_ENDHDR);
- zip64eocd.order(ByteOrder.LITTLE_ENDIAN);
// Note that zip reading is necessarily best-effort, since an archive could contain 0xFFFF
// entries and the last entry's data could contain a ZIP64_ENDSIG. Some implementations
// read the full EOCD records and compare them.
- if (zip64eocd.getInt(0) == ZIP64_ENDSIG) {
- cdsize = zip64eocd.getLong(ZIP64_ENDSIZ);
+ long zip64cdsize = zip64cdsize(chan, zip64eocdOffset);
+ if (zip64cdsize != -1) {
eocdOffset = zip64eocdOffset;
+ cdsize = zip64cdsize;
+ } else {
+ // If we couldn't find a zip64 EOCD at a fixed offset, either it doesn't exist
+ // or there was a zip64 extensible data sector, so try going through the
+ // locator. This approach doesn't work if data was prepended to the archive
+ // without updating the offset in the locator.
+ MappedByteBuffer zip64loc =
+ chan.map(MapMode.READ_ONLY, size - ENDHDR - ZIP64_LOCHDR, ZIP64_LOCHDR);
+ zip64loc.order(ByteOrder.LITTLE_ENDIAN);
+ if (zip64loc.getInt(0) == ZIP64_LOCSIG) {
+ zip64eocdOffset = zip64loc.getLong(8);
+ zip64cdsize = zip64cdsize(chan, zip64eocdOffset);
+ if (zip64cdsize != -1) {
+ eocdOffset = zip64eocdOffset;
+ cdsize = zip64cdsize;
+ }
+ }
}
}
this.cd = chan.map(MapMode.READ_ONLY, eocdOffset - cdsize, cdsize);
cd.order(ByteOrder.LITTLE_ENDIAN);
}
+ static long zip64cdsize(FileChannel chan, long eocdOffset) throws IOException {
+ MappedByteBuffer zip64eocd = chan.map(MapMode.READ_ONLY, eocdOffset, ZIP64_ENDHDR);
+ zip64eocd.order(ByteOrder.LITTLE_ENDIAN);
+ if (zip64eocd.getInt(0) == ZIP64_ENDSIG) {
+ return zip64eocd.getLong(ZIP64_ENDSIZ);
+ }
+ return -1;
+ }
+
@Override
public Iterator<Entry> iterator() {
return new ZipIterator(path, chan, cd);
diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
index 97170ca..94f1d07 100644
--- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java
+++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
@@ -44,7 +44,12 @@ import org.junit.runners.Parameterized.Parameters;
public class LowerIntegrationTest {
private static final ImmutableMap<String, Integer> SOURCE_VERSION =
- ImmutableMap.of("record.test", 16, "record2.test", 16, "sealed.test", 17);
+ ImmutableMap.of(
+ "record.test", 16, //
+ "record2.test", 16,
+ "sealed.test", 17,
+ "sealed_nested.test", 17,
+ "textblock.test", 15);
@Parameters(name = "{index}: {0}")
public static Iterable<Object[]> parameters() {
@@ -285,6 +290,7 @@ public class LowerIntegrationTest {
"superabstract.test",
"supplierfunction.test",
"tbound.test",
+ "textblock.test",
"tyanno_inner.test",
"tyanno_varargs.test",
"typaram.test",
diff --git a/javatests/com/google/turbine/lower/testdata/sealed_nested.test b/javatests/com/google/turbine/lower/testdata/sealed_nested.test
new file mode 100644
index 0000000..6c4304e
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/sealed_nested.test
@@ -0,0 +1,7 @@
+=== T.java ===
+
+class T {
+ static sealed class Sealed permits Sealed.Foo {
+ static final class Foo extends Sealed {}
+ }
+}
diff --git a/javatests/com/google/turbine/lower/testdata/textblock.test b/javatests/com/google/turbine/lower/testdata/textblock.test
new file mode 100644
index 0000000..9683296
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/textblock.test
@@ -0,0 +1,30 @@
+=== TextBlock.java ===
+class TextBlock {
+ public static final String hello = """
+ hello
+ world
+ """;
+ public static final String escape = """
+ hello\nworld\"
+ \r\t\b
+ \0123
+ \'
+ \\
+ \"
+ """;
+ public static final String quotes = """
+ " "" ""\" """;
+ public static final String newline = """
+ hello
+ world""";
+ public static final String blank = """
+ hello
+
+
+ world
+ """;
+ public static final String allBlank = """
+
+
+ """;
+}
diff --git a/javatests/com/google/turbine/parse/LexerTest.java b/javatests/com/google/turbine/parse/LexerTest.java
index c3d7804..bf0b374 100644
--- a/javatests/com/google/turbine/parse/LexerTest.java
+++ b/javatests/com/google/turbine/parse/LexerTest.java
@@ -17,11 +17,15 @@
package com.google.turbine.parse;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import com.google.common.escape.SourceCodeEscapers;
+import com.google.common.truth.Expect;
import com.google.turbine.diag.SourceFile;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -29,6 +33,8 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class LexerTest {
+ @Rule public final Expect expect = Expect.create();
+
@Test
public void testSimple() {
assertThat(lex("\nasd dsa\n")).containsExactly("IDENT(asd)", "IDENT(dsa)", "EOF");
@@ -367,4 +373,25 @@ public class LexerTest {
} while (token != Token.EOF);
return tokens;
}
+
+ @Test
+ public void stripIndent() throws Exception {
+ assumeTrue(Runtime.version().feature() >= 13);
+ String[] inputs = {
+ "",
+ "hello",
+ "hello\n",
+ "\nhello",
+ "\n hello\n world",
+ "\n hello\n world\n ",
+ "\n hello\n world\n",
+ "\n hello\n world\n ",
+ "\n hello\nworld",
+ "\n hello\n \nworld\n ",
+ };
+ Method stripIndent = String.class.getMethod("stripIndent");
+ for (String input : inputs) {
+ expect.that(StreamLexer.stripIndent(input)).isEqualTo(stripIndent.invoke(input));
+ }
+ }
}
diff --git a/javatests/com/google/turbine/parse/ParseErrorTest.java b/javatests/com/google/turbine/parse/ParseErrorTest.java
index 2c48b81..0187ce0 100644
--- a/javatests/com/google/turbine/parse/ParseErrorTest.java
+++ b/javatests/com/google/turbine/parse/ParseErrorTest.java
@@ -307,6 +307,19 @@ public class ParseErrorTest {
" ^"));
}
+ @Test
+ public void singleLineTextBlockRejected() {
+ String input = "class T { String s = \"\"\" \"\"\"; }";
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unexpected input: \"",
+ "class T { String s = \"\"\" \"\"\"; }",
+ " ^"));
+ }
+
private static String lines(String... lines) {
return Joiner.on(System.lineSeparator()).join(lines);
}
diff --git a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
index fee2c75..65c7ed5 100644
--- a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
+++ b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
@@ -48,6 +48,9 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
@@ -61,6 +64,7 @@ import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ExecutableType;
import javax.tools.Diagnostic;
+import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import org.junit.Test;
@@ -804,4 +808,60 @@ public class ProcessingIntegrationTest {
"C#f<U>(java.util.List<U>)U <: A#f<U>(java.util.List<U>)U ? false",
"C#f<U>(java.util.List<U>)U <: B#f<U>(java.util.List<U>)U ? false");
}
+
+ @SupportedAnnotationTypes("*")
+ public static class URIProcessor extends AbstractProcessor {
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ private boolean first = true;
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (!first) {
+ return false;
+ }
+ first = false;
+ try {
+ FileObject output =
+ processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "foo", "Bar");
+ Path path = Paths.get(output.toUri());
+ processingEnv
+ .getMessager()
+ .printMessage(Diagnostic.Kind.ERROR, output.toUri() + " - " + path);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return false;
+ }
+ }
+
+ @Test
+ public void uriProcessing() throws IOException {
+ ImmutableList<Tree.CompUnit> units =
+ parseUnit(
+ "=== T.java ===", //
+ "class T {}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ ProcessorInfo.create(
+ ImmutableList.of(new URIProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ assertThat(
+ e.diagnostics().stream()
+ .filter(d -> d.severity().equals(Diagnostic.Kind.ERROR))
+ .map(d -> d.message()))
+ .containsExactly("file:///foo/Bar - " + Paths.get(URI.create("file:///foo/Bar")));
+ }
}
diff --git a/javatests/com/google/turbine/zip/ZipTest.java b/javatests/com/google/turbine/zip/ZipTest.java
index e9dfc44..2b6636d 100644
--- a/javatests/com/google/turbine/zip/ZipTest.java
+++ b/javatests/com/google/turbine/zip/ZipTest.java
@@ -25,6 +25,8 @@ import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -38,6 +40,7 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.junit.Rule;
import org.junit.Test;
@@ -164,4 +167,164 @@ public class ZipTest {
ZipException e = assertThrows(ZipException.class, () -> actual(path));
assertThat(e).hasMessageThat().isEqualTo("zip file comment length was 33, expected 17");
}
+
+ // Create a zip64 archive with an extensible data sector
+ @Test
+ public void zip64extension() throws IOException {
+
+ ByteBuffer buf = ByteBuffer.allocate(1000);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+
+ // The jar has a single entry named 'hello', with the value 'world'
+ byte[] name = "hello".getBytes(UTF_8);
+ byte[] value = "world".getBytes(UTF_8);
+ int crc = Hashing.crc32().hashBytes(value).asInt();
+
+ int localHeaderPosition = buf.position();
+
+ // local file header signature 4 bytes (0x04034b50)
+ buf.putInt((int) ZipFile.LOCSIG);
+ // version needed to extract 2 bytes
+ buf.putShort((short) 0);
+ // general purpose bit flag 2 bytes
+ buf.putShort((short) 0);
+ // compression method 2 bytes
+ buf.putShort((short) 0);
+ // last mod file time 2 bytes
+ buf.putShort((short) 0);
+ // last mod file date 2 bytes
+ buf.putShort((short) 0);
+ // crc-32 4 bytes
+ buf.putInt(crc);
+ // compressed size 4 bytes
+ buf.putInt(value.length);
+ // uncompressed size 4 bytes
+ buf.putInt(value.length);
+ // file name length 2 bytes
+ buf.putShort((short) name.length);
+ // extra field length 2 bytes
+ buf.putShort((short) 0);
+ // file name (variable size)
+ buf.put(name);
+ // extra field (variable size)
+ // file data
+ buf.put(value);
+
+ int centralDirectoryPosition = buf.position();
+
+ // central file header signature 4 bytes (0x02014b50)
+ buf.putInt((int) ZipFile.CENSIG);
+ // version made by 2 bytes
+ buf.putShort((short) 0);
+ // version needed to extract 2 bytes
+ buf.putShort((short) 0);
+ // general purpose bit flag 2 bytes
+ buf.putShort((short) 0);
+ // compression method 2 bytes
+ buf.putShort((short) 0);
+ // last mod file time 2 bytes
+ buf.putShort((short) 0);
+ // last mod file date 2 bytes
+ buf.putShort((short) 0);
+ // crc-32 4 bytes
+ buf.putInt(crc);
+ // compressed size 4 bytes
+ buf.putInt(value.length);
+ // uncompressed size 4 bytes
+ buf.putInt(value.length);
+ // file name length 2 bytes
+ buf.putShort((short) name.length);
+ // extra field length 2 bytes
+ buf.putShort((short) 0);
+ // file comment length 2 bytes
+ buf.putShort((short) 0);
+ // disk number start 2 bytes
+ buf.putShort((short) 0);
+ // internal file attributes 2 bytes
+ buf.putShort((short) 0);
+ // external file attributes 4 bytes
+ buf.putInt(0);
+ // relative offset of local header 4 bytes
+ buf.putInt(localHeaderPosition);
+ // file name (variable size)
+ buf.put(name);
+
+ int centralDirectorySize = buf.position() - centralDirectoryPosition;
+ int zip64eocdPosition = buf.position();
+
+ // zip64 end of central dir
+ // signature 4 bytes (0x06064b50)
+ buf.putInt(Zip.ZIP64_ENDSIG);
+ // size of zip64 end of central
+ // directory record 8 bytes
+ buf.putLong(Zip.ZIP64_ENDSIZ + 5);
+ // version made by 2 bytes
+ buf.putShort((short) 0);
+ // version needed to extract 2 bytes
+ buf.putShort((short) 0);
+ // number of this disk 4 bytes
+ buf.putInt(0);
+ // number of the disk with the
+ // start of the central directory 4 bytes
+ buf.putInt(0);
+ // total number of entries in the
+ // central directory on this disk 8 bytes
+ buf.putLong(1);
+ // total number of entries in the
+ // central directory 8 bytes
+ buf.putLong(1);
+ // size of the central directory 8 bytes
+ buf.putLong(centralDirectorySize);
+ // offset of start of central
+ // directory with respect to
+ // offset of start of central
+ // the starting disk number 8 bytes
+ buf.putLong(centralDirectoryPosition);
+ // zip64 extensible data sector (variable size)
+ buf.put((byte) 3);
+ buf.putInt(42);
+
+ // zip64 end of central dir locator
+ // signature 4 bytes (0x07064b50)
+ buf.putInt(Zip.ZIP64_LOCSIG);
+ // number of the disk with the
+ // start of the zip64 end of
+ // central directory 4 bytes
+ buf.putInt(0);
+ // relative offset of the zip64
+ // end of central directory record 8 bytes
+ buf.putLong(zip64eocdPosition);
+ // total number of disks 4 bytes
+ buf.putInt(0);
+
+ // end of central dir signature 4 bytes (0x06054b50)
+ buf.putInt((int) ZipFile.ENDSIG);
+ // number of this disk 2 bytes
+ buf.putShort((short) 0);
+ // number of the disk with the
+ // start of the central directory 2 bytes
+ buf.putShort((short) 0);
+ // total number of entries in the
+ // central directory on this disk 2 bytes
+ buf.putShort((short) 1);
+ // total number of entries in
+ // the central directory 2 bytes
+ buf.putShort((short) Zip.ZIP64_MAGICCOUNT);
+ // size of the central directory 4 bytes
+ buf.putInt(centralDirectorySize);
+ // offset of start of central
+ // directory with respect to
+ // the starting disk number 4 bytes
+ buf.putInt(centralDirectoryPosition);
+ // .ZIP file comment length 2 bytes
+ buf.putShort((short) 0);
+ // .ZIP file comment (variable size)
+
+ byte[] bytes = new byte[buf.position()];
+ buf.rewind();
+ buf.get(bytes);
+ Path path = temporaryFolder.newFile("test.jar").toPath();
+ Files.write(path, bytes);
+ assertThat(actual(path)).isEqualTo(expected(path));
+ }
}
diff --git a/pom.xml b/pom.xml
index b007e74..38e91d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,7 +30,7 @@
<url>https://github.com/google/turbine</url>
<properties>
- <asm.version>9.2</asm.version>
+ <asm.version>9.3</asm.version>
<guava.version>31.0.1-jre</guava.version>
<errorprone.version>2.11.0</errorprone.version>
<maven-javadoc-plugin.version>3.3.1</maven-javadoc-plugin.version>