From ef165db0380a28e8b5313c05ff3bf7430b011a57 Mon Sep 17 00:00:00 2001 From: Oleksii PELYKH Date: Sat, 22 Jun 2024 23:01:20 +0200 Subject: [PATCH] (feat) pcre2_config --- PCRE2_API.md | 2 +- api/src/main/java/org/pcre4j/api/IPcre2.java | 42 +++ ffm/src/main/java/org/pcre4j/ffm/Pcre2.java | 81 +++++ jna/src/main/java/org/pcre4j/jna/Pcre2.java | 78 +++++ lib/src/main/java/org/pcre4j/Pcre4jUtils.java | 298 ++++++++++++++++++ .../main/java/org/pcre4j/test/Pcre2Tests.java | 24 +- 6 files changed, 520 insertions(+), 5 deletions(-) diff --git a/PCRE2_API.md b/PCRE2_API.md index 8fd051e..5cb8d31 100644 --- a/PCRE2_API.md +++ b/PCRE2_API.md @@ -12,7 +12,7 @@ Here's the list of the PCRE2 API functions exposed via `org.pcre4j.api.IPcre2` a | ✅ | [pcre2_compile_context_copy](https://www.pcre.org/current/doc/html/pcre2_compile_context_copy.html) | Copy a compile context | | ✅ | [pcre2_compile_context_create](https://www.pcre.org/current/doc/html/pcre2_compile_context_create.html) | Create a compile context | | ✅ | [pcre2_compile_context_free](https://www.pcre.org/current/doc/html/pcre2_compile_context_free.html) | Free a compile context | -| | [pcre2_config](https://www.pcre.org/current/doc/html/pcre2_config.html) | Show build-time configuration options | +| ✅ | [pcre2_config](https://www.pcre.org/current/doc/html/pcre2_config.html) | Show build-time configuration options | | | [pcre2_convert_context_copy](https://www.pcre.org/current/doc/html/pcre2_convert_context_copy.html) | Copy a convert context | | | [pcre2_convert_context_create](https://www.pcre.org/current/doc/html/pcre2_convert_context_create.html) | Create a convert context | | | [pcre2_convert_context_free](https://www.pcre.org/current/doc/html/pcre2_convert_context_free.html) | Free a convert context | diff --git a/api/src/main/java/org/pcre4j/api/IPcre2.java b/api/src/main/java/org/pcre4j/api/IPcre2.java index f9bcd77..15b17fc 100644 --- a/api/src/main/java/org/pcre4j/api/IPcre2.java +++ b/api/src/main/java/org/pcre4j/api/IPcre2.java @@ -627,6 +627,48 @@ public interface IPcre2 { public static final int CONFIG_COMPILED_WIDTHS = 14; public static final int CONFIG_TABLES_LENGTH = 15; + /** + * Get the amount of memory needed to store the information referred to by {@param what} about the optional features + * of the PCRE2 library. + *

+ * Suitable for any information except: + * {@link #CONFIG_JITTARGET} Target architecture for the JIT compiler + * {@link #CONFIG_UNICODE_VERSION} Unicode version + * {@link #CONFIG_VERSION} PCRE2 version + * + * @param what the information to query the memory requirements for + * @return the amount of memory needed to store the information referred to by {@param what}. + */ + public int config(int what); + + /** + * Get the information referred to by {@param what} about the optional features of the PCRE2 library. + *

+ * Suitable for any information except: + * {@link #CONFIG_JITTARGET} Target architecture for the JIT compiler + * {@link #CONFIG_UNICODE_VERSION} Unicode version + * {@link #CONFIG_VERSION} PCRE2 version + * + * @param what the information to query + * @param where the array to store the information + * @return Non-negative value on success, otherwise a negative error code. + */ + public int config(int what, int[] where); + + /** + * Get the information referred to by {@param what} about the optional features of the PCRE2 library. + *

+ * Suitable only for the following information: + * {@link #CONFIG_JITTARGET} Target architecture for the JIT compiler + * {@link #CONFIG_UNICODE_VERSION} Unicode version + * {@link #CONFIG_VERSION} PCRE2 version + * + * @param what the information to query + * @param where a buffer to store the information + * @return Non-negative value on success, otherwise a negative error code. + */ + public int config(int what, ByteBuffer where); + /** * Create a new general context. * diff --git a/ffm/src/main/java/org/pcre4j/ffm/Pcre2.java b/ffm/src/main/java/org/pcre4j/ffm/Pcre2.java index aeabb92..f06f1c6 100644 --- a/ffm/src/main/java/org/pcre4j/ffm/Pcre2.java +++ b/ffm/src/main/java/org/pcre4j/ffm/Pcre2.java @@ -29,6 +29,8 @@ public class Pcre2 implements IPcre2 { private static final Linker LINKER = Linker.nativeLinker(); private static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup(); + private final MethodHandle pcre2_config; + private final MethodHandle pcre2_general_context_create; private final MethodHandle pcre2_general_context_copy; private final MethodHandle pcre2_general_context_free; @@ -76,6 +78,14 @@ public Pcre2(String library, String suffix) { System.loadLibrary(library); } + pcre2_config = LINKER.downcallHandle( + SYMBOL_LOOKUP.find("pcre2_config" + suffix).orElseThrow(), + FunctionDescriptor.of(ValueLayout.JAVA_INT, // int + ValueLayout.JAVA_INT, // int + ValueLayout.ADDRESS // void* + ) + ); + pcre2_general_context_create = LINKER.downcallHandle( SYMBOL_LOOKUP.find("pcre2_general_context_create" + suffix).orElseThrow(), FunctionDescriptor.of(ValueLayout.ADDRESS, // pcre2_general_context* @@ -229,6 +239,66 @@ public Pcre2(String library, String suffix) { ); } + @Override + public int config(int what) { + try (var arena = Arena.ofConfined()) { + final var pWhat = MemorySegment.ofAddress(0); + + return (int) pcre2_config.invokeExact( + what, + pWhat + ); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public int config(int what, int[] where) { + if (where == null) { + throw new IllegalArgumentException("where must not be null"); + } + if (where.length != 1) { + throw new IllegalArgumentException("where must be an array of length 1"); + } + + try (var arena = Arena.ofConfined()) { + final var pWhere = arena.allocateArray(ValueLayout.JAVA_INT, 1); + + final var result = (int) pcre2_config.invokeExact( + what, + pWhere + ); + + where[0] = pWhere.get(ValueLayout.JAVA_INT, 0); + + return result; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public int config(int what, ByteBuffer where) { + if (where == null) { + throw new IllegalArgumentException("where must not be null"); + } + if (!where.isDirect()) { + throw new IllegalArgumentException("where must be direct"); + } + + try (var arena = Arena.ofConfined()) { + final var pWhere = MemorySegment.ofBuffer(where); + + return (int) pcre2_config.invokeExact( + what, + pWhere + ); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + @Override public long generalContextCreate(long privateMalloc, long privateFree, long memoryData) { try (var arena = Arena.ofConfined()) { @@ -322,6 +392,9 @@ public void compileContextFree(long ccontext) { @Override public long compile(String pattern, int options, int[] errorcode, long[] erroroffset, long ccontext) { + if (pattern == null) { + throw new IllegalArgumentException("pattern must not be null"); + } if (errorcode == null || errorcode.length < 1) { throw new IllegalArgumentException("errorcode must be an array of length 1"); } @@ -576,6 +649,10 @@ public void matchContextFree(long mcontext) { @Override public int match(long code, String subject, int startoffset, int options, long matchData, long mcontext) { + if (subject == null) { + throw new IllegalArgumentException("subject must not be null"); + } + try (var arena = Arena.ofConfined()) { final var pCode = MemorySegment.ofAddress(code); final var pszSubject = arena.allocateUtf8String(subject); @@ -613,6 +690,10 @@ public int getOvectorCount(long matchData) { @Override public void getOvector(long matchData, long[] ovector) { + if (ovector == null) { + throw new IllegalArgumentException("ovector must not be null"); + } + try (var arena = Arena.ofConfined()) { final var pMatchData = MemorySegment.ofAddress(matchData); diff --git a/jna/src/main/java/org/pcre4j/jna/Pcre2.java b/jna/src/main/java/org/pcre4j/jna/Pcre2.java index 776c12d..9243f5a 100644 --- a/jna/src/main/java/org/pcre4j/jna/Pcre2.java +++ b/jna/src/main/java/org/pcre4j/jna/Pcre2.java @@ -60,6 +60,39 @@ public Pcre2(String libraryName, String suffix) { ); } + @Override + public int config(int what) { + return library.pcre2_config(what, Pointer.NULL); + } + + @Override + public int config(int what, int[] where) { + if (where == null) { + throw new IllegalArgumentException("where must not be null"); + } + if (where.length != 1) { + throw new IllegalArgumentException("where must be an array of length 1"); + } + + IntByReference whereRef = new IntByReference(); + int result = library.pcre2_config(what, whereRef.getPointer()); + where[0] = whereRef.getValue(); + return result; + } + + @Override + public int config(int what, ByteBuffer where) { + if (where == null) { + throw new IllegalArgumentException("where must not be null"); + } + if (!where.isDirect()) { + throw new IllegalArgumentException("where must be a direct buffer"); + } + + Pointer pWhere = Native.getDirectBufferPointer(where); + return library.pcre2_config(what, pWhere); + } + @Override public long generalContextCreate(long privateMalloc, long privateFree, long memoryData) { Pointer gContext = library.pcre2_general_context_create( @@ -100,6 +133,16 @@ public void compileContextFree(long ccontext) { @Override public long compile(String pattern, int options, int[] errorcode, long[] erroroffset, long ccontext) { + if (pattern == null) { + throw new IllegalArgumentException("pattern must not be null"); + } + if (errorcode == null || errorcode.length < 1) { + throw new IllegalArgumentException("errorcode must be an array of length 1"); + } + if (erroroffset == null || erroroffset.length < 1) { + throw new IllegalArgumentException("erroroffset must be an array of length 1"); + } + IntByReference errorCodeRef = new IntByReference(); LongByReference errorOffsetRef = new LongByReference(); @@ -127,6 +170,13 @@ public void codeFree(long code) { @Override public int getErrorMessage(int errorcode, ByteBuffer buffer) { + if (buffer == null) { + throw new IllegalArgumentException("buffer must not be null"); + } + if (!buffer.isDirect()) { + throw new IllegalArgumentException("buffer must be direct"); + } + Pointer pszBuffer = Native.getDirectBufferPointer(buffer); return library.pcre2_get_error_message(errorcode, pszBuffer, buffer.capacity()); } @@ -138,6 +188,13 @@ public int patternInfo(long code, int what) { @Override public int patternInfo(long code, int what, int[] where) { + if (where == null) { + throw new IllegalArgumentException("where must not be null"); + } + if (where.length != 1) { + throw new IllegalArgumentException("where must be an array of length 1"); + } + IntByReference whereRef = new IntByReference(); int result = library.pcre2_pattern_info(new Pointer(code), what, whereRef.getPointer()); where[0] = whereRef.getValue(); @@ -146,6 +203,13 @@ public int patternInfo(long code, int what, int[] where) { @Override public int patternInfo(long code, int what, long[] where) { + if (where == null) { + throw new IllegalArgumentException("where must not be null"); + } + if (where.length != 1) { + throw new IllegalArgumentException("where must be an array of length 1"); + } + LongByReference whereRef = new LongByReference(); int result = library.pcre2_pattern_info(new Pointer(code), what, whereRef.getPointer()); where[0] = whereRef.getValue(); @@ -154,6 +218,10 @@ public int patternInfo(long code, int what, long[] where) { @Override public int patternInfo(long code, int what, ByteBuffer where) { + if (where == null) { + throw new IllegalArgumentException("where must not be null"); + } + PointerByReference whereRef = new PointerByReference(); int result = library.pcre2_pattern_info(new Pointer(code), what, whereRef.getPointer()); where.put(whereRef.getValue().getByteArray(0, where.capacity())); @@ -196,6 +264,10 @@ public void matchContextFree(long mcontext) { @Override public int match(long code, String subject, int startoffset, int options, long matchData, long mcontext) { + if (subject == null) { + throw new IllegalArgumentException("subject must not be null"); + } + final var pszSubject = subject.getBytes(StandardCharsets.UTF_8); return library.pcre2_match( @@ -216,11 +288,17 @@ public int getOvectorCount(long matchData) { @Override public void getOvector(long matchData, long[] ovector) { + if (ovector == null) { + throw new IllegalArgumentException("ovector must not be null"); + } + Pointer pOvector = library.pcre2_get_ovector_pointer(new Pointer(matchData)); pOvector.read(0, ovector, 0, ovector.length); } private interface Library extends com.sun.jna.Library { + int pcre2_config(int what, Pointer where); + Pointer pcre2_general_context_create(Pointer malloc, Pointer free, Pointer memoryData); Pointer pcre2_general_context_copy(Pointer gcontext); diff --git a/lib/src/main/java/org/pcre4j/Pcre4jUtils.java b/lib/src/main/java/org/pcre4j/Pcre4jUtils.java index fa90a63..5c4011d 100644 --- a/lib/src/main/java/org/pcre4j/Pcre4jUtils.java +++ b/lib/src/main/java/org/pcre4j/Pcre4jUtils.java @@ -29,6 +29,304 @@ public final class Pcre4jUtils { private Pcre4jUtils() { } + /** + * Get the PCRE2 version. + * + * @param api the PCRE2 API + * @return the PCRE2 version + */ + public static String getVersion(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var versionSize = api.config(IPcre2.CONFIG_VERSION); + if (versionSize < 0) { + throw new IllegalStateException(getErrorMessage(api, versionSize)); + } + + final var versionBuffer = ByteBuffer.allocateDirect(versionSize); + final var versionCopyResult = api.config(IPcre2.CONFIG_VERSION, versionBuffer); + if (versionCopyResult < 0) { + throw new IllegalStateException(getErrorMessage(api, versionCopyResult)); + } + + return StandardCharsets.UTF_8.decode(versionBuffer.limit(versionSize - 1)).toString(); + } + + /** + * Get the Unicode version. + * + * @param api the PCRE2 API + * @return the Unicode version + */ + public static String getUnicodeVersion(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var versionSize = api.config(IPcre2.CONFIG_UNICODE_VERSION); + if (versionSize < 0) { + throw new IllegalStateException(getErrorMessage(api, versionSize)); + } + + final var versionBuffer = ByteBuffer.allocateDirect(versionSize); + final var versionCopyResult = api.config(IPcre2.CONFIG_UNICODE_VERSION, versionBuffer); + if (versionCopyResult < 0) { + throw new IllegalStateException(getErrorMessage(api, versionCopyResult)); + } + + return StandardCharsets.UTF_8.decode(versionBuffer.limit(versionSize - 1)).toString(); + } + + /** + * Check if Unicode is supported. + * + * @param api the PCRE2 API + * @return {@code true} if Unicode is supported, {@code false} otherwise + */ + public static boolean isUnicodeSupported(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var unicodeSupported = new int[1]; + final var result = api.config(IPcre2.CONFIG_UNICODE, unicodeSupported); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return unicodeSupported[0] == 1; + } + + /** + * Get the default parentheses nesting limit. + * + * @param api the PCRE2 API + * @return the default parentheses nesting limit + */ + public static int getDefaultParenthesesNestingLimit(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var parensLimit = new int[1]; + final var result = api.config(IPcre2.CONFIG_PARENSLIMIT, parensLimit); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return parensLimit[0]; + } + + /** + * Get the default newline sequence. + * + * @param api the PCRE2 API + * @return the default newline sequence + */ + public static Pcre2Newline getDefaultNewline(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var newline = new int[1]; + final var result = api.config(IPcre2.CONFIG_NEWLINE, newline); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return Pcre2Newline.valueOf(newline[0]).orElseThrow(); + } + + /** + * Check if the \C is disabled. + * + * @param api the PCRE2 API + * @return {@code true} if the \C is disabled, {@code false} otherwise + */ + public static boolean isBackslashCDisabled(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var backslashCDisabled = new int[1]; + final var result = api.config(IPcre2.CONFIG_BSR, backslashCDisabled); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return backslashCDisabled[0] == 1; + } + + /** + * Get the default match limit. + * + * @param api the PCRE2 API + * @return the default match limit + */ + public static int getDefaultMatchLimit(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var matchLimit = new int[1]; + final var result = api.config(IPcre2.CONFIG_MATCHLIMIT, matchLimit); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return matchLimit[0]; + } + + /** + * Get the internal link size. + * + * @param api the PCRE2 API + * @return the internal link size + */ + public static int getInternalLinkSize(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var internalLinkSize = new int[1]; + final var result = api.config(IPcre2.CONFIG_LINKSIZE, internalLinkSize); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return internalLinkSize[0]; + } + + /** + * Get the JIT target. + * + * @param api the PCRE2 API + * @return the JIT target or {@code null} if JIT is not supported + */ + public static String getJitTarget(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var targetSize = api.config(IPcre2.CONFIG_JITTARGET); + if (targetSize < 0) { + if (targetSize == IPcre2.ERROR_BADOPTION) { + return null; + } + throw new IllegalStateException(getErrorMessage(api, targetSize)); + } + + final var targetBuffer = ByteBuffer.allocateDirect(targetSize); + final var targetCopyResult = api.config(IPcre2.CONFIG_JITTARGET, targetBuffer); + if (targetCopyResult < 0) { + throw new IllegalStateException(getErrorMessage(api, targetCopyResult)); + } + + return StandardCharsets.UTF_8.decode(targetBuffer.limit(targetSize - 1)).toString(); + } + + /** + * Check if JIT is supported. + * + * @param api the PCRE2 API + * @return {@code true} if JIT is supported, {@code false} otherwise + */ + public static boolean isJitSupported(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var jitSupported = new int[1]; + final var result = api.config(IPcre2.CONFIG_JIT, jitSupported); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return jitSupported[0] == 1; + } + + /** + * Get the default heap memory limit. + * + * @param api the PCRE2 API + * @return the default heap memory limit + */ + public static int getDefaultHeapLimit(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var heapLimit = new int[1]; + final var result = api.config(IPcre2.CONFIG_HEAPLIMIT, heapLimit); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return heapLimit[0]; + } + + /** + * Get the default backtracking depth limit. + * + * @param api the PCRE2 API + * @return the default backtracking depth limit + */ + public static int getDefaultDepthLimit(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var depthLimit = new int[1]; + final var result = api.config(IPcre2.CONFIG_DEPTHLIMIT, depthLimit); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return depthLimit[0]; + } + + /** + * Get which of the character width is compiled. + * + * @param api the PCRE2 API + * @return the compiled character width (8, 16, or 32) + */ + public static int getCompiledWidth(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var width = new int[1]; + final var result = api.config(IPcre2.CONFIG_COMPILED_WIDTHS, width); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return width[0]; + } + + /** + * Get what \R matches by default. + * + * @param api the PCRE2 API + * @return the default \R match + */ + public static Pcre2Bsr getDefaultBsr(IPcre2 api) { + if (api == null) { + throw new IllegalArgumentException("api must not be null"); + } + + final var bsr = new int[1]; + final var result = api.config(IPcre2.CONFIG_BSR, bsr); + if (result < 0) { + throw new IllegalStateException(getErrorMessage(api, result)); + } + + return Pcre2Bsr.valueOf(bsr[0]).orElseThrow(); + } + /** * Get the error message for the given error code. * diff --git a/test/src/main/java/org/pcre4j/test/Pcre2Tests.java b/test/src/main/java/org/pcre4j/test/Pcre2Tests.java index 1247b44..3cffb7e 100644 --- a/test/src/main/java/org/pcre4j/test/Pcre2Tests.java +++ b/test/src/main/java/org/pcre4j/test/Pcre2Tests.java @@ -15,10 +15,7 @@ package org.pcre4j.test; import org.junit.jupiter.api.Test; -import org.pcre4j.Pcre2Code; -import org.pcre4j.Pcre2CompileOption; -import org.pcre4j.Pcre2MatchData; -import org.pcre4j.Pcre2MatchOption; +import org.pcre4j.*; import java.util.EnumSet; @@ -27,6 +24,25 @@ public abstract class Pcre2Tests { + @Test + public void config() { + final var api = Pcre4j.api(); + Pcre4jUtils.getVersion(api); + Pcre4jUtils.getUnicodeVersion(api); + Pcre4jUtils.isUnicodeSupported(api); + Pcre4jUtils.getDefaultParenthesesNestingLimit(api); + Pcre4jUtils.getDefaultNewline(api); + Pcre4jUtils.isBackslashCDisabled(api); + Pcre4jUtils.getDefaultMatchLimit(api); + Pcre4jUtils.getInternalLinkSize(api); + Pcre4jUtils.getJitTarget(api); + Pcre4jUtils.isJitSupported(api); + Pcre4jUtils.getDefaultHeapLimit(api); + Pcre4jUtils.getDefaultDepthLimit(api); + Pcre4jUtils.getCompiledWidth(api); + Pcre4jUtils.getDefaultBsr(api); + } + @Test public void plainStringMatch() { final var code = new Pcre2Code(