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


##########
src/tsutil/unit_tests/test_Regex.cc:
##########
@@ -1147,3 +1152,260 @@ TEST_CASE("Regex copies answer the same as their 
original", "[libts][Regex][copy
     CHECK(original.exec(ordinary, original_ordinary) == copy.exec(ordinary, 
copy_ordinary));
   }
 }
+
+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.
+ *
+ * This asks about one pattern, deliberately: PCRE2 declines some pattern 
items and
+ * leaves PCRE2_INFO_JITSIZE at zero on a build whose JIT is otherwise fine, 
so the
+ * library-wide probes (pcre2_config, PCRE2_JIT_TEST_ALLOC) answer a different
+ * question. Call it from the test thread only; Catch2 assertions are not 
thread safe.
+ */
+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);
+
+  // A pattern that will not compile is a broken test, not a build without a 
JIT.
+  // Reporting it as "no JIT" would turn a typo into a silent skip.
+  REQUIRE(code != nullptr);
+
+  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));
+
+  // 40 bytes of JIT stack per subject character, so PCRE2's 32KiB fallback 
stops at
+  // 818 characters and the shared context's 1MiB stack at 26,213. 5,000 sits 
six
+  // times above the first and five times below the second, so a blank context 
and
+  // the shared one give different answers with margin either way.
+  std::string const subject(5000, '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 copy paths must carry the inherited configuration too. Building a 
blank
+  // context in either of them is the same defect, and nothing else here 
notices.
+  RegexMatchContext const copied{match_context};
+  RegexMatchContext       assigned;
+
+  assigned = match_context;

Review Comment:
   Fixed. `operator=` releases the old context before taking the new one as of 
`e4cafab`, which is the leak this flagged, and `9e73cb6` adds the OOM case on 
top so a failing copy leaves the object untouched rather than empty.



##########
src/tsutil/Regex.cc:
##########
@@ -257,8 +310,20 @@ struct RegexMatchContext::_MatchContext {
 //----------------------------------------------------------------------------
 RegexMatchContext::RegexMatchContext()
 {
-  auto ctx = pcre2_match_context_create(nullptr);
-  debug_assert_message(ctx, "Failed to allocate custom pcre2 match context");
+  // Copy the shared context rather than building a blank one. A blank context
+  // silently drops everything the shared context configures, which is how this
+  // type came to run with PCRE2's fallback 32KiB JIT stack instead of the 1MiB
+  // one. Callers override only the fields they mean to.
+  //
+  // pcre2_match_context_copy dereferences its argument rather than returning 
null
+  // for one, and RegexContext's constructor does not check its allocations, 
so a
+  // shared context that failed to allocate would crash here. Fall back to a 
blank
+  // context, which is what this constructor built before and which no reader 
of
+  // _match_context dereferences unchecked.
+  auto *shared = RegexContext::get_instance()->get_match_context();
+  auto *ctx    = shared != nullptr ? pcre2_match_context_copy(shared) : 
pcre2_match_context_create(nullptr);

Review Comment:
   This one was right and it was self inflicted. Fixed in `d5029bc`.
   
   The old constructor called `pcre2_match_context_create(nullptr)` and touched 
no thread-local. Copying the shared context meant calling 
`RegexContext::get_instance()`, which constructs a `thread_local` whose 
destructor registers through `__cxa_thread_atexit` and takes the dynamic loader 
lock. That is the exact inversion documented above `jit_stack_key` that this 
change exists to avoid, and the constructor reintroduced it for any thread that 
builds a context without matching, including a plugin doing so during static 
initialization while `dlopen` already holds the lock.
   
   Nothing the shared context carries is actually needed there. The JIT stack 
callback is thread independent because it resolves its stack through the 
pthread key, so the constructor now assigns the callback directly and gets the 
1MiB stack without the thread-local. A null general context is what it used 
before this branch. The copy and its null-shared fallback are gone, so the 
constructor is shorter than the version it replaces.



-- 
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