From 943dd7112559ee393724cf8b8ac0fbd1ca00a34e Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Wed, 14 May 2025 01:53:19 +0000 Subject: [PATCH 1/3] 8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset 8355975: ZipFile uses incorrect Charset if another instance for the same ZIP file was constructed with a different Charset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eirik Bjørsnøs Reviewed-by: eirbjo, lancea, redestad, alanb --- .../share/classes/java/util/zip/ZipCoder.java | 22 ++ .../share/classes/java/util/zip/ZipFile.java | 317 ++++++++++++++++-- .../util/zip/ZipFile/ZipFileCharsetTest.java | 125 +++++++ .../zip/ZipFile/ZipFileSharedSourceTest.java | 137 ++++++++ 4 files changed, 568 insertions(+), 33 deletions(-) create mode 100644 test/jdk/java/util/zip/ZipFile/ZipFileCharsetTest.java create mode 100644 test/jdk/java/util/zip/ZipFile/ZipFileSharedSourceTest.java diff --git a/src/java.base/share/classes/java/util/zip/ZipCoder.java b/src/java.base/share/classes/java/util/zip/ZipCoder.java index 621b0b0bc8b..bf9cfc5f491 100644 --- a/src/java.base/share/classes/java/util/zip/ZipCoder.java +++ b/src/java.base/share/classes/java/util/zip/ZipCoder.java @@ -1,5 +1,11 @@ /* +<<<<<<< HEAD * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. +======= + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,7 +43,16 @@ import sun.nio.cs.UTF_8; /** +<<<<<<< HEAD * Utility class for zipfile name and comment decoding and encoding +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + * Utility class for ZIP file entry name and comment decoding and encoding +======= + * Utility class for ZIP file entry name and comment decoding and encoding. + *

+ * The {@code ZipCoder} for UTF-8 charset is thread safe, {@code ZipCoder} + * for other charsets require external synchronization. +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) */ class ZipCoder { @@ -157,6 +172,13 @@ protected CharsetDecoder decoder() { return dec; } + /** + * {@return the {@link Charset} used by this {@code ZipCoder}} + */ + final Charset charset() { + return this.cs; + } + private CharsetEncoder encoder() { if (enc == null) { enc = cs.newEncoder() diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java index 5b39137bf12..ef522337bc9 100644 --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -1,5 +1,11 @@ /* +<<<<<<< HEAD * Copyright (c) 1995, 2021, Oracle and/or its affiliates. All rights reserved. +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. +======= + * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved. +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -95,7 +101,17 @@ */ public class ZipFile implements ZipConstants, Closeable { +<<<<<<< HEAD private final String name; // zip file name +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + private final String filePath; // ZIP file path + private final String fileName; // name of the file +======= + private final String filePath; // ZIP file path + private final String fileName; // name of the file + // Used when decoding entry names and comments + private final ZipCoder zipCoder; +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) private volatile boolean closeRequested; // The "resource" used by this zip file that needs to be @@ -248,7 +264,8 @@ public ZipFile(File file, int mode, Charset charset) throws IOException this.name = name; long t0 = System.nanoTime(); - this.res = new CleanableResource(this, ZipCoder.get(charset), file, mode); + this.zipCoder = ZipCoder.get(charset); + this.res = new CleanableResource(this, zipCoder, file, mode); PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); PerfCounter.getZipFileCount().increment(); @@ -319,7 +336,25 @@ public String getComment() { if (res.zsrc.comment == null) { return null; } +<<<<<<< HEAD return res.zsrc.zc.toString(res.zsrc.comment); +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + // If there is a problem decoding the byte array which represents + // the ZIP file comment, return null; + try { + return res.zsrc.zc.toString(res.zsrc.comment); + } catch (IllegalArgumentException iae) { + return null; + } +======= + // If there is a problem decoding the byte array which represents + // the ZIP file comment, return null; + try { + return zipCoder.toString(res.zsrc.comment); + } catch (IllegalArgumentException iae) { + return null; + } +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) } } @@ -336,9 +371,25 @@ public ZipEntry getEntry(String name) { ZipEntry entry = null; synchronized (this) { ensureOpen(); +<<<<<<< HEAD int pos = res.zsrc.getEntryPos(name, true); if (pos != -1) { entry = getZipEntry(name, pos); +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + // Look up the name and CEN header position of the entry. + // The resolved name may include a trailing slash. + // See Source::getEntryPos for details. + EntryPos pos = res.zsrc.getEntryPos(name, true); + if (pos != null) { + entry = getZipEntry(pos.name, pos.pos); +======= + // Look up the name and CEN header position of the entry. + // The resolved name may include a trailing slash. + // See Source::getEntryPos for details. + EntryPos pos = res.zsrc.getEntryPos(name, true, zipCoder); + if (pos != null) { + entry = getZipEntry(pos.name, pos.pos); +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) } } return entry; @@ -369,7 +420,23 @@ public InputStream getInputStream(ZipEntry entry) throws IOException { if (Objects.equals(lastEntryName, entry.name)) { pos = lastEntryPos; } else { +<<<<<<< HEAD pos = zsrc.getEntryPos(entry.name, false); +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + EntryPos entryPos = zsrc.getEntryPos(entry.name, false); + if (entryPos != null) { + pos = entryPos.pos; + } else { + pos = -1; + } +======= + EntryPos entryPos = zsrc.getEntryPos(entry.name, false, zipCoder); + if (entryPos != null) { + pos = entryPos.pos; + } else { + pos = -1; + } +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) } if (pos == -1) { return null; @@ -402,6 +469,35 @@ public InputStream getInputStream(ZipEntry entry) throws IOException { } } + /** + * Determines and returns a {@link ZipCoder} to use for decoding + * name and comment fields of the ZIP entry identified by the {@code pos} + * in the ZIP file's {@code cen}. + *

+ * A ZIP entry's name and comment fields may be encoded using UTF-8, in + * which case this method returns a UTF-8 capable {@code ZipCoder}. If the + * entry doesn't require UTF-8, then this method returns the {@code fallback} + * {@code ZipCoder}. + * + * @param cen the CEN + * @param pos the ZIP entry's position in CEN + * @param fallback the fallback ZipCoder to return if the entry doesn't require UTF-8 + */ + private static ZipCoder zipCoderFor(final byte[] cen, final int pos, final ZipCoder fallback) { + if (fallback.isUTF8()) { + // the fallback ZipCoder is capable of handling UTF-8, + // so no need to parse the entry flags to determine if + // the entry has UTF-8 flag. + return fallback; + } + if ((CENFLG(cen, pos) & USE_UTF8) != 0) { + // entry requires a UTF-8 ZipCoder + return ZipCoder.UTF8; + } + // entry doesn't require a UTF-8 ZipCoder + return fallback; + } + private static class InflaterCleanupAction implements Runnable { private final Inflater inf; private final CleanableResource res; @@ -592,7 +688,7 @@ public Stream stream() { private String getEntryName(int pos) { byte[] cen = res.zsrc.cen; int nlen = CENNAM(cen, pos); - ZipCoder zc = res.zsrc.zipCoderForPos(pos); + ZipCoder zc = zipCoderFor(cen, pos, zipCoder); return zc.toString(cen, pos + CENHDR, nlen); } @@ -679,6 +775,12 @@ private ZipEntry getZipEntry(String name, int pos) { } if (clen != 0) { int start = pos + CENHDR + nlen + elen; +<<<<<<< HEAD +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + ZipCoder zc = res.zsrc.zipCoderForPos(pos); +======= + ZipCoder zc = zipCoderFor(cen, pos, zipCoder); +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) e.comment = zc.toString(cen, start, clen); } lastEntryName = e.name; @@ -710,11 +812,12 @@ private static class CleanableResource implements Runnable { Source zsrc; - CleanableResource(ZipFile zf, ZipCoder zc, File file, int mode) throws IOException { + CleanableResource(ZipFile zf, ZipCoder zipCoder, File file, int mode) throws IOException { + assert zipCoder != null : "null ZipCoder"; this.cleanable = CleanerFactory.cleaner().register(zf, this); this.istreams = Collections.newSetFromMap(new WeakHashMap<>()); this.inflaterCache = new ArrayDeque<>(); - this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0, zc); + this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0, zipCoder); } void clean() { @@ -1152,6 +1255,7 @@ public void setExtraAttributes(ZipEntry ze, int extraAttrs) { isWindows = VM.getSavedProperty("os.name").contains("Windows"); } + // Implementation note: This class is thread safe. private static class Source { // While this is only used from ZipFile, defining it there would cause // a bootstrap cycle that would leave this initialized as null @@ -1161,7 +1265,12 @@ private static class Source { private static final int[] EMPTY_META_VERSIONS = new int[0]; private final Key key; // the key in files +<<<<<<< HEAD private final @Stable ZipCoder zc; // zip coder used to decode/encode +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + private final @Stable ZipCoder zc; // ZIP coder used to decode/encode +======= +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) private int refs = 1; @@ -1197,8 +1306,9 @@ private static class Source { private int[] entries; // array of hashed cen entry // Checks the entry at offset pos in the CEN, calculates the Entry values as per above, - // then returns the length of the entry name. - private int checkAndAddEntry(int pos, int index) + // then returns the length of the entry name. Uses the given zipCoder for processing the + // entry name and the entry comment (if any). + private int checkAndAddEntry(final int pos, final int index, final ZipCoder zipCoder) throws ZipException { byte[] cen = this.cen; @@ -1229,15 +1339,38 @@ private int checkAndAddEntry(int pos, int index) } try { - ZipCoder zcp = zipCoderForPos(pos); - int hash = zcp.checkedHash(cen, entryPos, nlen); + int hash = zipCoder.checkedHash(cen, entryPos, nlen); int hsh = (hash & 0x7fffffff) % tablelen; int next = table[hsh]; table[hsh] = index; // Record the CEN offset and the name hash in our hash cell. +<<<<<<< HEAD + entries[index++] = hash; + entries[index++] = next; + entries[index ] = pos; +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) entries[index++] = hash; entries[index++] = next; entries[index ] = pos; + // Validate comment if it exists. + // If the bytes representing the comment cannot be converted to + // a String via zcp.toString, an Exception will be thrown + if (clen > 0) { + int start = entryPos + nlen + elen; + zcp.toString(cen, start, clen); + } +======= + entries[index] = hash; + entries[index + 1] = next; + entries[index + 2] = pos; + // Validate comment if it exists. + // If the bytes representing the comment cannot be converted to + // a String via zcp.toString, an Exception will be thrown + if (clen > 0) { + int start = entryPos + nlen + elen; + zipCoder.toString(cen, start, clen); + } +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) } catch (Exception e) { zerror("invalid CEN header (bad entry name)"); } @@ -1382,26 +1515,57 @@ private static boolean isZip64ExtBlockSizeValid(int blockSize) { private int[] table; // Hash chain heads: indexes into entries private int tablelen; // number of hash heads +<<<<<<< HEAD +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + /** + * A class representing a key to a ZIP file. A key is based + * on the file key if available, or the path value if the + * file key is not available. The key is also based on the + * file's last modified time to allow for cases where a ZIP + * file is re-opened after it has been modified. + */ +======= + /** + * A class representing a key to the Source of a ZipFile. + * The Key is composed of: + * - The BasicFileAttributes.fileKey() if available, or the Path of the ZIP file + * if the fileKey() is not available. + * - The ZIP file's last modified time (to allow for cases + * where a ZIP file is re-opened after it has been modified). + * - The Charset that was provided when constructing the ZipFile instance. + * The unique combination of these components identifies a Source of a ZipFile. + */ +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) private static class Key { - final BasicFileAttributes attrs; - File file; - final boolean utf8; - - public Key(File file, BasicFileAttributes attrs, ZipCoder zc) { + private final BasicFileAttributes attrs; + private final File file; + // the Charset that was provided when constructing the ZipFile instance + private final Charset charset; + + /** + * Constructs a {@code Key} to a {@code Source} of a {@code ZipFile} + * + * @param file the ZIP file + * @param attrs the attributes of the ZIP file + * @param charset the Charset that was provided when constructing the ZipFile instance + */ + public Key(File file, BasicFileAttributes attrs, Charset charset) { this.attrs = attrs; this.file = file; - this.utf8 = zc.isUTF8(); + this.charset = charset; } + @Override public int hashCode() { - long t = utf8 ? 0 : Long.MAX_VALUE; + long t = charset.hashCode(); t += attrs.lastModifiedTime().toMillis(); return ((int)(t ^ (t >>> 32))) + file.hashCode(); } + @Override public boolean equals(Object obj) { if (obj instanceof Key key) { - if (key.utf8 != utf8) { + if (!charset.equals(key.charset)) { return false; } if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) { @@ -1425,12 +1589,12 @@ public boolean equals(Object obj) { private static final java.nio.file.FileSystem builtInFS = DefaultFileSystemProvider.theFileSystem(); - static Source get(File file, boolean toDelete, ZipCoder zc) throws IOException { + static Source get(File file, boolean toDelete, ZipCoder zipCoder) throws IOException { final Key key; try { key = new Key(file, Files.readAttributes(builtInFS.getPath(file.getPath()), - BasicFileAttributes.class), zc); + BasicFileAttributes.class), zipCoder.charset()); } catch (InvalidPathException ipe) { throw new IOException(ipe); } @@ -1442,7 +1606,7 @@ static Source get(File file, boolean toDelete, ZipCoder zc) throws IOException { return src; } } - src = new Source(key, toDelete, zc); + src = new Source(key, toDelete, zipCoder); synchronized (files) { if (files.containsKey(key)) { // someone else put in first @@ -1465,8 +1629,7 @@ static void release(Source src) throws IOException { } } - private Source(Key key, boolean toDelete, ZipCoder zc) throws IOException { - this.zc = zc; + private Source(Key key, boolean toDelete, ZipCoder zipCoder) throws IOException { this.key = key; if (toDelete) { if (isWindows) { @@ -1480,7 +1643,7 @@ private Source(Key key, boolean toDelete, ZipCoder zc) throws IOException { this.zfile = new RandomAccessFile(key.file, "r"); } try { - initCEN(-1); + initCEN(-1, zipCoder); byte[] buf = new byte[4]; readFullyAt(buf, 0, 4, 0); this.startsWithLoc = (LOCSIG(buf) == LOCSIG); @@ -1637,8 +1800,16 @@ private End findEND() throws IOException { throw new ZipException("zip END header not found"); } +<<<<<<< HEAD // Reads zip file central directory. private void initCEN(int knownTotal) throws IOException { +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + // Reads ZIP file central directory. + private void initCEN(int knownTotal) throws IOException { +======= + // Reads ZIP file central directory. + private void initCEN(final int knownTotal, final ZipCoder zipCoder) throws IOException { +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) // Prefer locals for better performance during startup byte[] cen; if (knownTotal == -1) { @@ -1700,12 +1871,26 @@ private void initCEN(int knownTotal) throws IOException { // This will only happen if the zip file has an incorrect // ENDTOT field, which usually means it contains more than // 65535 entries. +<<<<<<< HEAD initCEN(countCENHeaders(cen, limit)); +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + initCEN(countCENHeaders(cen)); +======= + initCEN(countCENHeaders(cen), zipCoder); +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) return; } +<<<<<<< HEAD +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + int entryPos = pos + CENHDR; +======= + int entryPos = pos + CENHDR; + // the ZipCoder for any non-UTF8 entries + final ZipCoder entryZipCoder = zipCoderFor(cen, pos, zipCoder); +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) // Checks the entry and adds values to entries[idx ... idx+2] - int nlen = checkAndAddEntry(pos, idx); + int nlen = checkAndAddEntry(pos, idx, entryZipCoder); idx += 3; // Adds name to metanames. @@ -1726,9 +1911,39 @@ private void initCEN(int knownTotal) throws IOException { // performance in multi-release jar files int version = getMetaVersion(entryPos + META_INF_LEN, nlen - META_INF_LEN); if (version > 0) { +<<<<<<< HEAD if (metaVersionsSet == null) metaVersionsSet = new TreeSet<>(); metaVersionsSet.add(version); +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + try { + // Compute hash code of name from "META-INF/versions/{version)/{name} + int prefixLen = META_INF_VERSIONS_LEN + DecimalDigits.stringSize(version); + int hashCode = zipCoderForPos(pos).checkedHash(cen, + entryPos + prefixLen, + nlen - prefixLen); + // Register version for this hash code + if (metaVersions == null) + metaVersions = new HashMap<>(); + metaVersions.computeIfAbsent(hashCode, _ -> new BitSet()).set(version); + } catch (Exception e) { + zerror("invalid CEN header (bad entry name or comment)"); + } +======= + try { + // Compute hash code of name from "META-INF/versions/{version)/{name} + int prefixLen = META_INF_VERSIONS_LEN + DecimalDigits.stringSize(version); + int hashCode = entryZipCoder.checkedHash(cen, + entryPos + prefixLen, + nlen - prefixLen); + // Register version for this hash code + if (metaVersions == null) + metaVersions = new HashMap<>(); + metaVersions.computeIfAbsent(hashCode, _ -> new BitSet()).set(version); + } catch (Exception e) { + zerror("invalid CEN header (bad entry name or comment)"); + } +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) } } } @@ -1770,10 +1985,25 @@ private static void zerror(String msg) throws ZipException { } /* +<<<<<<< HEAD * Returns the {@code pos} of the zip cen entry corresponding to the * specified entry name, or -1 if not found. +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + * Returns the resolved name and position of the ZIP cen entry corresponding + * to the specified entry name, or {@code null} if not found. +======= + * Returns the resolved name and position of the ZIP cen entry corresponding + * to the specified entry name, or {@code null} if not found. +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) */ +<<<<<<< HEAD private int getEntryPos(String name, boolean addSlash) { +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + private EntryPos getEntryPos(String name, boolean addSlash) { +======= + private EntryPos getEntryPos(final String name, final boolean addSlash, + final ZipCoder zipCoder) { +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) if (total == 0) { return -1; } @@ -1788,6 +2018,7 @@ private int getEntryPos(String name, boolean addSlash) { // The CEN name must match the specfied one int pos = getEntryPos(idx); +<<<<<<< HEAD try { ZipCoder zc = zipCoderForPos(pos); String entry = zc.toString(cen, pos + CENHDR, CENNAM(cen, pos)); @@ -1811,6 +2042,36 @@ private int getEntryPos(String name, boolean addSlash) { } } catch (IllegalArgumentException iae) { // Ignore +||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + ZipCoder zc = zipCoderForPos(pos); + + // Compare the lookup name with the name encoded in the CEN + switch (zc.compare(name, cen, noff, nlen, addSlash)) { + case ZipCoder.EXACT_MATCH: + // We found an exact match for "name" + return new EntryPos(name, pos); + case ZipCoder.DIRECTORY_MATCH: + // We found the directory "name/" + // Track its position, then continue the search for "name" + dirPos = pos; + break; + case ZipCoder.NO_MATCH: + // Hash collision, continue searching +======= + final ZipCoder zc = zipCoderFor(cen, pos, zipCoder); + // Compare the lookup name with the name encoded in the CEN + switch (zc.compare(name, cen, noff, nlen, addSlash)) { + case ZipCoder.EXACT_MATCH: + // We found an exact match for "name" + return new EntryPos(name, pos); + case ZipCoder.DIRECTORY_MATCH: + // We found the directory "name/" + // Track its position, then continue the search for "name" + dirPos = pos; + break; + case ZipCoder.NO_MATCH: + // Hash collision, continue searching +>>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) } } idx = getEntryNext(idx); @@ -1818,16 +2079,6 @@ private int getEntryPos(String name, boolean addSlash) { return -1; } - private ZipCoder zipCoderForPos(int pos) { - if (zc.isUTF8()) { - return zc; - } - if ((CENFLG(cen, pos) & USE_UTF8) != 0) { - return ZipCoder.UTF8; - } - return zc; - } - /** * Returns true if the bytes represent a non-directory name * beginning with "META-INF/", disregarding ASCII case. diff --git a/test/jdk/java/util/zip/ZipFile/ZipFileCharsetTest.java b/test/jdk/java/util/zip/ZipFile/ZipFileCharsetTest.java new file mode 100644 index 00000000000..e97088247f4 --- /dev/null +++ b/test/jdk/java/util/zip/ZipFile/ZipFileCharsetTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.junit.jupiter.api.Test; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/* + * @test + * @bug 8355975 + * @summary verify that the internal ZIP structure caching in java.util.zip.ZipFile + * uses the correct Charset when parsing the ZIP structure of a ZIP file + * @run junit ZipFileCharsetTest + */ +public class ZipFileCharsetTest { + + private static final String ISO_8859_15_NAME = "ISO-8859-15"; + + /** + * The internal implementation of java.util.zip.ZipFile maintains a cache + * of the ZIP structure of each ZIP file that's currently open. This cache + * helps prevent repeat parsing of the ZIP structure of the same underlying + * ZIP file, every time a ZipFile instance is created for the same ZIP file. + * The cache uses an internal key to map a ZIP file to the corresponding + * ZIP structure that's cached. + * A ZipFile can be constructed by passing a Charset which will be used to + * decode the entry names (and comment) in a ZIP file. + * The test verifies that when multiple ZipFile instances are + * constructed using different Charsets but the same underlying ZIP file, + * then the internal caching implementation of ZipFile doesn't end up using + * a wrong Charset for parsing the ZIP structure of the ZIP file. + */ + @Test + void testCachedZipFileSource() throws Exception { + // ISO-8859-15 is not a standard charset in Java. We skip this test + // when it is unavailable + assumeTrue(Charset.availableCharsets().containsKey(ISO_8859_15_NAME), + "skipping test since " + ISO_8859_15_NAME + " charset isn't available"); + + // We choose the byte 0xA4 for entry name in the ZIP file. + // 0xA4 is "Euro sign" in ISO-8859-15 charset and + // "Currency sign (generic)" in ISO-8859-1 charset. + final byte[] entryNameBytes = new byte[]{(byte) 0xA4}; // intentional cast + final Charset euroSignCharset = Charset.forName(ISO_8859_15_NAME); + final Charset currencySignCharset = ISO_8859_1; + + final String euroSign = new String(entryNameBytes, euroSignCharset); + final String currencySign = new String(entryNameBytes, currencySignCharset); + + // create a ZIP file whose entry name is encoded using ISO-8859-15 charset + final Path zip = createZIP("euro", euroSignCharset, entryNameBytes); + + // Construct a ZipFile instance using the (incorrect) charset ISO-8859-1. + // While that ZipFile instance is still open (and the ZIP file structure + // still cached), construct another instance for the same ZIP file, using + // the (correct) charset ISO-8859-15. + try (ZipFile incorrect = new ZipFile(zip.toFile(), currencySignCharset); + ZipFile correct = new ZipFile(zip.toFile(), euroSignCharset)) { + + // correct encoding should resolve the entry name to euro sign + // and the entry should be thus be located + assertNotNull(correct.getEntry(euroSign), "euro sign entry missing in " + correct); + // correct encoding should not be able to find an entry name + // with the currency sign + assertNull(correct.getEntry(currencySign), "currency sign entry unexpectedly found in " + + correct); + + // incorrect encoding should resolve the entry name to currency sign + // and the entry should be thus be located by the currency sign name + assertNotNull(incorrect.getEntry(currencySign), "currency sign entry missing in " + + incorrect); + // incorrect encoding should not be able to find an entry name + // with the euro sign + assertNull(incorrect.getEntry(euroSign), "euro sign entry unexpectedly found in " + + incorrect); + } + } + + /** + * Creates and return ZIP file whose entry names are encoded using the given {@code charset} + */ + private static Path createZIP(final String fileNamePrefix, final Charset charset, + final byte[] entryNameBytes) throws IOException { + final Path zip = Files.createTempFile(Path.of("."), fileNamePrefix, ".zip"); + // create a ZIP file whose entry name(s) use the given charset + try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zip), charset)) { + zos.putNextEntry(new ZipEntry(new String(entryNameBytes, charset))); + final byte[] entryContent = "doesnotmatter".getBytes(US_ASCII); + zos.write(entryContent); + zos.closeEntry(); + } + return zip; + } +} diff --git a/test/jdk/java/util/zip/ZipFile/ZipFileSharedSourceTest.java b/test/jdk/java/util/zip/ZipFile/ZipFileSharedSourceTest.java new file mode 100644 index 00000000000..d4d057ede7b --- /dev/null +++ b/test/jdk/java/util/zip/ZipFile/ZipFileSharedSourceTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static java.nio.charset.StandardCharsets.US_ASCII; + +/* + * @test + * @bug 8347712 + * @summary verify that different instances of java.util.zip.ZipFile do not share + * the same instance of (non-thread-safe) java.nio.charset.CharsetEncoder/CharsetDecoder + * @run junit ZipFileSharedSourceTest + */ +public class ZipFileSharedSourceTest { + + static Path createZipFile(final Charset charset) throws Exception { + final Path zipFilePath = Files.createTempFile(Path.of("."), "8347712", ".zip"); + try (OutputStream os = Files.newOutputStream(zipFilePath); + ZipOutputStream zos = new ZipOutputStream(os, charset)) { + final int numEntries = 10240; + for (int i = 1; i <= numEntries; i++) { + final ZipEntry entry = new ZipEntry("entry-" + i); + zos.putNextEntry(entry); + zos.write("foo bar".getBytes(US_ASCII)); + zos.closeEntry(); + } + } + return zipFilePath; + } + + static List charsets() { + return List.of( + Arguments.of(StandardCharsets.UTF_8), + Arguments.of(StandardCharsets.ISO_8859_1), + Arguments.of(US_ASCII) + ); + } + + /** + * In this test, multiple concurrent threads each create an instance of java.util.zip.ZipFile + * with the given {@code charset} for the same underlying ZIP file. Each of the threads + * then iterate over the entries of their ZipFile instance. The test verifies that such access, + * where each thread is accessing an independent ZipFile instance corresponding to the same + * underlying ZIP file, doesn't lead to unexpected failures contributed by concurrent + * threads. + */ + @ParameterizedTest + @MethodSource("charsets") + void testMultipleZipFileInstances(final Charset charset) throws Exception { + final Path zipFilePath = createZipFile(charset); + final int numTasks = 200; + final CountDownLatch startLatch = new CountDownLatch(numTasks); + final List> results = new ArrayList<>(); + try (final ExecutorService executor = + Executors.newThreadPerTaskExecutor(Thread.ofPlatform().factory())) { + for (int i = 0; i < numTasks; i++) { + final var task = new ZipEntryIteratingTask(zipFilePath, charset, + startLatch); + results.add(executor.submit(task)); + } + System.out.println(numTasks + " tasks submitted, waiting for them to complete"); + for (final Future f : results) { + f.get(); + } + } + System.out.println("All " + numTasks + " tasks completed successfully"); + } + + private static final class ZipEntryIteratingTask implements Callable { + private final Path file; + private final Charset charset; + private final CountDownLatch startLatch; + + private ZipEntryIteratingTask(final Path file, final Charset charset, + final CountDownLatch startLatch) { + this.file = file; + this.charset = charset; + this.startLatch = startLatch; + } + + @Override + public Void call() throws Exception { + // let other tasks know we are ready to run + this.startLatch.countDown(); + // wait for other tasks to be ready to run + this.startLatch.await(); + // create a new instance of ZipFile and iterate over the entries + try (final ZipFile zf = new ZipFile(this.file.toFile(), this.charset)) { + final var entries = zf.entries(); + while (entries.hasMoreElements()) { + final ZipEntry ze = entries.nextElement(); + // additionally exercise the ZipFile.getEntry() method + zf.getEntry(ze.getName()); + } + } + return null; + } + } +} From 6fa35f83bf8298a60d7b2a6ded16994589da13a6 Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Tue, 8 Jul 2025 15:28:52 +0200 Subject: [PATCH 2/3] resolve conflicts --- .../share/classes/java/util/zip/ZipCoder.java | 12 - .../share/classes/java/util/zip/ZipFile.java | 225 +----------------- 2 files changed, 11 insertions(+), 226 deletions(-) diff --git a/src/java.base/share/classes/java/util/zip/ZipCoder.java b/src/java.base/share/classes/java/util/zip/ZipCoder.java index bf9cfc5f491..b833383b883 100644 --- a/src/java.base/share/classes/java/util/zip/ZipCoder.java +++ b/src/java.base/share/classes/java/util/zip/ZipCoder.java @@ -1,11 +1,5 @@ /* -<<<<<<< HEAD - * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. -======= * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -43,16 +37,10 @@ import sun.nio.cs.UTF_8; /** -<<<<<<< HEAD - * Utility class for zipfile name and comment decoding and encoding -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - * Utility class for ZIP file entry name and comment decoding and encoding -======= * Utility class for ZIP file entry name and comment decoding and encoding. *

* The {@code ZipCoder} for UTF-8 charset is thread safe, {@code ZipCoder} * for other charsets require external synchronization. ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) */ class ZipCoder { diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java index ef522337bc9..e0eee98a20b 100644 --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -1,11 +1,5 @@ /* -<<<<<<< HEAD - * Copyright (c) 1995, 2021, Oracle and/or its affiliates. All rights reserved. -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. -======= * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved. ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -101,17 +95,9 @@ */ public class ZipFile implements ZipConstants, Closeable { -<<<<<<< HEAD private final String name; // zip file name -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - private final String filePath; // ZIP file path - private final String fileName; // name of the file -======= - private final String filePath; // ZIP file path - private final String fileName; // name of the file // Used when decoding entry names and comments private final ZipCoder zipCoder; ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) private volatile boolean closeRequested; // The "resource" used by this zip file that needs to be @@ -336,25 +322,7 @@ public String getComment() { if (res.zsrc.comment == null) { return null; } -<<<<<<< HEAD - return res.zsrc.zc.toString(res.zsrc.comment); -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - // If there is a problem decoding the byte array which represents - // the ZIP file comment, return null; - try { - return res.zsrc.zc.toString(res.zsrc.comment); - } catch (IllegalArgumentException iae) { - return null; - } -======= - // If there is a problem decoding the byte array which represents - // the ZIP file comment, return null; - try { - return zipCoder.toString(res.zsrc.comment); - } catch (IllegalArgumentException iae) { - return null; - } ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + return zipCoder.toString(res.zsrc.comment); } } @@ -371,25 +339,12 @@ public ZipEntry getEntry(String name) { ZipEntry entry = null; synchronized (this) { ensureOpen(); -<<<<<<< HEAD - int pos = res.zsrc.getEntryPos(name, true); - if (pos != -1) { - entry = getZipEntry(name, pos); -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) // Look up the name and CEN header position of the entry. // The resolved name may include a trailing slash. // See Source::getEntryPos for details. - EntryPos pos = res.zsrc.getEntryPos(name, true); - if (pos != null) { - entry = getZipEntry(pos.name, pos.pos); -======= - // Look up the name and CEN header position of the entry. - // The resolved name may include a trailing slash. - // See Source::getEntryPos for details. - EntryPos pos = res.zsrc.getEntryPos(name, true, zipCoder); - if (pos != null) { - entry = getZipEntry(pos.name, pos.pos); ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + int pos = res.zsrc.getEntryPos(name, true, zipCoder); + if (pos != -1) { + entry = getZipEntry(name, pos); } } return entry; @@ -420,23 +375,7 @@ public InputStream getInputStream(ZipEntry entry) throws IOException { if (Objects.equals(lastEntryName, entry.name)) { pos = lastEntryPos; } else { -<<<<<<< HEAD - pos = zsrc.getEntryPos(entry.name, false); -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - EntryPos entryPos = zsrc.getEntryPos(entry.name, false); - if (entryPos != null) { - pos = entryPos.pos; - } else { - pos = -1; - } -======= - EntryPos entryPos = zsrc.getEntryPos(entry.name, false, zipCoder); - if (entryPos != null) { - pos = entryPos.pos; - } else { - pos = -1; - } ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + pos = zsrc.getEntryPos(entry.name, false, zipCoder); } if (pos == -1) { return null; @@ -738,7 +677,7 @@ private ZipEntry getZipEntry(String name, int pos) { int elen = CENEXT(cen, pos); int clen = CENCOM(cen, pos); - ZipCoder zc = res.zsrc.zipCoderForPos(pos); + ZipCoder zc = zipCoderFor(cen, pos, zipCoder); if (name != null) { // only need to check for mismatch of trailing slash if (nlen > 0 && @@ -775,12 +714,6 @@ private ZipEntry getZipEntry(String name, int pos) { } if (clen != 0) { int start = pos + CENHDR + nlen + elen; -<<<<<<< HEAD -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - ZipCoder zc = res.zsrc.zipCoderForPos(pos); -======= - ZipCoder zc = zipCoderFor(cen, pos, zipCoder); ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) e.comment = zc.toString(cen, start, clen); } lastEntryName = e.name; @@ -1265,12 +1198,6 @@ private static class Source { private static final int[] EMPTY_META_VERSIONS = new int[0]; private final Key key; // the key in files -<<<<<<< HEAD - private final @Stable ZipCoder zc; // zip coder used to decode/encode -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - private final @Stable ZipCoder zc; // ZIP coder used to decode/encode -======= ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) private int refs = 1; @@ -1344,33 +1271,9 @@ private int checkAndAddEntry(final int pos, final int index, final ZipCoder zipC int next = table[hsh]; table[hsh] = index; // Record the CEN offset and the name hash in our hash cell. -<<<<<<< HEAD - entries[index++] = hash; - entries[index++] = next; - entries[index ] = pos; -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - entries[index++] = hash; - entries[index++] = next; - entries[index ] = pos; - // Validate comment if it exists. - // If the bytes representing the comment cannot be converted to - // a String via zcp.toString, an Exception will be thrown - if (clen > 0) { - int start = entryPos + nlen + elen; - zcp.toString(cen, start, clen); - } -======= entries[index] = hash; entries[index + 1] = next; entries[index + 2] = pos; - // Validate comment if it exists. - // If the bytes representing the comment cannot be converted to - // a String via zcp.toString, an Exception will be thrown - if (clen > 0) { - int start = entryPos + nlen + elen; - zipCoder.toString(cen, start, clen); - } ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) } catch (Exception e) { zerror("invalid CEN header (bad entry name)"); } @@ -1515,16 +1418,6 @@ private static boolean isZip64ExtBlockSizeValid(int blockSize) { private int[] table; // Hash chain heads: indexes into entries private int tablelen; // number of hash heads -<<<<<<< HEAD -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - /** - * A class representing a key to a ZIP file. A key is based - * on the file key if available, or the path value if the - * file key is not available. The key is also based on the - * file's last modified time to allow for cases where a ZIP - * file is re-opened after it has been modified. - */ -======= /** * A class representing a key to the Source of a ZipFile. * The Key is composed of: @@ -1535,7 +1428,6 @@ private static boolean isZip64ExtBlockSizeValid(int blockSize) { * - The Charset that was provided when constructing the ZipFile instance. * The unique combination of these components identifies a Source of a ZipFile. */ ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) private static class Key { private final BasicFileAttributes attrs; private final File file; @@ -1800,16 +1692,8 @@ private End findEND() throws IOException { throw new ZipException("zip END header not found"); } -<<<<<<< HEAD - // Reads zip file central directory. - private void initCEN(int knownTotal) throws IOException { -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - // Reads ZIP file central directory. - private void initCEN(int knownTotal) throws IOException { -======= // Reads ZIP file central directory. private void initCEN(final int knownTotal, final ZipCoder zipCoder) throws IOException { ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) // Prefer locals for better performance during startup byte[] cen; if (knownTotal == -1) { @@ -1871,24 +1755,12 @@ private void initCEN(final int knownTotal, final ZipCoder zipCoder) throws IOExc // This will only happen if the zip file has an incorrect // ENDTOT field, which usually means it contains more than // 65535 entries. -<<<<<<< HEAD - initCEN(countCENHeaders(cen, limit)); -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - initCEN(countCENHeaders(cen)); -======= - initCEN(countCENHeaders(cen), zipCoder); ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + initCEN(countCENHeaders(cen, limit), zipCoder); return; } -<<<<<<< HEAD -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - int entryPos = pos + CENHDR; -======= - int entryPos = pos + CENHDR; // the ZipCoder for any non-UTF8 entries final ZipCoder entryZipCoder = zipCoderFor(cen, pos, zipCoder); ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) // Checks the entry and adds values to entries[idx ... idx+2] int nlen = checkAndAddEntry(pos, idx, entryZipCoder); idx += 3; @@ -1911,39 +1783,9 @@ private void initCEN(final int knownTotal, final ZipCoder zipCoder) throws IOExc // performance in multi-release jar files int version = getMetaVersion(entryPos + META_INF_LEN, nlen - META_INF_LEN); if (version > 0) { -<<<<<<< HEAD if (metaVersionsSet == null) metaVersionsSet = new TreeSet<>(); metaVersionsSet.add(version); -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - try { - // Compute hash code of name from "META-INF/versions/{version)/{name} - int prefixLen = META_INF_VERSIONS_LEN + DecimalDigits.stringSize(version); - int hashCode = zipCoderForPos(pos).checkedHash(cen, - entryPos + prefixLen, - nlen - prefixLen); - // Register version for this hash code - if (metaVersions == null) - metaVersions = new HashMap<>(); - metaVersions.computeIfAbsent(hashCode, _ -> new BitSet()).set(version); - } catch (Exception e) { - zerror("invalid CEN header (bad entry name or comment)"); - } -======= - try { - // Compute hash code of name from "META-INF/versions/{version)/{name} - int prefixLen = META_INF_VERSIONS_LEN + DecimalDigits.stringSize(version); - int hashCode = entryZipCoder.checkedHash(cen, - entryPos + prefixLen, - nlen - prefixLen); - // Register version for this hash code - if (metaVersions == null) - metaVersions = new HashMap<>(); - metaVersions.computeIfAbsent(hashCode, _ -> new BitSet()).set(version); - } catch (Exception e) { - zerror("invalid CEN header (bad entry name or comment)"); - } ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) } } } @@ -1985,25 +1827,11 @@ private static void zerror(String msg) throws ZipException { } /* -<<<<<<< HEAD - * Returns the {@code pos} of the zip cen entry corresponding to the - * specified entry name, or -1 if not found. -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - * Returns the resolved name and position of the ZIP cen entry corresponding - * to the specified entry name, or {@code null} if not found. -======= * Returns the resolved name and position of the ZIP cen entry corresponding * to the specified entry name, or {@code null} if not found. ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) */ -<<<<<<< HEAD - private int getEntryPos(String name, boolean addSlash) { -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - private EntryPos getEntryPos(String name, boolean addSlash) { -======= - private EntryPos getEntryPos(final String name, final boolean addSlash, - final ZipCoder zipCoder) { ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) + private int getEntryPos(final String name, final boolean addSlash, + final ZipCoder zipCoder) { if (total == 0) { return -1; } @@ -2018,9 +1846,8 @@ private EntryPos getEntryPos(final String name, final boolean addSlash, // The CEN name must match the specfied one int pos = getEntryPos(idx); -<<<<<<< HEAD try { - ZipCoder zc = zipCoderForPos(pos); + final ZipCoder zc = zipCoderFor(cen, pos, zipCoder); String entry = zc.toString(cen, pos + CENHDR, CENNAM(cen, pos)); // If addSlash is true we'll test for name+/ in addition to @@ -2037,41 +1864,11 @@ private EntryPos getEntryPos(final String name, final boolean addSlash, && entry.startsWith(name) && entry.charAt(entryLen - 1) == '/') { // Found the entry "name+/", now find the CEN entry pos - int exactPos = getEntryPos(name, false); + int exactPos = getEntryPos(name, false, zipCoder); return exactPos == -1 ? pos : exactPos; } } catch (IllegalArgumentException iae) { // Ignore -||||||| parent of 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) - ZipCoder zc = zipCoderForPos(pos); - - // Compare the lookup name with the name encoded in the CEN - switch (zc.compare(name, cen, noff, nlen, addSlash)) { - case ZipCoder.EXACT_MATCH: - // We found an exact match for "name" - return new EntryPos(name, pos); - case ZipCoder.DIRECTORY_MATCH: - // We found the directory "name/" - // Track its position, then continue the search for "name" - dirPos = pos; - break; - case ZipCoder.NO_MATCH: - // Hash collision, continue searching -======= - final ZipCoder zc = zipCoderFor(cen, pos, zipCoder); - // Compare the lookup name with the name encoded in the CEN - switch (zc.compare(name, cen, noff, nlen, addSlash)) { - case ZipCoder.EXACT_MATCH: - // We found an exact match for "name" - return new EntryPos(name, pos); - case ZipCoder.DIRECTORY_MATCH: - // We found the directory "name/" - // Track its position, then continue the search for "name" - dirPos = pos; - break; - case ZipCoder.NO_MATCH: - // Hash collision, continue searching ->>>>>>> 2c4e8d211a0 (8347712: IllegalStateException on multithreaded ZipFile access with non-UTF8 charset) } } idx = getEntryNext(idx); From e405c770b48ea79f7f995f8467c5472ecc873b44 Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Wed, 9 Jul 2025 20:02:06 +0200 Subject: [PATCH 3/3] Fix testcase compatibility with jdk17 --- .../jdk/java/util/zip/ZipFile/ZipFileSharedSourceTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/util/zip/ZipFile/ZipFileSharedSourceTest.java b/test/jdk/java/util/zip/ZipFile/ZipFileSharedSourceTest.java index d4d057ede7b..00ff0cd0e45 100644 --- a/test/jdk/java/util/zip/ZipFile/ZipFileSharedSourceTest.java +++ b/test/jdk/java/util/zip/ZipFile/ZipFileSharedSourceTest.java @@ -89,8 +89,9 @@ void testMultipleZipFileInstances(final Charset charset) throws Exception { final int numTasks = 200; final CountDownLatch startLatch = new CountDownLatch(numTasks); final List> results = new ArrayList<>(); - try (final ExecutorService executor = - Executors.newThreadPerTaskExecutor(Thread.ofPlatform().factory())) { + ExecutorService executor = Executors.newFixedThreadPool( + numTasks, Executors.defaultThreadFactory()); + try { for (int i = 0; i < numTasks; i++) { final var task = new ZipEntryIteratingTask(zipFilePath, charset, startLatch); @@ -100,6 +101,8 @@ void testMultipleZipFileInstances(final Charset charset) throws Exception { for (final Future f : results) { f.get(); } + } finally { + executor.shutdown(); } System.out.println("All " + numTasks + " tasks completed successfully"); }