RegExpEmpty RegExpEmpty::kInstance;
+static Interval ListCaptureRegisters(ZoneList<RegExpTree*>* children) {
+ Interval result = Interval::Empty();
+ for (int i = 0; i < children->length(); i++)
+ result = result.Union(children->at(i)->CaptureRegisters());
+ return result;
+}
+
+
+Interval RegExpAlternative::CaptureRegisters() {
+ return ListCaptureRegisters(nodes());
+}
+
+
+Interval RegExpDisjunction::CaptureRegisters() {
+ return ListCaptureRegisters(alternatives());
+}
+
+
+Interval RegExpLookahead::CaptureRegisters() {
+ return body()->CaptureRegisters();
+}
+
+
+Interval RegExpCapture::CaptureRegisters() {
+ Interval self(StartRegister(index()), EndRegister(index()));
+ return self.Union(body()->CaptureRegisters());
+}
+
+
+Interval RegExpQuantifier::CaptureRegisters() {
+ return body()->CaptureRegisters();
+}
+
+
// Convert regular expression trees to a simple sexp representation.
// This representation should be different from the input grammar
// in as many cases as possible, to make it more difficult for incorrect
// Regular expressions
+class RegExpVisitor BASE_EMBEDDED {
+ public:
+ virtual ~RegExpVisitor() { }
+#define MAKE_CASE(Name) \
+ virtual void* Visit##Name(RegExp##Name*, void* data) = 0;
+ FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE)
+#undef MAKE_CASE
+};
+
+
class RegExpTree: public ZoneObject {
public:
static const int kInfinity = kMaxInt;
virtual bool IsTextElement() { return false; }
virtual int min_match() = 0;
virtual int max_match() = 0;
+ // Returns the interval of registers used for captures within this
+ // expression.
+ virtual Interval CaptureRegisters() { return Interval::Empty(); }
virtual void AppendToText(RegExpText* text);
SmartPointer<const char> ToString();
#define MAKE_ASTYPE(Name) \
virtual RegExpNode* ToNode(RegExpCompiler* compiler,
RegExpNode* on_success);
virtual RegExpDisjunction* AsDisjunction();
+ virtual Interval CaptureRegisters();
virtual bool IsDisjunction();
virtual int min_match() { return min_match_; }
virtual int max_match() { return max_match_; }
virtual RegExpNode* ToNode(RegExpCompiler* compiler,
RegExpNode* on_success);
virtual RegExpAlternative* AsAlternative();
+ virtual Interval CaptureRegisters();
virtual bool IsAlternative();
virtual int min_match() { return min_match_; }
virtual int max_match() { return max_match_; }
RegExpCompiler* compiler,
RegExpNode* on_success);
virtual RegExpQuantifier* AsQuantifier();
+ virtual Interval CaptureRegisters();
virtual bool IsQuantifier();
virtual int min_match() { return min_match_; }
virtual int max_match() { return max_match_; }
RegExpCompiler* compiler,
RegExpNode* on_success);
virtual RegExpCapture* AsCapture();
+ virtual Interval CaptureRegisters();
virtual bool IsCapture();
virtual int min_match() { return body_->min_match(); }
virtual int max_match() { return body_->max_match(); }
virtual RegExpNode* ToNode(RegExpCompiler* compiler,
RegExpNode* on_success);
virtual RegExpLookahead* AsLookahead();
+ virtual Interval CaptureRegisters();
virtual bool IsLookahead();
virtual int min_match() { return 0; }
virtual int max_match() { return 0; }
RegExpNode* on_success);
virtual RegExpBackReference* AsBackReference();
virtual bool IsBackReference();
- virtual int min_match() { return capture_->min_match(); }
+ virtual int min_match() { return 0; }
virtual int max_match() { return capture_->max_match(); }
int index() { return capture_->index(); }
RegExpCapture* capture() { return capture_; }
};
-class RegExpVisitor BASE_EMBEDDED {
- public:
- virtual ~RegExpVisitor() { }
-#define MAKE_CASE(Name) \
- virtual void* Visit##Name(RegExp##Name*, void* data) = 0;
- FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE)
-#undef MAKE_CASE
-};
-
-
// ----------------------------------------------------------------------------
// Basic visitor
// - leaf node visitors are abstract.
return array;
}
+bool GenerationVariant::DeferredAction::Mentions(int that) {
+ if (type() == ActionNode::CLEAR_CAPTURES) {
+ Interval range = static_cast<DeferredClearCaptures*>(this)->range();
+ return range.Contains(that);
+ } else {
+ return reg() == that;
+ }
+}
+
bool GenerationVariant::mentions_reg(int reg) {
for (DeferredAction* action = actions_;
action != NULL;
action = action->next()) {
- if (reg == action->reg()) return true;
+ if (action->Mentions(reg))
+ return true;
}
return false;
}
for (DeferredAction* action = actions_;
action != NULL;
action = action->next()) {
- if (reg == action->reg()) {
+ if (action->Mentions(reg)) {
if (action->type() == ActionNode::STORE_POSITION) {
*cp_offset = static_cast<DeferredCapture*>(action)->cp_offset();
return true;
for (DeferredAction* action = actions_;
action != NULL;
action = action->next()) {
- affected_registers->Set(action->reg());
- if (action->reg() > max_register) max_register = action->reg();
+ if (action->type() == ActionNode::CLEAR_CAPTURES) {
+ Interval range = static_cast<DeferredClearCaptures*>(action)->range();
+ for (int i = range.from(); i <= range.to(); i++)
+ affected_registers->Set(i);
+ if (range.to() > max_register) max_register = range.to();
+ } else {
+ affected_registers->Set(action->reg());
+ if (action->reg() > max_register) max_register = action->reg();
+ }
}
return max_register;
}
}
int value = 0;
bool absolute = false;
+ bool clear = false;
int store_position = -1;
// This is a little tricky because we are scanning the actions in reverse
// historical order (newest first).
for (DeferredAction* action = actions_;
action != NULL;
action = action->next()) {
- if (action->reg() == reg) {
+ if (action->Mentions(reg)) {
switch (action->type()) {
case ActionNode::SET_REGISTER: {
GenerationVariant::DeferredSetRegister* psr =
value += psr->value();
absolute = true;
ASSERT_EQ(store_position, -1);
+ ASSERT(!clear);
break;
}
case ActionNode::INCREMENT_REGISTER:
value++;
}
ASSERT_EQ(store_position, -1);
+ ASSERT(!clear);
break;
case ActionNode::STORE_POSITION: {
GenerationVariant::DeferredCapture* pc =
static_cast<GenerationVariant::DeferredCapture*>(action);
- if (store_position == -1) {
+ if (!clear && store_position == -1) {
store_position = pc->cp_offset();
}
ASSERT(!absolute);
ASSERT_EQ(value, 0);
break;
}
+ case ActionNode::CLEAR_CAPTURES: {
+ // Since we're scanning in reverse order, if we've already
+ // set the position we have to ignore historically earlier
+ // clearing operations.
+ if (store_position == -1)
+ clear = true;
+ ASSERT(!absolute);
+ ASSERT_EQ(value, 0);
+ break;
+ }
default:
UNREACHABLE();
break;
}
if (store_position != -1) {
assembler->WriteCurrentPositionToRegister(reg, store_position);
- } else {
- if (absolute) {
- assembler->SetRegister(reg, value);
- } else {
- if (value != 0) {
- assembler->AdvanceRegister(reg, value);
- }
- }
+ } else if (clear) {
+ assembler->ClearRegister(reg);
+ } else if (absolute) {
+ assembler->SetRegister(reg, value);
+ } else if (value != 0) {
+ assembler->AdvanceRegister(reg, value);
}
}
}
}
+ActionNode* ActionNode::ClearCaptures(Interval range,
+ RegExpNode* on_success) {
+ ActionNode* result = new ActionNode(CLEAR_CAPTURES, on_success);
+ result->data_.u_clear_captures.range_from = range.from();
+ result->data_.u_clear_captures.range_to = range.to();
+ return result;
+}
+
+
ActionNode* ActionNode::BeginSubmatch(int stack_reg,
int position_reg,
RegExpNode* on_success) {
}
+class VisitMarker {
+ public:
+ explicit VisitMarker(NodeInfo* info) : info_(info) {
+ ASSERT(!info->visited);
+ info->visited = true;
+ }
+ ~VisitMarker() {
+ info_->visited = false;
+ }
+ private:
+ NodeInfo* info_;
+};
+
+
void LoopChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details,
RegExpCompiler* compiler,
int characters_filled_in) {
- if (body_can_be_zero_length_) return;
+ if (body_can_be_zero_length_ || info()->visited) return;
+ VisitMarker marker(info());
return ChoiceNode::GetQuickCheckDetails(details,
compiler,
characters_filled_in);
// is to use the Dispatch table to try only the relevant ones.
for (int i = first_normal_choice; i < choice_count; i++) {
GuardedAlternative alternative = alternatives_->at(i);
- AlternativeGeneration* alt_gen(alt_gens.at(i));
+ AlternativeGeneration* alt_gen = alt_gens.at(i);
alt_gen->quick_check_details.set_characters(preload_characters);
ZoneList<Guard*>* guards = alternative.guards();
int guard_count = (guards == NULL) ? 0 : guards->length();
new_variant.add_action(&new_set);
return on_success()->Emit(compiler, &new_variant);
}
+ case CLEAR_CAPTURES: {
+ GenerationVariant::DeferredClearCaptures
+ new_capture(Interval(data_.u_clear_captures.range_from,
+ data_.u_clear_captures.range_to));
+ GenerationVariant new_variant = *variant;
+ new_variant.add_action(&new_capture);
+ return on_success()->Emit(compiler, &new_variant);
+ }
case BEGIN_SUBMATCH:
if (!variant->is_trivial()) return variant->Flush(compiler, this);
assembler->WriteCurrentPositionToRegister(
that->data_.u_empty_match_check.repetition_register,
that->data_.u_empty_match_check.repetition_limit);
break;
+ case ActionNode::CLEAR_CAPTURES: {
+ stream()->Add("label=\"clear $%i to $%i\", shape=septagon",
+ that->data_.u_clear_captures.range_from,
+ that->data_.u_clear_captures.range_to);
+ break;
+ }
}
stream()->Add("];\n");
PrintAttributes(that);
if (max == 0) return on_success; // This can happen due to recursion.
bool body_can_be_empty = (body->min_match() == 0);
int body_start_reg = RegExpCompiler::kNoRegister;
+ Interval capture_registers = body->CaptureRegisters();
+ bool needs_capture_clearing = !capture_registers.is_empty();
if (body_can_be_empty) {
body_start_reg = compiler->AllocateRegister();
- } else {
+ } else if (!needs_capture_clearing) {
+ // Only unroll if there are no captures and the body can't be
+ // empty.
if (min > 0 && min <= kMaxUnrolledMinMatches) {
int new_max = (max == kInfinity) ? max : max - min;
// Recurse once to get the loop or optional matches after the fixed ones.
// so we can bail out if it was empty.
body_node = ActionNode::StorePosition(body_start_reg, body_node);
}
+ if (needs_capture_clearing) {
+ // Before entering the body of this loop we need to clear captures.
+ body_node = ActionNode::ClearCaptures(capture_registers, body_node);
+ }
GuardedAlternative body_alt(body_node);
if (has_max) {
Guard* body_guard = new Guard(reg_ctr, Guard::LT, max);
};
+// A simple closed interval.
+class Interval {
+ public:
+ Interval() : from_(kNone), to_(kNone) { }
+ Interval(int from, int to) : from_(from), to_(to) { }
+ Interval Union(Interval that) {
+ if (that.from_ == kNone)
+ return *this;
+ else if (from_ == kNone)
+ return that;
+ else
+ return Interval(Min(from_, that.from_), Max(to_, that.to_));
+ }
+ bool Contains(int value) {
+ return (from_ <= value) && (value <= to_);
+ }
+ bool is_empty() { return from_ == kNone; }
+ int from() { return from_; }
+ int to() { return to_; }
+ static Interval Empty() { return Interval(); }
+ static const int kNone = -1;
+ private:
+ int from_;
+ int to_;
+};
+
+
class SeqRegExpNode: public RegExpNode {
public:
explicit SeqRegExpNode(RegExpNode* on_success)
STORE_POSITION,
BEGIN_SUBMATCH,
POSITIVE_SUBMATCH_SUCCESS,
- EMPTY_MATCH_CHECK
+ EMPTY_MATCH_CHECK,
+ CLEAR_CAPTURES
};
static ActionNode* SetRegister(int reg, int val, RegExpNode* on_success);
static ActionNode* IncrementRegister(int reg, RegExpNode* on_success);
static ActionNode* StorePosition(int reg, RegExpNode* on_success);
- static ActionNode* BeginSubmatch(
- int stack_pointer_reg,
- int position_reg,
- RegExpNode* on_success);
- static ActionNode* PositiveSubmatchSuccess(
- int stack_pointer_reg,
- int restore_reg,
- RegExpNode* on_success);
- static ActionNode* EmptyMatchCheck(
- int start_register,
- int repetition_register,
- int repetition_limit,
- RegExpNode* on_success);
+ static ActionNode* ClearCaptures(Interval range, RegExpNode* on_success);
+ static ActionNode* BeginSubmatch(int stack_pointer_reg,
+ int position_reg,
+ RegExpNode* on_success);
+ static ActionNode* PositiveSubmatchSuccess(int stack_pointer_reg,
+ int restore_reg,
+ RegExpNode* on_success);
+ static ActionNode* EmptyMatchCheck(int start_register,
+ int repetition_register,
+ int repetition_limit,
+ RegExpNode* on_success);
virtual void Accept(NodeVisitor* visitor);
virtual bool Emit(RegExpCompiler* compiler, GenerationVariant* variant);
virtual int EatsAtLeast(int recursion_depth);
int repetition_register;
int repetition_limit;
} u_empty_match_check;
+ struct {
+ int range_from;
+ int range_to;
+ } u_clear_captures;
} data_;
ActionNode(Type type, RegExpNode* on_success)
: SeqRegExpNode(on_success),
DeferredAction(ActionNode::Type type, int reg)
: type_(type), reg_(reg), next_(NULL) { }
DeferredAction* next() { return next_; }
+ bool Mentions(int reg);
int reg() { return reg_; }
ActionNode::Type type() { return type_; }
private:
int value_;
};
+ class DeferredClearCaptures : public DeferredAction {
+ public:
+ explicit DeferredClearCaptures(Interval range)
+ : DeferredAction(ActionNode::CLEAR_CAPTURES, -1),
+ range_(range) { }
+ Interval range() { return range_; }
+ private:
+ Interval range_;
+ };
+
class DeferredIncrementRegister: public DeferredAction {
public:
explicit DeferredIncrementRegister(int reg)
__ push(esi);
__ push(edi);
__ push(ebx); // Callee-save on MacOS.
+ __ push(Immediate(0)); // Make room for input start minus one
// Check if we have space on the stack for registers.
Label retry_stack_check;
// Set eax to address of char before start of input
// (effectively string position -1).
__ lea(eax, Operand(edi, -char_size()));
+ // Store this value in a local variable, for use when clearing
+ // position registers.
+ __ mov(Operand(ebp, kInputStartMinusOne), eax);
Label init_loop;
__ bind(&init_loop);
__ mov(Operand(ebp, ecx, times_1, +0), eax);
}
+void RegExpMacroAssemblerIA32::ClearRegister(int reg) {
+ __ mov(eax, Operand(ebp, kInputStartMinusOne));
+ __ mov(register_location(reg), eax);
+}
+
+
void RegExpMacroAssemblerIA32::WriteStackPointerToRegister(int reg) {
__ mov(register_location(reg), backtrack_stackpointer());
}
virtual void SetRegister(int register_index, int to);
virtual void Succeed();
virtual void WriteCurrentPositionToRegister(int reg, int cp_offset);
+ virtual void ClearRegister(int reg);
virtual void WriteStackPointerToRegister(int reg);
static Result Execute(Code* code,
static const int kAtStart = kRegisterOutput + kPointerSize;
static const int kStackHighEnd = kAtStart + kPointerSize;
// Below the frame pointer - local stack variables.
+ // When adding local variables remember to push space for them in
+ // the frame in GetCode.
static const int kBackup_esi = kFramePointer - kPointerSize;
static const int kBackup_edi = kBackup_esi - kPointerSize;
static const int kBackup_ebx = kBackup_edi - kPointerSize;
+ static const int kInputStartMinusOne = kBackup_ebx - kPointerSize;
// First register address. Following registers are below it on the stack.
- static const int kRegisterZero = kBackup_ebx - kPointerSize;
+ static const int kRegisterZero = kInputStartMinusOne - kPointerSize;
// Initial size of code buffer.
static const size_t kRegExpCodeSize = 1024;
}
+void RegExpMacroAssemblerIrregexp::ClearRegister(int reg) {
+ SetRegister(reg, -1);
+}
+
+
void RegExpMacroAssemblerIrregexp::ReadCurrentPositionFromRegister(
int register_index) {
ASSERT(register_index >= 0);
virtual void AdvanceRegister(int reg, int by); // r[reg] += by.
virtual void SetRegister(int register_index, int to);
virtual void WriteCurrentPositionToRegister(int reg, int cp_offset);
+ virtual void ClearRegister(int reg);
virtual void ReadCurrentPositionFromRegister(int reg);
virtual void WriteStackPointerToRegister(int reg);
virtual void ReadStackPointerFromRegister(int reg);
}
+void RegExpMacroAssemblerTracer::ClearRegister(int reg) {
+ PrintF(" ClearRegister(register=%d);\n", reg);
+ assembler_->ClearRegister(reg);
+}
+
+
void RegExpMacroAssemblerTracer::ReadCurrentPositionFromRegister(int reg) {
PrintF(" ReadCurrentPositionFromRegister(register=%d);\n", reg);
assembler_->ReadCurrentPositionFromRegister(reg);
virtual void SetRegister(int register_index, int to);
virtual void Succeed();
virtual void WriteCurrentPositionToRegister(int reg, int cp_offset);
+ virtual void ClearRegister(int reg);
virtual void WriteStackPointerToRegister(int reg);
private:
RegExpMacroAssembler* assembler_;
virtual void SetRegister(int register_index, int to) = 0;
virtual void Succeed() = 0;
virtual void WriteCurrentPositionToRegister(int reg, int cp_offset) = 0;
+ virtual void ClearRegister(int reg) = 0;
virtual void WriteStackPointerToRegister(int reg) = 0;
private:
TEST(Graph) {
V8::Initialize(NULL);
- Execute("(?:a|)*", false, true, true);
+ Execute("(?:(?:x(.))?\1)+$", false, true, true);
}
+++ /dev/null
-// Copyright 2008 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following
-// disclaimer in the documentation and/or other materials provided
-// with the distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived
-// from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-// See http://code.google.com/p/v8/issues/detail?id=176
-
-assertEquals("f,",
- "foo".match(/(?:(?=(f)o))?f/).toString(),
- "zero length match in (?:) with capture in lookahead");
-assertEquals("f,",
- "foo".match(/(?=(f)o)?f/).toString(),
- "zero length match in (?=) with capture in lookahead");
-assertEquals("fo,f",
- "foo".match(/(?:(?=(f)o)f)?o/),
- "non-zero length match with capture in lookahead");
-assertEquals("fo,f",
- "foo".match(/(?:(?=(f)o)f?)?o/),
- "non-zero length match with greedy ? in (?:)");
-assertEquals("fo,f",
- "foo".match(/(?:(?=(f)o)f??)?o/),
- "non-zero length match with non-greedy ? in (?:), o forces backtrack");
-assertEquals("fo,f",
- "foo".match(/(?:(?=(f)o)f??)?./),
- "non-zero length match with non-greedy ? in (?:), zero length match causes backtrack");
-assertEquals("f,",
- "foo".match(/(?:(?=(f)o)fx)?./),
- "x causes backtrack inside (?:)");
--- /dev/null
+// Copyright 2008 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// See http://code.google.com/p/v8/issues/detail?id=187
+
+assertEquals("f,", "foo".match(/(?:(?=(f)o)fx|)./));
}
+function deepEquals(a, b) {
+ if (a == b) return true;
+ if ((typeof a) !== 'object' || (typeof b) !== 'object' ||
+ (a === null) || (b === null))
+ return false;
+ if (a.constructor === Array) {
+ if (b.constructor !== Array)
+ return false;
+ if (a.length != b.length)
+ return false;
+ for (var i = 0; i < a.length; i++) {
+ if (i in a) {
+ if (!(i in b) || !(deepEquals(a[i], b[i])))
+ return false;
+ } else if (i in b) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+
function assertEquals(expected, found, name_opt) {
- if (expected != found) {
+ if (!deepEquals(found, expected)) {
fail(expected, found, name_opt);
}
}
# no longer using JSCRE.
regexp-UC16: PASS || FAIL
+# These tests pass with irregexp but fail with jscre
+regress/regress-176: PASS || FAIL
+regexp-loop-capture: PASS || FAIL
+
[ $arch == arm ]
# Slow tests which times out in debug mode.
--- /dev/null
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+assertEquals(["abc",undefined,undefined,"c"], /(?:(a)|(b)|(c))+/.exec("abc"));
+assertEquals(["ab",undefined], /(?:(a)|b)*/.exec("ab"));
--- /dev/null
+// Copyright 2008 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// See http://code.google.com/p/v8/issues/detail?id=176
+
+assertEquals("f,",
+ "foo".match(/(?:(?=(f)o))?f/).toString(),
+ "zero length match in (?:) with capture in lookahead");
+assertEquals("f,",
+ "foo".match(/(?=(f)o)?f/).toString(),
+ "zero length match in (?=) with capture in lookahead");
+assertEquals("fo,f",
+ "foo".match(/(?:(?=(f)o)f)?o/),
+ "non-zero length match with capture in lookahead");
+assertEquals("fo,f",
+ "foo".match(/(?:(?=(f)o)f?)?o/),
+ "non-zero length match with greedy ? in (?:)");
+assertEquals("fo,f",
+ "foo".match(/(?:(?=(f)o)f??)?o/),
+ "non-zero length match with non-greedy ? in (?:), o forces backtrack");
+assertEquals("fo,f",
+ "foo".match(/(?:(?=(f)o)f??)?./),
+ "non-zero length match with non-greedy ? in (?:), zero length match causes backtrack");
+assertEquals("f,",
+ "foo".match(/(?:(?=(f)o)fx)?./),
+ "x causes backtrack inside (?:)");