commit 7c3cc4a7aa4ee76df736855165b44cca0d1a20c9
parent dde0952bd6f442b6891fea91cfba17ee34bf490b
Author: André Bargull <andre.bargull@gmail.com>
Date: Fri, 17 Oct 2025 09:55:31 +0000
Bug 1992107: Handle near overrecursion in RegExpDepthCheck. r=iain
`AutoCheckRecursionLimit` checks for overrecursion by comparing against the
base-pointer (`__builtin_frame_address(0)`). This can lead to issues when we're
near the recursion limit and the RegExp doesn't contain any nested structures:
`uint8_t padding[FRAME_PADDING]` increases the stack-pointer, but because recursion
checks are performed against the base-pointer, adding the padding array is a
no-op for `RegExpNode` elements without child nodes (the `LEAF_DEPTH` case).
Add `AutoCheckRecursionLimit::checkWithExtraDontReport` so we can add the frame
padding directly to the base-pointer address, so we can also check for over-
recursion on leaf RegExp nodes.
Differential Revision: https://phabricator.services.mozilla.com/D268720
Diffstat:
3 files changed, 46 insertions(+), 26 deletions(-)
diff --git a/js/public/friend/StackLimits.h b/js/public/friend/StackLimits.h
@@ -163,6 +163,8 @@ class MOZ_RAII AutoCheckRecursionLimit {
FrontendContext* fc) const;
[[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithExtra(JSContext* cx,
size_t extra) const;
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithExtraDontReport(
+ JSContext* cx, size_t extra) const;
[[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithStackPointerDontReport(
JSContext* cx, void* sp) const;
[[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithStackPointerDontReport(
@@ -285,17 +287,22 @@ MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithStackPointerDontReport(
MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithExtra(
JSContext* cx, size_t extra) const {
+ if (MOZ_UNLIKELY(!checkWithExtraDontReport(cx, extra))) {
+ ReportOverRecursed(cx);
+ return false;
+ }
+ return true;
+}
+
+MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithExtraDontReport(
+ JSContext* cx, size_t extra) const {
char* sp = reinterpret_cast<char*>(__builtin_frame_address(0));
#if JS_STACK_GROWTH_DIRECTION > 0
sp += extra;
#else
sp -= extra;
#endif
- if (MOZ_UNLIKELY(!checkWithStackPointerDontReport(cx, sp))) {
- ReportOverRecursed(cx);
- return false;
- }
- return true;
+ return checkWithStackPointerDontReport(cx, sp);
}
MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkSystem(
diff --git a/js/src/irregexp/RegExpAPI.cpp b/js/src/irregexp/RegExpAPI.cpp
@@ -417,12 +417,10 @@ class RegExpDepthCheck final : public v8::internal::RegExpVisitor {
}
// Leaf nodes with no children
-#define LEAF_DEPTH(Kind) \
- void* Visit##Kind(v8::internal::RegExp##Kind* node, void*) override { \
- uint8_t padding[FRAME_PADDING]; \
- dummy_ = padding; /* Prevent padding from being optimized away.*/ \
- AutoCheckRecursionLimit recursion(cx_); \
- return (void*)recursion.checkDontReport(cx_); \
+#define LEAF_DEPTH(Kind) \
+ void* Visit##Kind(v8::internal::RegExp##Kind* node, void*) override { \
+ AutoCheckRecursionLimit recursion(cx_); \
+ return (void*)recursion.checkWithExtraDontReport(cx_, FRAME_PADDING); \
}
LEAF_DEPTH(Assertion)
@@ -437,10 +435,8 @@ class RegExpDepthCheck final : public v8::internal::RegExpVisitor {
// Wrapper nodes with one child
#define WRAPPER_DEPTH(Kind) \
void* Visit##Kind(v8::internal::RegExp##Kind* node, void*) override { \
- uint8_t padding[FRAME_PADDING]; \
- dummy_ = padding; /* Prevent padding from being optimized away.*/ \
AutoCheckRecursionLimit recursion(cx_); \
- if (!recursion.checkDontReport(cx_)) { \
+ if (!recursion.checkWithExtraDontReport(cx_, FRAME_PADDING)) { \
return nullptr; \
} \
return node->body()->Accept(this, nullptr); \
@@ -454,10 +450,8 @@ class RegExpDepthCheck final : public v8::internal::RegExpVisitor {
void* VisitAlternative(v8::internal::RegExpAlternative* node,
void*) override {
- uint8_t padding[FRAME_PADDING];
- dummy_ = padding; /* Prevent padding from being optimized away.*/
AutoCheckRecursionLimit recursion(cx_);
- if (!recursion.checkDontReport(cx_)) {
+ if (!recursion.checkWithExtraDontReport(cx_, FRAME_PADDING)) {
return nullptr;
}
for (auto* child : *node->nodes()) {
@@ -469,10 +463,8 @@ class RegExpDepthCheck final : public v8::internal::RegExpVisitor {
}
void* VisitDisjunction(v8::internal::RegExpDisjunction* node,
void*) override {
- uint8_t padding[FRAME_PADDING];
- dummy_ = padding; /* Prevent padding from being optimized away.*/
AutoCheckRecursionLimit recursion(cx_);
- if (!recursion.checkDontReport(cx_)) {
+ if (!recursion.checkWithExtraDontReport(cx_, FRAME_PADDING)) {
return nullptr;
}
for (auto* child : *node->alternatives()) {
@@ -484,10 +476,8 @@ class RegExpDepthCheck final : public v8::internal::RegExpVisitor {
}
void* VisitClassSetExpression(v8::internal::RegExpClassSetExpression* node,
void*) override {
- uint8_t padding[FRAME_PADDING];
- dummy_ = padding; /* Prevent padding from being optimized away.*/
AutoCheckRecursionLimit recursion(cx_);
- if (!recursion.checkDontReport(cx_)) {
+ if (!recursion.checkWithExtraDontReport(cx_, FRAME_PADDING)) {
return nullptr;
}
for (auto* child : *node->operands()) {
@@ -500,7 +490,6 @@ class RegExpDepthCheck final : public v8::internal::RegExpVisitor {
private:
JSContext* cx_;
- void* dummy_ = nullptr;
// This size is picked to be comfortably larger than any
// RegExp*::ToNode stack frame.
@@ -794,8 +783,7 @@ bool CompilePattern(JSContext* cx, MutableHandleRegExpShared re,
// Avoid stack overflow while recursively walking the AST.
RegExpDepthCheck depthCheck(cx);
if (!depthCheck.check(data.tree)) {
- JS_ReportErrorASCII(cx, "regexp too big");
- cx->reportResourceExhaustion();
+ ReportOverRecursed(cx);
return false;
}
diff --git a/js/src/jit-test/tests/regexp/over-recursion-with-leaf-nodes.js b/js/src/jit-test/tests/regexp/over-recursion-with-leaf-nodes.js
@@ -0,0 +1,25 @@
+function hasChildNodes() {
+ // Recurse so we get close to the stack limit.
+ try {
+ hasChildNodes();
+ } catch {
+ // Ignore over-recursion error.
+ }
+
+ // RegExp with child nodes.
+ return RegExp(".|.").test("");
+}
+hasChildNodes();
+
+function hasLeafNode() {
+ // Recurse so we get close to the stack limit.
+ try {
+ hasLeafNode();
+ } catch {
+ // Ignore over-recursion error.
+ }
+
+ // RegExp consisting of a single leaf node.
+ return RegExp(".").test("");
+}
+hasLeafNode();