diff --git a/Libraries/LibRegex/RegexByteCode.cpp b/Libraries/LibRegex/RegexByteCode.cpp index 5447b4f0edf..b2df209a7be 100644 --- a/Libraries/LibRegex/RegexByteCode.cpp +++ b/Libraries/LibRegex/RegexByteCode.cpp @@ -218,6 +218,15 @@ ALWAYS_INLINE ExecutionResult OpCode_FailForks::execute(MatchInput const& input, return ExecutionResult::Failed_ExecuteLowPrioForks; } +ALWAYS_INLINE ExecutionResult OpCode_PopSaved::execute(MatchInput const& input, MatchState&) const +{ + if (input.saved_positions.is_empty() || input.saved_code_unit_positions.is_empty()) + return ExecutionResult::Failed_ExecuteLowPrioForks; + input.saved_positions.take_last(); + input.saved_code_unit_positions.take_last(); + return ExecutionResult::Failed_ExecuteLowPrioForks; +} + ALWAYS_INLINE ExecutionResult OpCode_Jump::execute(MatchInput const&, MatchState& state) const { state.instruction_position += offset(); diff --git a/Libraries/LibRegex/RegexByteCode.h b/Libraries/LibRegex/RegexByteCode.h index 5fede59fd65..b9a8cdb57b2 100644 --- a/Libraries/LibRegex/RegexByteCode.h +++ b/Libraries/LibRegex/RegexByteCode.h @@ -31,6 +31,7 @@ using ByteCodeValueType = u64; __ENUMERATE_OPCODE(ForkReplaceJump) \ __ENUMERATE_OPCODE(ForkReplaceStay) \ __ENUMERATE_OPCODE(FailForks) \ + __ENUMERATE_OPCODE(PopSaved) \ __ENUMERATE_OPCODE(SaveLeftCaptureGroup) \ __ENUMERATE_OPCODE(SaveRightCaptureGroup) \ __ENUMERATE_OPCODE(SaveRightNamedCaptureGroup) \ @@ -266,9 +267,15 @@ public: switch (type) { case LookAroundType::LookAhead: { // SAVE + // FORKJUMP _BODY + // POPSAVED + // LABEL _BODY // REGEXP BODY // RESTORE empend((ByteCodeValueType)OpCodeId::Save); + empend((ByteCodeValueType)OpCodeId::ForkJump); + empend((ByteCodeValueType)1); + empend((ByteCodeValueType)OpCodeId::PopSaved); extend(move(lookaround_body)); empend((ByteCodeValueType)OpCodeId::Restore); return; @@ -613,6 +620,14 @@ public: ByteString arguments_string() const override { return ByteString::empty(); } }; +class OpCode_PopSaved final : public OpCode { +public: + ExecutionResult execute(MatchInput const& input, MatchState& state) const override; + ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::PopSaved; } + ALWAYS_INLINE size_t size() const override { return 1; } + ByteString arguments_string() const override { return ByteString::empty(); } +}; + class OpCode_Save final : public OpCode { public: ExecutionResult execute(MatchInput const& input, MatchState& state) const override; diff --git a/Tests/LibRegex/Regex.cpp b/Tests/LibRegex/Regex.cpp index 5d9e5045d3f..2c8e393d96f 100644 --- a/Tests/LibRegex/Regex.cpp +++ b/Tests/LibRegex/Regex.cpp @@ -738,6 +738,9 @@ TEST_CASE(ECMA262_match) // Optimizer bug, ignoring an enabled trailing 'invert' when comparing blocks, ladybird#3421. { "[^]*[^]"sv, "i"sv, true }, { "xx|...|...."sv, "cd"sv, false }, + // Tests nested lookahead with alternation - verifies proper save/restore stack cleanup + { "a(?=.(?=c)|b)b"sv, "ab"sv, true }, + { "(?=)(?=\\d)"sv, "smart"sv, false }, }; for (auto& test : tests) {