bryancall commented on code in PR #13661:
URL: https://github.com/apache/trafficserver/pull/13661#discussion_r3983498957


##########
src/tsutil/unit_tests/test_Regex.cc:
##########
@@ -1050,3 +1050,90 @@ TEST_CASE("Regex end-anchor with alternation", 
"[libts][Regex]")
   CHECK(r.exec("cdn.example.com.evil.com", matches) == RE_ERROR_NOMATCH);
   CHECK(r.exec("prefix.cdn.example.com", matches) == RE_ERROR_NOMATCH);
 }
+
+namespace
+{
+/** Does PCRE2 have JIT code for this pattern?
+ *
+ * The two tests below are about the JIT stack, and PCRE2 consults it only 
when it
+ * has JIT code to run. Without it both a blank context and the shared one 
take the
+ * interpreter and return the same answer, so the tests would pass whether or 
not
+ * the behaviour they describe is present. Ask PCRE2 rather than assume.
+ */
+bool
+pattern_has_jit(char const *pattern)
+{
+  int         errnum    = 0;
+  PCRE2_SIZE  erroffset = 0;
+  pcre2_code *code = pcre2_compile(reinterpret_cast<PCRE2_SPTR>(pattern), 
PCRE2_ZERO_TERMINATED, 0, &errnum, &erroffset, nullptr);
+  if (code == nullptr) {
+    return false;
+  }
+  pcre2_jit_compile(code, PCRE2_JIT_COMPLETE);
+  size_t jit_size = 0;
+  pcre2_pattern_info(code, PCRE2_INFO_JITSIZE, &jit_size);
+  pcre2_code_free(code);
+  return jit_size > 0;
+}
+} // namespace
+
+// A caller-supplied RegexMatchContext must behave like the shared context that
+// Regex::exec uses when none is supplied. A context built from scratch 
silently
+// drops everything the shared one configures, which is how regex_remap came to
+// run with PCRE2's fallback 32KiB JIT stack instead of the 1MiB one.
+TEST_CASE("RegexMatchContext matches the shared context", 
"[libts][Regex][RegexMatchContext]")
+{
+  // Quantified alternation of capture groups: every subject character pushes a
+  // backtracking frame, so the JIT stack size is what bounds this.
+  char const *const pattern = R"(^(?:(a)|(b))+$)";
+  if (!pattern_has_jit(pattern)) {
+    SKIP("PCRE2 has no JIT for this pattern, so the JIT stack is never 
consulted");
+  }
+
+  Regex re;
+  REQUIRE(re.compile(pattern));
+
+  std::string const subject(1000, 'a');
+
+  RegexMatches      shared_matches;
+  RegexMatchContext match_context;
+  RegexMatches      own_matches;
+
+  int const shared_rc = re.exec(subject, shared_matches);
+  int const own_rc    = re.exec(subject, own_matches, 0, &match_context);
+  CAPTURE(shared_rc, own_rc);
+
+  REQUIRE(shared_rc > 0);
+  REQUIRE(own_rc == shared_rc);
+}
+
+// The guard from #5762: a pattern that backtracks once per character must fail
+// cleanly rather than run the thread out of stack. PCRE1 recursed on the 
machine
+// stack and a long enough subject crashed the server; PCRE2 must report an 
error
+// instead. If this ever crashes rather than fails, that regression is back.
+TEST_CASE("Regex reports resource exhaustion rather than crashing", 
"[libts][Regex][limits]")
+{
+  // Only the JIT path has a bound to exhaust here. PCRE2's interpreter keeps 
its
+  // backtracking frames on the heap, so it matches this subject rather than 
running
+  // out of anything, and there is no resource error to assert.
+  char const *const pattern = 
R"(^/alpha/bravo/[?]((?!action=(newsfeed|calendar|contacts|notepad)).)*$)";
+  if (!pattern_has_jit(pattern)) {
+    SKIP("PCRE2 has no JIT for this pattern, so there is no stack bound to 
exhaust");
+  }
+
+  Regex re;
+  REQUIRE(re.compile(pattern));
+
+  // Past what a 1MiB JIT stack holds for this pattern, which starts failing at
+  // roughly 43KiB of subject, so the bound is still exercised.
+  std::string subject{"/alpha/bravo/?"};
+  subject.append(2 * 1024 * 1024, 'x');
+

Review Comment:
   Done in db72f15ace, sized to 256KiB.
   
   One correction to the reasoning, since the outcome is right but the stated 
cost is not
   what I measured. The match time does not scale with the subject here: it 
bails at the
   JIT stack limit long before traversing it. Measured against libpcre2 10.47:
   
   ```
        65536 bytes ( 1.5x threshold) -> rc=-46    0.172 ms
       131072 bytes ( 3.0x threshold) -> rc=-46    0.138 ms
       262144 bytes ( 6.0x threshold) -> rc=-46    0.136 ms
      2097152 bytes (48.0x threshold) -> rc=-46    0.148 ms
   ```
   
   So this was not slower and had no timing sensitivity to remove. What it did 
have was a
   48 times oversized allocation for no added coverage, which is worth fixing 
on its own.
   
   I chose 256KiB rather than something nearer the threshold to keep a six 
times margin.
   The 43KiB figure is measured, and it came out identical on x86_64 and arm64, 
but a
   platform with larger JIT frames would push it up, and a test that stops 
exercising the
   bound would pass silently rather than fail. The margin and that reasoning 
are now in a
   comment above the line so it does not get trimmed later.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to