diff --git a/src/builtins/builtins-regexp-gen.cc b/src/builtins/builtins-regexp-gen.cc index b76ee8cec7..d87fdf600d 100644 --- a/src/builtins/builtins-regexp-gen.cc +++ b/src/builtins/builtins-regexp-gen.cc @@ -482,7 +482,9 @@ Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult( Node* const int_zero = IntPtrConstant(0); Node* const smi_zero = SmiConstant(Smi::kZero); - if (!is_fastpath) { + if (is_fastpath) { + CSA_ASSERT(this, IsFastRegExpNoPrototype(context, regexp, LoadMap(regexp))); + } else { ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE, "RegExp.prototype.exec"); } @@ -499,18 +501,23 @@ Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult( Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath); var_lastindex.Bind(regexp_lastindex); - // Omit ToLength if lastindex is a non-negative smi. - Label call_tolength(this, Label::kDeferred), next(this); - Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength); + if (is_fastpath) { + // ToLength on a positive smi is a nop and can be skipped. + CSA_ASSERT(this, TaggedIsPositiveSmi(regexp_lastindex)); + } else { + // Omit ToLength if lastindex is a non-negative smi. + Label call_tolength(this, Label::kDeferred), next(this); + Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength); - Bind(&call_tolength); - { - var_lastindex.Bind( - CallBuiltin(Builtins::kToLength, context, regexp_lastindex)); - Goto(&next); + Bind(&call_tolength); + { + var_lastindex.Bind( + CallBuiltin(Builtins::kToLength, context, regexp_lastindex)); + Goto(&next); + } + + Bind(&next); } - - Bind(&next); } // Check whether the regexp is global or sticky, which determines whether we @@ -658,7 +665,12 @@ Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver( return var_value_map.value(); } -Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) { +Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context, + Node* const object, + Node* const map) { + Label out(this); + Variable var_result(this, MachineRepresentation::kWord32); + Node* const native_context = LoadNativeContext(context); Node* const regexp_fun = LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); @@ -666,17 +678,33 @@ Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) { LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); Node* const has_initialmap = WordEqual(map, initial_map); - return has_initialmap; + var_result.Bind(has_initialmap); + GotoIfNot(has_initialmap, &out); + + // The smi check is required to omit ToLength(lastIndex) calls with possible + // user-code execution on the fast path. + Node* const last_index = FastLoadLastIndex(object); + var_result.Bind(TaggedIsPositiveSmi(last_index)); + Goto(&out); + + Bind(&out); + return var_result.value(); } // RegExp fast path implementations rely on unmodified JSRegExp instances. // We use a fairly coarse granularity for this and simply check whether both -// the regexp itself is unmodified (i.e. its map has not changed) and its -// prototype is unmodified. +// the regexp itself is unmodified (i.e. its map has not changed), its +// prototype is unmodified, and lastIndex is a non-negative smi. void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context, + Node* const object, Node* const map, Label* const if_isunmodified, Label* const if_ismodified) { + CSA_ASSERT(this, WordEqual(LoadMap(object), map)); + + // TODO(ishell): Update this check once map changes for constant field + // tracking are landing. + Node* const native_context = LoadNativeContext(context); Node* const regexp_fun = LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); @@ -692,18 +720,21 @@ void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context, Node* const proto_has_initialmap = WordEqual(proto_map, initial_proto_initial_map); - // TODO(ishell): Update this check once map changes for constant field - // tracking are landing. + GotoIfNot(proto_has_initialmap, if_ismodified); - Branch(proto_has_initialmap, if_isunmodified, if_ismodified); + // The smi check is required to omit ToLength(lastIndex) calls with possible + // user-code execution on the fast path. + Node* const last_index = FastLoadLastIndex(object); + Branch(TaggedIsPositiveSmi(last_index), if_isunmodified, if_ismodified); } -Node* RegExpBuiltinsAssembler::IsFastRegExpMap(Node* const context, - Node* const map) { +Node* RegExpBuiltinsAssembler::IsFastRegExp(Node* const context, + Node* const object, + Node* const map) { Label yup(this), nope(this), out(this); Variable var_result(this, MachineRepresentation::kWord32); - BranchIfFastRegExp(context, map, &yup, &nope); + BranchIfFastRegExp(context, object, map, &yup, &nope); Bind(&yup); var_result.Bind(Int32Constant(1)); @@ -753,7 +784,7 @@ TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) { Node* const string = ToString(context, maybe_string); Label if_isfastpath(this), if_isslowpath(this); - Branch(IsInitialRegExpMap(context, regexp_map), &if_isfastpath, + Branch(IsFastRegExpNoPrototype(context, receiver, regexp_map), &if_isfastpath, &if_isslowpath); Bind(&if_isfastpath); @@ -960,7 +991,8 @@ TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) { Node* const receiver = maybe_receiver; Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred); - Branch(IsInitialRegExpMap(context, map), &if_isfastpath, &if_isslowpath); + Branch(IsFastRegExpNoPrototype(context, receiver, map), &if_isfastpath, + &if_isslowpath); Bind(&if_isfastpath); Return(FlagsGetter(context, receiver, true)); @@ -1405,8 +1437,6 @@ TF_BUILTIN(RegExpPrototypeUnicodeGetter, RegExpBuiltinsAssembler) { // ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S ) Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp, Node* string) { - CSA_ASSERT(this, Word32BinaryNot(IsFastRegExpMap(context, LoadMap(regexp)))); - Variable var_result(this, MachineRepresentation::kTagged); Label out(this); @@ -1471,7 +1501,7 @@ TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) { Node* const string = ToString(context, maybe_string); Label fast_path(this), slow_path(this); - BranchIfFastRegExp(context, map, &fast_path, &slow_path); + BranchIfFastRegExp(context, receiver, map, &fast_path, &slow_path); Bind(&fast_path); { @@ -1800,12 +1830,24 @@ void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context, Node* const match_length = LoadStringLength(match); GotoIfNot(SmiEqual(match_length, smi_zero), &loop); - Node* const last_index = - CallBuiltin(Builtins::kToLength, context, - LoadLastIndex(context, regexp, is_fastpath)); + Node* last_index = LoadLastIndex(context, regexp, is_fastpath); + if (is_fastpath) { + CSA_ASSERT(this, TaggedIsPositiveSmi(last_index)); + } else { + last_index = CallBuiltin(Builtins::kToLength, context, last_index); + } + Node* const new_last_index = AdvanceStringIndex(string, last_index, is_unicode); + if (is_fastpath) { + // On the fast path, we can be certain that lastIndex can never be + // incremented to overflow the Smi range since the maximal string + // length is less than the maximal Smi value. + STATIC_ASSERT(String::kMaxLength < Smi::kMaxValue); + CSA_ASSERT(this, TaggedIsPositiveSmi(new_last_index)); + } + StoreLastIndex(context, regexp, new_last_index, is_fastpath); Goto(&loop); @@ -1839,7 +1881,7 @@ TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) { Node* const string = ToString(context, maybe_string); Label fast_path(this), slow_path(this); - BranchIfFastRegExp(context, map, &fast_path, &slow_path); + BranchIfFastRegExp(context, receiver, map, &fast_path, &slow_path); Bind(&fast_path); RegExpPrototypeMatchBody(context, receiver, string, true); @@ -1961,7 +2003,7 @@ TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) { Node* const string = ToString(context, maybe_string); Label fast_path(this), slow_path(this); - BranchIfFastRegExp(context, map, &fast_path, &slow_path); + BranchIfFastRegExp(context, receiver, map, &fast_path, &slow_path); Bind(&fast_path); RegExpPrototypeSearchBodyFast(context, receiver, string); @@ -2219,7 +2261,7 @@ TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) { Node* const maybe_limit = Parameter(Descriptor::kLimit); Node* const context = Parameter(Descriptor::kContext); - CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp))); + CSA_ASSERT(this, IsFastRegExp(context, regexp, LoadMap(regexp))); CSA_ASSERT(this, IsString(string)); // TODO(jgruber): Even if map checks send us to the fast path, we still need @@ -2273,7 +2315,7 @@ TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) { Node* const string = ToString(context, maybe_string); Label stub(this), runtime(this, Label::kDeferred); - BranchIfFastRegExp(context, map, &stub, &runtime); + BranchIfFastRegExp(context, receiver, map, &stub, &runtime); Bind(&stub); Return(CallBuiltin(Builtins::kRegExpSplit, context, receiver, string, @@ -2599,7 +2641,7 @@ TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) { Node* const replace_value = Parameter(Descriptor::kReplaceValue); Node* const context = Parameter(Descriptor::kContext); - CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp))); + CSA_ASSERT(this, IsFastRegExp(context, regexp, LoadMap(regexp))); CSA_ASSERT(this, IsString(string)); Label checkreplacestring(this), if_iscallable(this), @@ -2688,7 +2730,7 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) { // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance? Label stub(this), runtime(this, Label::kDeferred); - BranchIfFastRegExp(context, map, &stub, &runtime); + BranchIfFastRegExp(context, receiver, map, &stub, &runtime); Bind(&stub); Return(CallBuiltin(Builtins::kRegExpReplace, context, receiver, string, diff --git a/src/builtins/builtins-regexp-gen.h b/src/builtins/builtins-regexp-gen.h index 440cc6b7f5..7c818e750a 100644 --- a/src/builtins/builtins-regexp-gen.h +++ b/src/builtins/builtins-regexp-gen.h @@ -15,8 +15,8 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler { explicit RegExpBuiltinsAssembler(compiler::CodeAssemblerState* state) : CodeStubAssembler(state) {} - void BranchIfFastRegExp(Node* const context, Node* const map, - Label* const if_isunmodified, + void BranchIfFastRegExp(Node* const context, Node* const object, + Node* const map, Label* const if_isunmodified, Label* const if_ismodified); protected: @@ -58,9 +58,13 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler { char const* method_name); // Analogous to BranchIfFastRegExp, for use in asserts. - Node* IsFastRegExpMap(Node* const context, Node* const map); + Node* IsFastRegExp(Node* const context, Node* const object, Node* const map); + + // Performs fast path checks on the given object itself, but omits prototype + // checks. + Node* IsFastRegExpNoPrototype(Node* const context, Node* const object, + Node* const map); - Node* IsInitialRegExpMap(Node* context, Node* map); void BranchIfFastRegExpResult(Node* context, Node* map, Label* if_isunmodified, Label* if_ismodified); diff --git a/src/builtins/builtins-string-gen.cc b/src/builtins/builtins-string-gen.cc index 9100739ea1..fcb732b883 100644 --- a/src/builtins/builtins-string-gen.cc +++ b/src/builtins/builtins-string-gen.cc @@ -969,7 +969,7 @@ void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol( Label stub_call(this), slow_lookup(this); RegExpBuiltinsAssembler regexp_asm(state()); - regexp_asm.BranchIfFastRegExp(context, object_map, &stub_call, + regexp_asm.BranchIfFastRegExp(context, object, object_map, &stub_call, &slow_lookup); Bind(&stub_call); diff --git a/src/regexp/regexp-utils.cc b/src/regexp/regexp-utils.cc index d40431866a..570a348f74 100644 --- a/src/regexp/regexp-utils.cc +++ b/src/regexp/regexp-utils.cc @@ -145,7 +145,14 @@ bool RegExpUtils::IsUnmodifiedRegExp(Isolate* isolate, Handle obj) { if (!proto->IsJSReceiver()) return false; Handle initial_proto_initial_map = isolate->regexp_prototype_map(); - return (JSReceiver::cast(proto)->map() == *initial_proto_initial_map); + if (JSReceiver::cast(proto)->map() != *initial_proto_initial_map) { + return false; + } + + // The smi check is required to omit ToLength(lastIndex) calls with possible + // user-code execution on the fast path. + Object* last_index = JSRegExp::cast(recv)->LastIndex(); + return last_index->IsSmi() && Smi::cast(last_index)->value() >= 0; } int RegExpUtils::AdvanceStringIndex(Isolate* isolate, Handle string, diff --git a/test/mjsunit/regress/regress-708247.js b/test/mjsunit/regress/regress-708247.js new file mode 100644 index 0000000000..7512791cd9 --- /dev/null +++ b/test/mjsunit/regress/regress-708247.js @@ -0,0 +1,26 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --predictable + +const str = '2016-01-02'; + +function t() { + var re; + function toDictMode() { + for (var i = 0; i < 100; i++) { // Loop is required. + re.x = 42; + delete re.x; + } + return 0; + } + + re = /-/g; // Needs to be global to trigger lastIndex accesses. + re.lastIndex = { valueOf : toDictMode }; + return re.exec(str); +} + +for (var q = 0; q < 10000; q++) { + t(); // Needs repetitions to trigger a crash. +}