Skip to content

Commit 55a9807

Browse files
schuayCommit Bot
authored andcommitted
[string] Fix regexp fast path in MaybeCallFunctionAtSymbol
The regexp fast path in MaybeCallFunctionAtSymbol had an issue in which we'd call ToString after checking that the given {object} was a fast regexp and deciding to take the fast path. This is invalid since ToString() can call into user-controlled JS and may mutate {object}. There's no way to place the ToString call correctly in this instance: 1 before BranchIfFastRegExp, it's a spec violation if we end up on the slow regexp path; 2 the problem with the current location is already described above; 3 and we can't place it into the fast-path regexp builtin (e.g. RegExpReplace) either due to the same reasons as 1. The solution in this CL is to restrict the fast path to string arguments only, i.e. cases where ToString would be a nop and can safely be skipped. Bug: chromium:782145 Change-Id: Ifd35b3a9a6cf2e77c96cb860a8ec98eaec35aa85 Reviewed-on: https://chromium-review.googlesource.com/758257 Commit-Queue: Jakob Gruber <[email protected]> Reviewed-by: Yang Guo <[email protected]> Cr-Commit-Position: refs/heads/master@{#49213}
1 parent c5a7358 commit 55a9807

File tree

3 files changed

+41
-18
lines changed

3 files changed

+41
-18
lines changed

src/builtins/builtins-string-gen.cc

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,9 +1082,9 @@ void StringBuiltinsAssembler::RequireObjectCoercible(Node* const context,
10821082
}
10831083

10841084
void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
1085-
Node* const context, Node* const object, Handle<Symbol> symbol,
1086-
const NodeFunction0& regexp_call, const NodeFunction1& generic_call,
1087-
CodeStubArguments* args) {
1085+
Node* const context, Node* const object, Node* const maybe_string,
1086+
Handle<Symbol> symbol, const NodeFunction0& regexp_call,
1087+
const NodeFunction1& generic_call, CodeStubArguments* args) {
10881088
Label out(this);
10891089

10901090
// Smis definitely don't have an attached symbol.
@@ -1114,14 +1114,21 @@ void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
11141114
}
11151115

11161116
// Take the fast path for RegExps.
1117+
// There's two conditions: {object} needs to be a fast regexp, and
1118+
// {maybe_string} must be a string (we can't call ToString on the fast path
1119+
// since it may mutate {object}).
11171120
{
11181121
Label stub_call(this), slow_lookup(this);
11191122

1123+
GotoIf(TaggedIsSmi(maybe_string), &slow_lookup);
1124+
GotoIfNot(IsString(maybe_string), &slow_lookup);
1125+
11201126
RegExpBuiltinsAssembler regexp_asm(state());
11211127
regexp_asm.BranchIfFastRegExp(context, object, object_map, &stub_call,
11221128
&slow_lookup);
11231129

11241130
BIND(&stub_call);
1131+
// TODO(jgruber): Add a no-JS scope once it exists.
11251132
Node* const result = regexp_call();
11261133
if (args == nullptr) {
11271134
Return(result);
@@ -1345,12 +1352,10 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
13451352
// Redirect to replacer method if {search[@@replace]} is not undefined.
13461353

13471354
MaybeCallFunctionAtSymbol(
1348-
context, search, isolate()->factory()->replace_symbol(),
1355+
context, search, receiver, isolate()->factory()->replace_symbol(),
13491356
[=]() {
1350-
Node* const subject_string = ToString_Inline(context, receiver);
1351-
1352-
return CallBuiltin(Builtins::kRegExpReplace, context, search,
1353-
subject_string, replace);
1357+
return CallBuiltin(Builtins::kRegExpReplace, context, search, receiver,
1358+
replace);
13541359
},
13551360
[=](Node* fn) {
13561361
Callable call_callable = CodeFactory::Call(isolate());
@@ -1511,11 +1516,8 @@ class StringMatchSearchAssembler : public StringBuiltinsAssembler {
15111516
RequireObjectCoercible(context, receiver, method_name);
15121517

15131518
MaybeCallFunctionAtSymbol(
1514-
context, maybe_regexp, symbol,
1515-
[=] {
1516-
Node* const receiver_string = ToString_Inline(context, receiver);
1517-
return CallBuiltin(builtin, context, maybe_regexp, receiver_string);
1518-
},
1519+
context, maybe_regexp, receiver, symbol,
1520+
[=] { return CallBuiltin(builtin, context, maybe_regexp, receiver); },
15191521
[=](Node* fn) {
15201522
Callable call_callable = CodeFactory::Call(isolate());
15211523
return CallJS(call_callable, context, fn, maybe_regexp, receiver);
@@ -1804,12 +1806,10 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
18041806
// Redirect to splitter method if {separator[@@split]} is not undefined.
18051807

18061808
MaybeCallFunctionAtSymbol(
1807-
context, separator, isolate()->factory()->split_symbol(),
1809+
context, separator, receiver, isolate()->factory()->split_symbol(),
18081810
[=]() {
1809-
Node* const subject_string = ToString_Inline(context, receiver);
1810-
1811-
return CallBuiltin(Builtins::kRegExpSplit, context, separator,
1812-
subject_string, limit);
1811+
return CallBuiltin(Builtins::kRegExpSplit, context, separator, receiver,
1812+
limit);
18131813
},
18141814
[=](Node* fn) {
18151815
Callable call_callable = CodeFactory::Call(isolate());

src/builtins/builtins-string-gen.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,11 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
8484
// }
8585
//
8686
// Contains fast paths for Smi and RegExp objects.
87+
// Important: {regexp_call} may not contain any code that can call into JS.
8788
typedef std::function<Node*()> NodeFunction0;
8889
typedef std::function<Node*(Node* fn)> NodeFunction1;
8990
void MaybeCallFunctionAtSymbol(Node* const context, Node* const object,
91+
Node* const maybe_string,
9092
Handle<Symbol> symbol,
9193
const NodeFunction0& regexp_call,
9294
const NodeFunction1& generic_call,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2017 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
function newFastRegExp() { return new RegExp('.'); }
6+
function toSlowRegExp(re) { re.exec = 42; }
7+
8+
let re = newFastRegExp();
9+
const evil_nonstring = { [Symbol.toPrimitive]: () => toSlowRegExp(re) };
10+
const empty_string = "";
11+
12+
String.prototype.replace.call(evil_nonstring, re, empty_string);
13+
14+
re = newFastRegExp();
15+
String.prototype.match.call(evil_nonstring, re, empty_string);
16+
17+
re = newFastRegExp();
18+
String.prototype.search.call(evil_nonstring, re, empty_string);
19+
20+
re = newFastRegExp();
21+
String.prototype.split.call(evil_nonstring, re, empty_string);

0 commit comments

Comments
 (0)