diff --git a/AGENTS.md b/AGENTS.md index ca83e807..96827dad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -88,6 +88,8 @@ the repo carries only the distilled knowledge. Use it instead of re-deriving: + + diff --git a/reggie-codegen/src/main/java/com/datadoghq/reggie/codegen/codegen/DFAUnrolledBytecodeGenerator.java b/reggie-codegen/src/main/java/com/datadoghq/reggie/codegen/codegen/DFAUnrolledBytecodeGenerator.java index cd42c124..f1c983a3 100644 --- a/reggie-codegen/src/main/java/com/datadoghq/reggie/codegen/codegen/DFAUnrolledBytecodeGenerator.java +++ b/reggie-codegen/src/main/java/com/datadoghq/reggie/codegen/codegen/DFAUnrolledBytecodeGenerator.java @@ -1718,9 +1718,13 @@ private void generateFindMatchFromMethodTagged(ClassWriter cw, String className) * Generates a private {@code findMatchFromForMatch} variant that uses the tagged DFA WITHOUT * priority-cut. Used by {@link #generateMatchMethod} for full-input (match()) semantics: the * caller must verify that the returned match spans the entire input, but the TDFA must not commit - * early to a shorter high-priority prefix match. + * early to a shorter high-priority prefix match. No-op unless the tagged DFA is used with groups + * (the only case where {@link #generateMatchMethod} calls it). */ public void generateFindMatchFromMethodTaggedNoCut(ClassWriter cw, String className) { + if (!(useTaggedDFA && groupCount > 0)) { + return; + } this.ownerInternalName = className; generateFindMatchFromMethodTaggedImpl( cw, className, "findMatchFromForMatch", ACC_PRIVATE, false); diff --git a/reggie-processor/src/main/java/com/datadoghq/reggie/processor/ReggieMatcherBytecodeGenerator.java b/reggie-processor/src/main/java/com/datadoghq/reggie/processor/ReggieMatcherBytecodeGenerator.java index ade69a68..b4430762 100644 --- a/reggie-processor/src/main/java/com/datadoghq/reggie/processor/ReggieMatcherBytecodeGenerator.java +++ b/reggie-processor/src/main/java/com/datadoghq/reggie/processor/ReggieMatcherBytecodeGenerator.java @@ -459,6 +459,9 @@ public byte[] generate() throws Exception { unrolledGen.generateFindFromMethod(cw, getJavaClassName()); unrolledGen.generateMatchesAtStartMethod(cw); unrolledGen.generateMatchMethod(cw, getJavaClassName()); + // Private findMatchFromForMatch helper called by tagged match() (same as RuntimeCompiler); + // no-op for non-tagged DFAs + unrolledGen.generateFindMatchFromMethodTaggedNoCut(cw, getJavaClassName()); unrolledGen.generateMatchesBoundedMethod(cw, getJavaClassName()); unrolledGen.generateMatchBoundedMethod(cw, getJavaClassName()); unrolledGen.generateFindMatchMethod(cw, getJavaClassName()); @@ -468,6 +471,9 @@ public byte[] generate() throws Exception { // findAll/replaceAll/split use throws NoSuchMethodError unrolledGen.generateFindLongestMatchEndMethod(cw, getJavaClassName()); unrolledGen.generateFindBoundsFromMethod(cw, getJavaClassName()); + // Static lookup tables for range-heavy charsets referenced by the methods above (same as + // RuntimeCompiler); must follow every generate*Method. No-op when none were needed. + unrolledGen.generateLookupTables(cw); break; case DFA_SWITCH: diff --git a/reggie-processor/src/test/java/com/datadoghq/reggie/processor/ReggieMatcherBytecodeGeneratorTest.java b/reggie-processor/src/test/java/com/datadoghq/reggie/processor/ReggieMatcherBytecodeGeneratorTest.java index d7cfe06f..b65b7680 100644 --- a/reggie-processor/src/test/java/com/datadoghq/reggie/processor/ReggieMatcherBytecodeGeneratorTest.java +++ b/reggie-processor/src/test/java/com/datadoghq/reggie/processor/ReggieMatcherBytecodeGeneratorTest.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.*; +import com.datadoghq.reggie.runtime.MatchResult; import com.datadoghq.reggie.runtime.ReggieMatcher; import java.lang.reflect.Method; import org.junit.jupiter.api.Test; @@ -595,6 +596,36 @@ void twoProviderClassesWithSameMethodNameProduceDistinctMatchers() throws Except (Boolean) matchesB.invoke(matcherB, "123"), "ProviderB_ValueMatcher must not match digits"); } + /** + * Tagged DFA_UNROLLED match() calls the private {@code findMatchFromForMatch} helper; the APT + * path must emit it (as RuntimeCompiler does) or match() throws NoSuchMethodError. + */ + @Test + void taggedDfaUnrolledMatchHelperIsGenerated() throws Exception { + Object matcher = compile("(?i)^https?://", "TaggedUnrolledMatchMatcher"); + Method match = matcher.getClass().getMethod("match", String.class); + + Object result = match.invoke(matcher, "https://"); + assertNotNull(result); + assertEquals(8, ((MatchResult) result).end()); + assertNotNull(match.invoke(matcher, "http://")); + assertNull(match.invoke(matcher, "https://example.com")); + assertNull(match.invoke(matcher, "ftp://")); + } + + /** + * DFA_UNROLLED matches range-heavy charsets through static {@code $cs_N} lookup tables; the APT + * path must emit them (as RuntimeCompiler does) or matching throws NoSuchFieldError. + */ + @Test + void dfaUnrolledLookupTablesAreGenerated() throws Exception { + Object matcher = compile(".*[\\p{IsAlphabetic}].*", "HugeCharsetMatcher"); + Method matches = matcher.getClass().getMethod("matches", String.class); + assertTrue((Boolean) matches.invoke(matcher, "a1")); + assertTrue((Boolean) matches.invoke(matcher, "1\u00e9")); + assertFalse((Boolean) matches.invoke(matcher, "123")); + } + // --- Tests for Fix 1: resolveRealization() must honour needsFallback() for PIKEVM_CAPTURE --- @Test