From 7fa794d6cc1c659ee7d4e9632b6fbf0ee4e1fb2d Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Fri, 26 Jun 2026 20:37:06 +0200 Subject: [PATCH 1/3] refactor: extract native-call helpers into NativeCall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the FFM-downcall conventions out of Zstd into a dedicated package-private NativeCall: - interface ZstdCall (was SizeCall) - checkReturnValue(ZstdCall) (was call) — run a size_t call, decode a ZSTD_isError code into a ZstdException - isError / errorCode / errorName - requireNative segment guard Zstd keeps its one-shot byte[] API plus copyIn/copyOut. All call sites (contexts, streams, dicts, frame, bounds) repointed. No behaviour change; these are all package-private internals. Co-Authored-By: Claude Opus 4.8 --- .../java/io/github/dfa1/zstd/NativeCall.java | 78 +++++++++++++++++++ .../main/java/io/github/dfa1/zstd/Zstd.java | 65 +--------------- .../java/io/github/dfa1/zstd/ZstdBounds.java | 2 +- .../io/github/dfa1/zstd/ZstdCompressCtx.java | 20 ++--- .../io/github/dfa1/zstd/ZstdCompressDict.java | 2 +- .../github/dfa1/zstd/ZstdCompressStream.java | 10 +-- .../github/dfa1/zstd/ZstdDecompressCtx.java | 20 ++--- .../github/dfa1/zstd/ZstdDecompressDict.java | 2 +- .../dfa1/zstd/ZstdDecompressStream.java | 4 +- .../java/io/github/dfa1/zstd/ZstdFrame.java | 8 +- .../io/github/dfa1/zstd/ZstdInputStream.java | 4 +- .../io/github/dfa1/zstd/ZstdOutputStream.java | 8 +- 12 files changed, 122 insertions(+), 101 deletions(-) create mode 100644 zstd/src/main/java/io/github/dfa1/zstd/NativeCall.java diff --git a/zstd/src/main/java/io/github/dfa1/zstd/NativeCall.java b/zstd/src/main/java/io/github/dfa1/zstd/NativeCall.java new file mode 100644 index 0000000..b2ee7c5 --- /dev/null +++ b/zstd/src/main/java/io/github/dfa1/zstd/NativeCall.java @@ -0,0 +1,78 @@ +package io.github.dfa1.zstd; + +import java.lang.foreign.MemorySegment; +import java.nio.charset.StandardCharsets; + +/// Package-private helpers that adapt raw FFM downcalls to the zstd error +/// convention: run a native call, decode a zstd `size_t` error code into a +/// {@link ZstdException}, and guard native-segment arguments. Shared by the +/// binding classes so the conventions live in one place. +final class NativeCall { + + /// A native call returning a zstd `size_t` status that may encode an error. + @FunctionalInterface + interface ZstdCall { + long run() throws Throwable; + } + + /// Invokes a size-returning zstd call and converts a zstd error code into a + /// {@link ZstdException}. + static long checkReturnValue(ZstdCall c) { + long code; + try { + code = c.run(); + } catch (Throwable t) { + throw sneaky(t); + } + if (isError(code)) { + throw new ZstdException(errorName(code), ZstdErrorCode.of(errorCode(code))); + } + return code; + } + + static boolean isError(long code) { + try { + return ((int) Bindings.IS_ERROR.invokeExact(code)) != 0; + } catch (Throwable t) { + throw sneaky(t); + } + } + + private static int errorCode(long code) { + try { + return (int) Bindings.GET_ERROR_CODE.invokeExact(code); + } catch (Throwable t) { + throw sneaky(t); + } + } + + @SuppressWarnings("restricted") // reinterpret needed to read a C string of unknown length + private static String errorName(long code) { + try { + MemorySegment p = (MemorySegment) Bindings.GET_ERROR_NAME.invokeExact(code); + return p.reinterpret(Long.MAX_VALUE).getString(0, StandardCharsets.US_ASCII); + } catch (Throwable t) { + throw sneaky(t); + } + } + + /// Guards a zero-copy entry point: the segment handed to zstd must be backed + /// by native (off-heap) memory, since its address is dereferenced in C. Fails + /// fast with a clear message instead of the FFM linker's cryptic error. + static MemorySegment requireNative(MemorySegment seg, String name) { + if (!seg.isNative()) { + throw new IllegalArgumentException( + name + " must be a native (off-heap) MemorySegment; got a heap segment"); + } + return seg; + } + + @SuppressWarnings("unchecked") + private static RuntimeException sneaky(Throwable t) throws E { + throw (E) t; + } + + private NativeCall() { + // no instances + } +} diff --git a/zstd/src/main/java/io/github/dfa1/zstd/Zstd.java b/zstd/src/main/java/io/github/dfa1/zstd/Zstd.java index 98495f0..47c1629 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/Zstd.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/Zstd.java @@ -42,7 +42,7 @@ public static byte[] compress(byte[] src, int level) { MemorySegment in = copyIn(arena, src); long bound = compressBound(src.length); MemorySegment out = arena.allocate(bound); - long written = call(() -> (long) Bindings.COMPRESS.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.COMPRESS.invokeExact( out, bound, in, (long) src.length, level)); return copyOut(out, written); } @@ -77,7 +77,7 @@ public static byte[] decompress(byte[] compressed, int maxSize) { try (Arena arena = Arena.ofConfined()) { MemorySegment in = copyIn(arena, compressed); MemorySegment out = arena.allocate(Math.max(maxSize, 1)); - long written = call(() -> (long) Bindings.DECOMPRESS.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.DECOMPRESS.invokeExact( out, (long) maxSize, in, (long) compressed.length)); return copyOut(out, written); } @@ -91,7 +91,7 @@ public static byte[] decompress(byte[] compressed, int maxSize) { /// @return the decompressed length in bytes /// @throws ZstdException if the frame is invalid or does not store its size public static long decompressedSize(MemorySegment frame) { - requireNative(frame, "frame"); + NativeCall.requireNative(frame, "frame"); long size; try { size = (long) Bindings.GET_FRAME_CONTENT_SIZE.invokeExact(frame, frame.byteSize()); @@ -228,64 +228,7 @@ public static String version() { } // --- package-private helpers shared with the context classes --- - - /// A native call returning a zstd `size_t` status that may encode an error. - @FunctionalInterface - interface SizeCall { - long run() throws Throwable; - } - - /// Invokes a size-returning zstd call and converts a zstd error code into a - /// {@link ZstdException}. - static long call(SizeCall c) { - long code; - try { - code = c.run(); - } catch (Throwable t) { - throw sneaky(t); - } - if (isError(code)) { - throw new ZstdException(errorName(code), ZstdErrorCode.of(errorCode(code))); - } - return code; - } - - static boolean isError(long code) { - try { - return ((int) Bindings.IS_ERROR.invokeExact(code)) != 0; - } catch (Throwable t) { - throw sneaky(t); - } - } - - private static int errorCode(long code) { - try { - return (int) Bindings.GET_ERROR_CODE.invokeExact(code); - } catch (Throwable t) { - throw sneaky(t); - } - } - - @SuppressWarnings("restricted") // reinterpret needed to read a C string of unknown length - private static String errorName(long code) { - try { - MemorySegment p = (MemorySegment) Bindings.GET_ERROR_NAME.invokeExact(code); - return p.reinterpret(Long.MAX_VALUE).getString(0, StandardCharsets.US_ASCII); - } catch (Throwable t) { - throw sneaky(t); - } - } - - /// Guards a zero-copy entry point: the segment handed to zstd must be backed - /// by native (off-heap) memory, since its address is dereferenced in C. Fails - /// fast with a clear message instead of the FFM linker's cryptic error. - static MemorySegment requireNative(MemorySegment seg, String name) { - if (!seg.isNative()) { - throw new IllegalArgumentException( - name + " must be a native (off-heap) MemorySegment; got a heap segment"); - } - return seg; - } + // Native-call status checking and segment guards live in NativeCall. static MemorySegment copyIn(Arena arena, byte[] src) { MemorySegment seg = arena.allocate(Math.max(src.length, 1)); diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java index 8677dff..9afe1d5 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java @@ -21,7 +21,7 @@ static ZstdBounds query(MethodHandle getBounds, int parameter) { try (Arena arena = Arena.ofConfined()) { MemorySegment bounds = (MemorySegment) getBounds.invokeExact((SegmentAllocator) arena, parameter); long error = bounds.get(JAVA_LONG, 0); - if (Zstd.isError(error)) { + if (NativeCall.isError(error)) { throw new ZstdException("parameter has no queryable bounds"); } return new ZstdBounds(bounds.get(JAVA_INT, 8), bounds.get(JAVA_INT, 12)); diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressCtx.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressCtx.java index 0df19a8..24c67b6 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressCtx.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressCtx.java @@ -99,14 +99,14 @@ public byte[] compress(byte[] src) { MemorySegment in = Zstd.copyIn(arena, src); long bound = Zstd.compressBound(src.length); MemorySegment out = arena.allocate(bound); - long written = Zstd.call(() -> (long) Bindings.COMPRESS2.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.COMPRESS2.invokeExact( ptr(), out, bound, in, (long) src.length)); return Zstd.copyOut(out, written); } } private void setParam(ZstdCompressParameter parameter, int value) { - Zstd.call(() -> (long) Bindings.CCTX_SET_PARAMETER.invokeExact(ptr(), parameter.value(), value)); + NativeCall.checkReturnValue(() -> (long) Bindings.CCTX_SET_PARAMETER.invokeExact(ptr(), parameter.value(), value)); } /// Compresses `src` against `dict` at this context's level. @@ -125,7 +125,7 @@ public byte[] compress(byte[] src, ZstdDictionary dict) { MemorySegment dseg = Zstd.copyIn(arena, d); long bound = Zstd.compressBound(src.length); MemorySegment out = arena.allocate(bound); - long written = Zstd.call(() -> (long) Bindings.COMPRESS_USING_DICT.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.COMPRESS_USING_DICT.invokeExact( ptr(), out, bound, in, (long) src.length, dseg, (long) d.length, level)); return Zstd.copyOut(out, written); } @@ -143,7 +143,7 @@ public byte[] compress(byte[] src, ZstdCompressDict dict) { long bound = Zstd.compressBound(src.length); MemorySegment out = arena.allocate(bound); MemorySegment cdict = dict.ptr(); - long written = Zstd.call(() -> (long) Bindings.COMPRESS_USING_CDICT.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.COMPRESS_USING_CDICT.invokeExact( ptr(), out, bound, in, (long) src.length, cdict)); return Zstd.copyOut(out, written); } @@ -162,9 +162,9 @@ public byte[] compress(byte[] src, ZstdCompressDict dict) { /// @return the number of bytes written into `dst` /// @throws ZstdException if `dst` is too small or compression fails public long compress(MemorySegment dst, MemorySegment src) { - Zstd.requireNative(dst, "dst"); - Zstd.requireNative(src, "src"); - return Zstd.call(() -> (long) Bindings.COMPRESS2.invokeExact( + NativeCall.requireNative(dst, "dst"); + NativeCall.requireNative(src, "src"); + return NativeCall.checkReturnValue(() -> (long) Bindings.COMPRESS2.invokeExact( ptr(), dst, dst.byteSize(), src, src.byteSize())); } @@ -175,10 +175,10 @@ public long compress(MemorySegment dst, MemorySegment src) { /// @param dict the pre-digested compression dictionary /// @return the number of bytes written into `dst` public long compress(MemorySegment dst, MemorySegment src, ZstdCompressDict dict) { - Zstd.requireNative(dst, "dst"); - Zstd.requireNative(src, "src"); + NativeCall.requireNative(dst, "dst"); + NativeCall.requireNative(src, "src"); MemorySegment cdict = dict.ptr(); - return Zstd.call(() -> (long) Bindings.COMPRESS_USING_CDICT.invokeExact( + return NativeCall.checkReturnValue(() -> (long) Bindings.COMPRESS_USING_CDICT.invokeExact( ptr(), dst, dst.byteSize(), src, src.byteSize(), cdict)); } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressDict.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressDict.java index 30b5ec5..0322a0e 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressDict.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressDict.java @@ -68,7 +68,7 @@ private static MemorySegment create(ZstdDictionary dict, int level) { } private static MemorySegment create(MemorySegment dict, int level) { - Zstd.requireNative(dict, "dict"); + NativeCall.requireNative(dict, "dict"); try { MemorySegment p = (MemorySegment) Bindings.CREATE_CDICT.invokeExact(dict, dict.byteSize(), level); if (MemorySegment.NULL.equals(p)) { diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressStream.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressStream.java index 75fec0c..8af0c82 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressStream.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressStream.java @@ -53,7 +53,7 @@ public ZstdCompressStream(int level, ZstdDictionary dictionary) { // close() — one release path, no leak on a half-built stream. super(createCctx()); try { - Zstd.call(() -> (long) Bindings.CCTX_SET_PARAMETER.invokeExact( + NativeCall.checkReturnValue(() -> (long) Bindings.CCTX_SET_PARAMETER.invokeExact( ptr(), ZstdCompressParameter.COMPRESSION_LEVEL.value(), level)); if (dictionary != null) { loadDictionary(dictionary); @@ -80,7 +80,7 @@ private void loadDictionary(ZstdDictionary dictionary) { try (Arena staging = Arena.ofConfined()) { byte[] raw = dictionary.raw(); MemorySegment d = Zstd.copyIn(staging, raw); - Zstd.call(() -> (long) Bindings.CCTX_LOAD_DICTIONARY.invokeExact( + NativeCall.checkReturnValue(() -> (long) Bindings.CCTX_LOAD_DICTIONARY.invokeExact( ptr(), d, (long) raw.length)); } } @@ -97,11 +97,11 @@ private void loadDictionary(ZstdDictionary dictionary) { /// @return how much was consumed and produced, and the remaining hint /// @throws ZstdException if compression fails public ZstdStreamResult compress(MemorySegment dst, MemorySegment src, ZstdEndDirective directive) { - Zstd.requireNative(dst, "dst"); - Zstd.requireNative(src, "src"); + NativeCall.requireNative(dst, "dst"); + NativeCall.requireNative(src, "src"); in.set(src, src.byteSize(), 0); out.set(dst, dst.byteSize(), 0); - long remaining = Zstd.call(() -> (long) Bindings.COMPRESS_STREAM2.invokeExact( + long remaining = NativeCall.checkReturnValue(() -> (long) Bindings.COMPRESS_STREAM2.invokeExact( ptr(), out.segment(), in.segment(), directive.value())); return new ZstdStreamResult(in.pos(), out.pos(), remaining); } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressCtx.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressCtx.java index e2c2b80..7e10c8d 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressCtx.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressCtx.java @@ -33,7 +33,7 @@ private static MemorySegment create() { /// @return `this`, for chaining /// @throws ZstdException if the value is out of range for the parameter public ZstdDecompressCtx parameter(ZstdDecompressParameter parameter, int value) { - Zstd.call(() -> (long) Bindings.DCTX_SET_PARAMETER.invokeExact(ptr(), parameter.value(), value)); + NativeCall.checkReturnValue(() -> (long) Bindings.DCTX_SET_PARAMETER.invokeExact(ptr(), parameter.value(), value)); return this; } @@ -55,7 +55,7 @@ public byte[] decompress(byte[] compressed, int maxSize) { try (Arena arena = Arena.ofConfined()) { MemorySegment in = Zstd.copyIn(arena, compressed); MemorySegment out = arena.allocate(Math.max(maxSize, 1)); - long written = Zstd.call(() -> (long) Bindings.DECOMPRESS_DCTX.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.DECOMPRESS_DCTX.invokeExact( ptr(), out, (long) maxSize, in, (long) compressed.length)); return Zstd.copyOut(out, written); } @@ -77,7 +77,7 @@ public byte[] decompress(byte[] compressed, int maxSize, ZstdDictionary dict) { byte[] d = dict.raw(); MemorySegment dseg = Zstd.copyIn(arena, d); MemorySegment out = arena.allocate(Math.max(maxSize, 1)); - long written = Zstd.call(() -> (long) Bindings.DECOMPRESS_USING_DICT.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.DECOMPRESS_USING_DICT.invokeExact( ptr(), out, (long) maxSize, in, (long) compressed.length, dseg, (long) d.length)); return Zstd.copyOut(out, written); } @@ -94,7 +94,7 @@ public byte[] decompress(byte[] compressed, int maxSize, ZstdDecompressDict dict MemorySegment in = Zstd.copyIn(arena, compressed); MemorySegment out = arena.allocate(Math.max(maxSize, 1)); MemorySegment ddict = dict.ptr(); - long written = Zstd.call(() -> (long) Bindings.DECOMPRESS_USING_DDICT.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.DECOMPRESS_USING_DDICT.invokeExact( ptr(), out, (long) maxSize, in, (long) compressed.length, ddict)); return Zstd.copyOut(out, written); } @@ -115,9 +115,9 @@ public byte[] decompress(byte[] compressed, int maxSize, ZstdDecompressDict dict /// @return the number of bytes written into `dst` /// @throws ZstdException if `dst` is too small or the frame is invalid public long decompress(MemorySegment dst, MemorySegment src) { - Zstd.requireNative(dst, "dst"); - Zstd.requireNative(src, "src"); - return Zstd.call(() -> (long) Bindings.DECOMPRESS_DCTX.invokeExact( + NativeCall.requireNative(dst, "dst"); + NativeCall.requireNative(src, "src"); + return NativeCall.checkReturnValue(() -> (long) Bindings.DECOMPRESS_DCTX.invokeExact( ptr(), dst, dst.byteSize(), src, src.byteSize())); } @@ -128,10 +128,10 @@ public long decompress(MemorySegment dst, MemorySegment src) { /// @param dict the pre-digested decompression dictionary /// @return the number of bytes written into `dst` public long decompress(MemorySegment dst, MemorySegment src, ZstdDecompressDict dict) { - Zstd.requireNative(dst, "dst"); - Zstd.requireNative(src, "src"); + NativeCall.requireNative(dst, "dst"); + NativeCall.requireNative(src, "src"); MemorySegment ddict = dict.ptr(); - return Zstd.call(() -> (long) Bindings.DECOMPRESS_USING_DDICT.invokeExact( + return NativeCall.checkReturnValue(() -> (long) Bindings.DECOMPRESS_USING_DDICT.invokeExact( ptr(), dst, dst.byteSize(), src, src.byteSize(), ddict)); } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressDict.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressDict.java index 159166c..700375e 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressDict.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressDict.java @@ -45,7 +45,7 @@ private static MemorySegment create(ZstdDictionary dict) { } private static MemorySegment create(MemorySegment dict) { - Zstd.requireNative(dict, "dict"); + NativeCall.requireNative(dict, "dict"); try { MemorySegment p = (MemorySegment) Bindings.CREATE_DDICT.invokeExact(dict, dict.byteSize()); if (MemorySegment.NULL.equals(p)) { diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressStream.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressStream.java index c42aa2f..bf3b1b0 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressStream.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressStream.java @@ -54,7 +54,7 @@ private void loadDictionary(ZstdDictionary dictionary) { try (Arena staging = Arena.ofConfined()) { byte[] raw = dictionary.raw(); MemorySegment d = Zstd.copyIn(staging, raw); - Zstd.call(() -> (long) Bindings.DCTX_LOAD_DICTIONARY.invokeExact( + NativeCall.checkReturnValue(() -> (long) Bindings.DCTX_LOAD_DICTIONARY.invokeExact( ptr(), d, (long) raw.length)); } } @@ -72,7 +72,7 @@ private void loadDictionary(ZstdDictionary dictionary) { public ZstdStreamResult decompress(MemorySegment dst, MemorySegment src) { in.set(src, src.byteSize(), 0); out.set(dst, dst.byteSize(), 0); - long remaining = Zstd.call(() -> (long) Bindings.DECOMPRESS_STREAM.invokeExact( + long remaining = NativeCall.checkReturnValue(() -> (long) Bindings.DECOMPRESS_STREAM.invokeExact( ptr(), out.segment(), in.segment())); return new ZstdStreamResult(in.pos(), out.pos(), remaining); } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdFrame.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdFrame.java index 7099ace..b245f04 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdFrame.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdFrame.java @@ -145,7 +145,7 @@ public static byte[] writeSkippableFrame(byte[] content, int magicVariant) { MemorySegment src = Zstd.copyIn(arena, content); long cap = content.length + 8L; MemorySegment dst = arena.allocate(cap); - long written = Zstd.call(() -> (long) Bindings.WRITE_SKIPPABLE_FRAME.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.WRITE_SKIPPABLE_FRAME.invokeExact( dst, cap, src, (long) content.length, magicVariant)); return Zstd.copyOut(dst, written); } @@ -162,7 +162,7 @@ public static ZstdSkippableContent readSkippableFrame(byte[] frame) { MemorySegment src = Zstd.copyIn(arena, frame); MemorySegment magic = arena.allocate(JAVA_INT); MemorySegment dst = arena.allocate(Math.max(frame.length, 1)); - long written = Zstd.call(() -> (long) Bindings.READ_SKIPPABLE_FRAME.invokeExact( + long written = NativeCall.checkReturnValue(() -> (long) Bindings.READ_SKIPPABLE_FRAME.invokeExact( dst, (long) frame.length, magic, src, (long) frame.length)); return new ZstdSkippableContent(Zstd.copyOut(dst, written), magic.get(JAVA_INT, 0)); } @@ -171,7 +171,7 @@ public static ZstdSkippableContent readSkippableFrame(byte[] frame) { private static ZstdFrameHeader header(MemorySegment data, long size) { try (Arena arena = Arena.ofConfined()) { MemorySegment zfh = arena.allocate(48); - long remaining = Zstd.call(() -> (long) Bindings.GET_FRAME_HEADER.invokeExact(zfh, data, size)); + long remaining = NativeCall.checkReturnValue(() -> (long) Bindings.GET_FRAME_HEADER.invokeExact(zfh, data, size)); if (remaining != 0) { throw new ZstdException("incomplete frame header: need " + remaining + " more bytes"); } @@ -203,7 +203,7 @@ private static boolean isZstdFrame(MemorySegment data, long size) { } private static long compressedSize(MemorySegment data, long size) { - return Zstd.call(() -> (long) Bindings.FIND_FRAME_COMPRESSED_SIZE.invokeExact(data, size)); + return NativeCall.checkReturnValue(() -> (long) Bindings.FIND_FRAME_COMPRESSED_SIZE.invokeExact(data, size)); } private static long decompressedBound(MemorySegment data, long size) { diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdInputStream.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdInputStream.java index 1380875..fa54d4c 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdInputStream.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdInputStream.java @@ -96,7 +96,7 @@ private void loadDictionary(ZstdDictionary dictionary) { try (Arena staging = Arena.ofConfined()) { byte[] raw = dictionary.raw(); MemorySegment dictSeg = Zstd.copyIn(staging, raw); - Zstd.call(() -> (long) Bindings.DCTX_LOAD_DICTIONARY.invokeExact( + NativeCall.checkReturnValue(() -> (long) Bindings.DCTX_LOAD_DICTIONARY.invokeExact( dctx, dictSeg, (long) raw.length)); } } @@ -143,7 +143,7 @@ private boolean produce() throws IOException { inBuf.set(inSeg, r, 0); } outBufView.set(outSeg, outCap, 0); - lastHint = Zstd.call(() -> (long) Bindings.DECOMPRESS_STREAM.invokeExact( + lastHint = NativeCall.checkReturnValue(() -> (long) Bindings.DECOMPRESS_STREAM.invokeExact( dctx, outBufView.segment(), inBuf.segment())); int produced = Math.toIntExact(outBufView.pos()); if (produced > 0) { diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdOutputStream.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdOutputStream.java index 2a42e30..cbaad94 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdOutputStream.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdOutputStream.java @@ -83,7 +83,7 @@ public static ZstdOutputStream withPledgedSize(OutputStream out, int level, long } private void setPledgedSrcSize(long pledgedSrcSize) { - Zstd.call(() -> (long) Bindings.CCTX_SET_PLEDGED_SRC_SIZE.invokeExact(cctx, pledgedSrcSize)); + NativeCall.checkReturnValue(() -> (long) Bindings.CCTX_SET_PLEDGED_SRC_SIZE.invokeExact(cctx, pledgedSrcSize)); } /// Wraps `out`, compressing against `dictionary` at `level`. @@ -100,7 +100,7 @@ public ZstdOutputStream(OutputStream out, int level, ZstdDictionary dictionary) throw new ZstdException("ZSTD_createCCtx returned NULL"); } this.cctx = c; - Zstd.call(() -> (long) Bindings.CCTX_SET_PARAMETER.invokeExact( + NativeCall.checkReturnValue(() -> (long) Bindings.CCTX_SET_PARAMETER.invokeExact( cctx, ZSTD_C_COMPRESSION_LEVEL, level)); if (dictionary != null) { loadDictionary(dictionary); @@ -133,7 +133,7 @@ private void loadDictionary(ZstdDictionary dictionary) { try (Arena staging = Arena.ofConfined()) { byte[] raw = dictionary.raw(); MemorySegment dictSeg = Zstd.copyIn(staging, raw); - Zstd.call(() -> (long) Bindings.CCTX_LOAD_DICTIONARY.invokeExact( + NativeCall.checkReturnValue(() -> (long) Bindings.CCTX_LOAD_DICTIONARY.invokeExact( cctx, dictSeg, (long) raw.length)); } } @@ -197,7 +197,7 @@ public void close() throws IOException { /// Returns the zstd "remaining" hint (0 means the directive is fully flushed). private long drainOutput(int directive) throws IOException { outBuf.set(outSeg, outCap, 0); - long remainingHint = Zstd.call(() -> (long) Bindings.COMPRESS_STREAM2.invokeExact( + long remainingHint = NativeCall.checkReturnValue(() -> (long) Bindings.COMPRESS_STREAM2.invokeExact( cctx, outBuf.segment(), in.segment(), directive)); int produced = Math.toIntExact(outBuf.pos()); if (produced > 0) { From 6fc1b37977ce6dbecab3dec3919d3763cdbdb4f7 Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Fri, 26 Jun 2026 20:39:59 +0200 Subject: [PATCH 2/3] refactor: collapse 13 copies of the sneaky-rethrow helper into NativeCall Every binding class carried its own private rethrow/sneaky to launder the checked Throwable from MethodHandle.invokeExact. Replace them with one shared NativeCall.rethrow and repoint all catch blocks. Co-Authored-By: Claude Opus 4.8 --- .../java/io/github/dfa1/zstd/NativeCall.java | 13 +++++---- .../main/java/io/github/dfa1/zstd/Zstd.java | 27 ++++++++----------- .../java/io/github/dfa1/zstd/ZstdBounds.java | 7 +---- .../io/github/dfa1/zstd/ZstdCompressCtx.java | 9 ++----- .../io/github/dfa1/zstd/ZstdCompressDict.java | 13 +++------ .../github/dfa1/zstd/ZstdCompressStream.java | 13 +++------ .../github/dfa1/zstd/ZstdDecompressCtx.java | 9 ++----- .../github/dfa1/zstd/ZstdDecompressDict.java | 13 +++------ .../dfa1/zstd/ZstdDecompressStream.java | 11 +++----- .../io/github/dfa1/zstd/ZstdDictionary.java | 19 +++++-------- .../io/github/dfa1/zstd/ZstdErrorCode.java | 7 +---- .../java/io/github/dfa1/zstd/ZstdFrame.java | 13 +++------ .../io/github/dfa1/zstd/ZstdInputStream.java | 7 +---- .../io/github/dfa1/zstd/ZstdOutputStream.java | 7 +---- 14 files changed, 53 insertions(+), 115 deletions(-) diff --git a/zstd/src/main/java/io/github/dfa1/zstd/NativeCall.java b/zstd/src/main/java/io/github/dfa1/zstd/NativeCall.java index b2ee7c5..71181b8 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/NativeCall.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/NativeCall.java @@ -22,7 +22,7 @@ static long checkReturnValue(ZstdCall c) { try { code = c.run(); } catch (Throwable t) { - throw sneaky(t); + throw rethrow(t); } if (isError(code)) { throw new ZstdException(errorName(code), ZstdErrorCode.of(errorCode(code))); @@ -34,7 +34,7 @@ static boolean isError(long code) { try { return ((int) Bindings.IS_ERROR.invokeExact(code)) != 0; } catch (Throwable t) { - throw sneaky(t); + throw rethrow(t); } } @@ -42,7 +42,7 @@ private static int errorCode(long code) { try { return (int) Bindings.GET_ERROR_CODE.invokeExact(code); } catch (Throwable t) { - throw sneaky(t); + throw rethrow(t); } } @@ -52,7 +52,7 @@ private static String errorName(long code) { MemorySegment p = (MemorySegment) Bindings.GET_ERROR_NAME.invokeExact(code); return p.reinterpret(Long.MAX_VALUE).getString(0, StandardCharsets.US_ASCII); } catch (Throwable t) { - throw sneaky(t); + throw rethrow(t); } } @@ -67,8 +67,11 @@ static MemorySegment requireNative(MemorySegment seg, String name) { return seg; } + /// Rethrows any `Throwable` as if unchecked, laundering the checked + /// `Throwable` that {@link java.lang.invoke.MethodHandle#invokeExact} declares. + /// The shared sink for every binding class's native-call catch blocks. @SuppressWarnings("unchecked") - private static RuntimeException sneaky(Throwable t) throws E { + static RuntimeException rethrow(Throwable t) throws E { throw (E) t; } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/Zstd.java b/zstd/src/main/java/io/github/dfa1/zstd/Zstd.java index 47c1629..5020e6f 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/Zstd.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/Zstd.java @@ -96,7 +96,7 @@ public static long decompressedSize(MemorySegment frame) { try { size = (long) Bindings.GET_FRAME_CONTENT_SIZE.invokeExact(frame, frame.byteSize()); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } if (size == CONTENTSIZE_UNKNOWN) { throw new ZstdException("decompressed size not stored in frame"); @@ -116,7 +116,7 @@ public static long compressBound(long srcSize) { try { return (long) Bindings.COMPRESS_BOUND.invokeExact(srcSize); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -126,7 +126,7 @@ private static long frameContentSize(byte[] compressed) { MemorySegment in = copyIn(arena, compressed); return (long) Bindings.GET_FRAME_CONTENT_SIZE.invokeExact(in, (long) compressed.length); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -137,7 +137,7 @@ public static int maxCompressionLevel() { try { return (int) Bindings.MAX_C_LEVEL.invokeExact(); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -148,7 +148,7 @@ public static int minCompressionLevel() { try { return (int) Bindings.MIN_C_LEVEL.invokeExact(); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -159,7 +159,7 @@ public static int defaultCompressionLevel() { try { return (int) Bindings.DEFAULT_C_LEVEL.invokeExact(); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -172,7 +172,7 @@ public static long estimateCompressContextSize(int level) { try { return (long) Bindings.ESTIMATE_CCTX_SIZE.invokeExact(level); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -183,7 +183,7 @@ public static long estimateDecompressContextSize() { try { return (long) Bindings.ESTIMATE_DCTX_SIZE.invokeExact(); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -197,7 +197,7 @@ public static long estimateCompressDictSize(long dictSize, int level) { try { return (long) Bindings.ESTIMATE_CDICT_SIZE.invokeExact(dictSize, level); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -210,7 +210,7 @@ public static long estimateDecompressDictSize(long dictSize) { try { return (long) Bindings.ESTIMATE_DDICT_SIZE.invokeExact(dictSize, 0); // ZSTD_dlm_byCopy } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -223,7 +223,7 @@ public static String version() { MemorySegment p = (MemorySegment) Bindings.VERSION_STRING.invokeExact(); return p.reinterpret(Long.MAX_VALUE).getString(0, StandardCharsets.US_ASCII); } catch (Throwable t) { - throw sneaky(t); + throw NativeCall.rethrow(t); } } @@ -242,11 +242,6 @@ static byte[] copyOut(MemorySegment seg, long len) { return out; } - @SuppressWarnings("unchecked") - private static RuntimeException sneaky(Throwable t) throws E { - throw (E) t; - } - private Zstd() { // no instances } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java index 9afe1d5..8752713 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java @@ -26,12 +26,7 @@ static ZstdBounds query(MethodHandle getBounds, int parameter) { } return new ZstdBounds(bounds.get(JAVA_INT, 8), bounds.get(JAVA_INT, 12)); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressCtx.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressCtx.java index 24c67b6..ded4987 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressCtx.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressCtx.java @@ -33,7 +33,7 @@ private static MemorySegment create() { } return p; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -216,7 +216,7 @@ public long sizeOf() { try { return (long) Bindings.SIZEOF_CCTX.invokeExact(ptr()); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -224,9 +224,4 @@ public long sizeOf() { protected void tryClose(MemorySegment ptr) throws Throwable { var _ = (long) Bindings.FREE_CCTX.invokeExact(ptr); } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressDict.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressDict.java index 0322a0e..c900a41 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressDict.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressDict.java @@ -63,7 +63,7 @@ private static MemorySegment create(ZstdDictionary dict, int level) { } return p; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -76,7 +76,7 @@ private static MemorySegment create(MemorySegment dict, int level) { } return p; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -94,7 +94,7 @@ public int id() { try { return (int) Bindings.GET_DICT_ID_FROM_CDICT.invokeExact(ptr()); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -105,7 +105,7 @@ public long sizeOf() { try { return (long) Bindings.SIZEOF_CDICT.invokeExact(ptr()); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -113,9 +113,4 @@ public long sizeOf() { protected void tryClose(MemorySegment ptr) throws Throwable { var _ = (long) Bindings.FREE_CDICT.invokeExact(ptr); } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressStream.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressStream.java index 8af0c82..cbb19da 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressStream.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressStream.java @@ -60,7 +60,7 @@ public ZstdCompressStream(int level, ZstdDictionary dictionary) { } } catch (Throwable t) { close(); - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -72,7 +72,7 @@ private static MemorySegment createCctx() { } return cctx; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -122,7 +122,7 @@ public ZstdFrameProgression progress() { p.get(JAVA_INT, 32), // currentJobID p.get(JAVA_INT, 36)); // nbActiveWorkers } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -133,7 +133,7 @@ public long sizeOf() { try { return (long) Bindings.SIZEOF_CCTX.invokeExact(ptr()); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -145,9 +145,4 @@ protected void tryClose(MemorySegment ptr) throws Throwable { arena.close(); } } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressCtx.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressCtx.java index 7e10c8d..d709916 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressCtx.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressCtx.java @@ -22,7 +22,7 @@ private static MemorySegment create() { } return p; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -176,7 +176,7 @@ public long sizeOf() { try { return (long) Bindings.SIZEOF_DCTX.invokeExact(ptr()); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -184,9 +184,4 @@ public long sizeOf() { protected void tryClose(MemorySegment ptr) throws Throwable { var _ = (long) Bindings.FREE_DCTX.invokeExact(ptr); } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressDict.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressDict.java index 700375e..aefe4ca 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressDict.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressDict.java @@ -40,7 +40,7 @@ private static MemorySegment create(ZstdDictionary dict) { } return p; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -53,7 +53,7 @@ private static MemorySegment create(MemorySegment dict) { } return p; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -64,7 +64,7 @@ public int id() { try { return (int) Bindings.GET_DICT_ID_FROM_DDICT.invokeExact(ptr()); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -75,7 +75,7 @@ public long sizeOf() { try { return (long) Bindings.SIZEOF_DDICT.invokeExact(ptr()); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -83,9 +83,4 @@ public long sizeOf() { protected void tryClose(MemorySegment ptr) throws Throwable { var _ = (long) Bindings.FREE_DDICT.invokeExact(ptr); } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressStream.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressStream.java index bf3b1b0..cd44173 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressStream.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressStream.java @@ -34,7 +34,7 @@ public ZstdDecompressStream(ZstdDictionary dictionary) { } } catch (Throwable t) { close(); - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -46,7 +46,7 @@ private static MemorySegment createDctx() { } return dctx; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -84,7 +84,7 @@ public long sizeOf() { try { return (long) Bindings.SIZEOF_DCTX.invokeExact(ptr()); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -96,9 +96,4 @@ protected void tryClose(MemorySegment ptr) throws Throwable { arena.close(); } } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDictionary.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDictionary.java index 2c6faa8..876b5e8 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdDictionary.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdDictionary.java @@ -116,7 +116,7 @@ public static ZstdDictionary train(List samples, int maxDictBytes) { produced = (long) Bindings.ZDICT_TRAIN.invokeExact( dictBuf, (long) maxDictBytes, flat, sizes, samples.size()); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } if (zdictIsError(produced)) { throw new ZstdException("dictionary training failed: " + zdictErrorName(produced)); @@ -204,7 +204,7 @@ private static ZstdDictionary optimize(List samples, int maxDictBytes, produced = (long) handle.invokeExact( dictBuf, (long) maxDictBytes, flat, sizes, samples.size(), params); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } if (zdictIsError(produced)) { throw new ZstdException("dictionary training failed: " + zdictErrorName(produced)); @@ -255,7 +255,7 @@ public static ZstdDictionary finalizeFrom(byte[] content, List samples, dictBuf, (long) maxDictBytes, contentSeg, (long) content.length, flat, sizes, samples.size(), params); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } if (zdictIsError(produced)) { throw new ZstdException("dictionary finalisation failed: " + zdictErrorName(produced)); @@ -275,7 +275,7 @@ public int id() { MemorySegment seg = Zstd.copyIn(arena, bytes); return (int) Bindings.ZDICT_GET_DICT_ID.invokeExact(seg, (long) bytes.length); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -293,7 +293,7 @@ public int headerSize() { } return Math.toIntExact(size); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -320,7 +320,7 @@ private static boolean zdictIsError(long code) { try { return ((int) Bindings.ZDICT_IS_ERROR.invokeExact(code)) != 0; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -330,12 +330,7 @@ private static String zdictErrorName(long code) { MemorySegment p = (MemorySegment) Bindings.ZDICT_GET_ERROR_NAME.invokeExact(code); return p.reinterpret(Long.MAX_VALUE).getString(0, StandardCharsets.US_ASCII); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdErrorCode.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdErrorCode.java index b4d1f0f..37c8e10 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdErrorCode.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdErrorCode.java @@ -109,15 +109,10 @@ public String description() { MemorySegment p = (MemorySegment) Bindings.GET_ERROR_STRING.invokeExact(value); return p.reinterpret(Long.MAX_VALUE).getString(0, StandardCharsets.US_ASCII); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } - /// Maps a native `ZSTD_ErrorCode` integer to its category. /// /// @param value the native error code diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdFrame.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdFrame.java index b245f04..86c9070 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdFrame.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdFrame.java @@ -190,7 +190,7 @@ private static boolean isSkippableFrame(MemorySegment data, long size) { try { return ((int) Bindings.IS_SKIPPABLE_FRAME.invokeExact(data, size)) != 0; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -198,7 +198,7 @@ private static boolean isZstdFrame(MemorySegment data, long size) { try { return ((int) Bindings.IS_FRAME.invokeExact(data, size)) != 0; } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -211,7 +211,7 @@ private static long decompressedBound(MemorySegment data, long size) { try { bound = (long) Bindings.DECOMPRESS_BOUND.invokeExact(data, size); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } if (bound == CONTENTSIZE_ERROR) { throw new ZstdException("not valid zstd data"); @@ -223,15 +223,10 @@ private static int dictId(MemorySegment data, long size) { try { return (int) Bindings.GET_DICT_ID_FROM_FRAME.invokeExact(data, size); } catch (Throwable t) { - throw rethrow(t); + throw NativeCall.rethrow(t); } } - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } - private ZstdFrame() { // no instances } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdInputStream.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdInputStream.java index fa54d4c..99a3eff 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdInputStream.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdInputStream.java @@ -80,7 +80,7 @@ public ZstdInputStream(InputStream in, ZstdDictionary dictionary) { freeDctx(d); } arena.close(); - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -183,9 +183,4 @@ private void ensureOpen() throws IOException { throw new IOException("stream closed"); } } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdOutputStream.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdOutputStream.java index cbaad94..2f1b89c 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdOutputStream.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdOutputStream.java @@ -117,7 +117,7 @@ public ZstdOutputStream(OutputStream out, int level, ZstdDictionary dictionary) freeCctx(c); } arena.close(); - throw rethrow(t); + throw NativeCall.rethrow(t); } } @@ -212,9 +212,4 @@ private void ensureOpen() throws IOException { throw new IOException("stream closed"); } } - - @SuppressWarnings("unchecked") - private static RuntimeException rethrow(Throwable t) throws E { - throw (E) t; - } } From f48806ede346a7afedca39c6af0a860814a20763 Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Fri, 26 Jun 2026 20:46:55 +0200 Subject: [PATCH 3/3] refactor: read ZSTD_bounds via the named layout, not magic offsets Name BOUNDS_LAYOUT's fields (error/lowerBound/upperBound) and derive the read offsets from it with byteOffset(groupElement(...)), so the struct reads track the definition instead of hand-counted 0/8/12. Document how the struct-by-value return allocates through the arena SegmentAllocator. Co-Authored-By: Claude Opus 4.8 --- .../main/java/io/github/dfa1/zstd/Bindings.java | 7 +++++-- .../java/io/github/dfa1/zstd/ZstdBounds.java | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/zstd/src/main/java/io/github/dfa1/zstd/Bindings.java b/zstd/src/main/java/io/github/dfa1/zstd/Bindings.java index 9d1b45b..91baf28 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/Bindings.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/Bindings.java @@ -158,8 +158,11 @@ final class Bindings { NativeLibrary.lookup("ZSTD_getDictID_fromDDict", FunctionDescriptor.of(JAVA_INT, ADDRESS)); // ZSTD_bounds { size_t error; int lowerBound; int upperBound; } — returned by value - private static final MemoryLayout BOUNDS_LAYOUT = - MemoryLayout.structLayout(JAVA_LONG, JAVA_INT, JAVA_INT); + static final MemoryLayout BOUNDS_LAYOUT = + MemoryLayout.structLayout( + JAVA_LONG.withName("error"), + JAVA_INT.withName("lowerBound"), + JAVA_INT.withName("upperBound")); // ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter) / ZSTD_dParam_getBounds(ZSTD_dParameter) static final MethodHandle CPARAM_GET_BOUNDS = NativeLibrary.lookup("ZSTD_cParam_getBounds", FunctionDescriptor.of(BOUNDS_LAYOUT, JAVA_INT)); diff --git a/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java b/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java index 8752713..da97266 100644 --- a/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java +++ b/zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java @@ -1,6 +1,7 @@ package io.github.dfa1.zstd; import java.lang.foreign.Arena; +import java.lang.foreign.MemoryLayout.PathElement; import java.lang.foreign.MemorySegment; import java.lang.foreign.SegmentAllocator; import java.lang.invoke.MethodHandle; @@ -15,16 +16,28 @@ /// @param upperBound the largest accepted value, inclusive public record ZstdBounds(int lowerBound, int upperBound) { + // Offsets into the returned ZSTD_bounds struct, derived from the named layout + // rather than hand-counted, so they track the struct definition. + private static final long ERROR_OFFSET = Bindings.BOUNDS_LAYOUT.byteOffset(PathElement.groupElement("error")); + private static final long LOWER_OFFSET = Bindings.BOUNDS_LAYOUT.byteOffset(PathElement.groupElement("lowerBound")); + private static final long UPPER_OFFSET = Bindings.BOUNDS_LAYOUT.byteOffset(PathElement.groupElement("upperBound")); + /// Calls a `*_getBounds` function (which returns a `ZSTD_bounds` struct by /// value: `{ size_t error; int lowerBound; int upperBound; }`). static ZstdBounds query(MethodHandle getBounds, int parameter) { try (Arena arena = Arena.ofConfined()) { + // getBounds returns a ZSTD_bounds struct by value. For a struct return, + // the FFM linker prepends a SegmentAllocator parameter to the handle: + // it allocates BOUNDS_LAYOUT.byteSize() bytes from that allocator, the + // native call writes the struct there, and the handle returns a segment + // viewing it. Passing the arena makes the struct arena-owned (freed on + // close); the cast satisfies invokeExact's exact-type requirement. MemorySegment bounds = (MemorySegment) getBounds.invokeExact((SegmentAllocator) arena, parameter); - long error = bounds.get(JAVA_LONG, 0); + long error = bounds.get(JAVA_LONG, ERROR_OFFSET); if (NativeCall.isError(error)) { throw new ZstdException("parameter has no queryable bounds"); } - return new ZstdBounds(bounds.get(JAVA_INT, 8), bounds.get(JAVA_INT, 12)); + return new ZstdBounds(bounds.get(JAVA_INT, LOWER_OFFSET), bounds.get(JAVA_INT, UPPER_OFFSET)); } catch (Throwable t) { throw NativeCall.rethrow(t); }