Skip to content

Commit cfc3404

Browse files
schuayCommit Bot
authored andcommitted
Merged: [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. NOTRY=true NOPRESUBMIT=true NOTREECHECKS=true [email protected] Bug: chromium:782145 Change-Id: Ifd35b3a9a6cf2e77c96cb860a8ec98eaec35aa85 Reviewed-on: https://chromium-review.googlesource.com/763207 Reviewed-by: Jakob Gruber <[email protected]> Commit-Queue: Jakob Gruber <[email protected]> Cr-Commit-Position: refs/branch-heads/6.2@{#88} Cr-Branched-From: efa2ac4-refs/heads/6.2.414@{#1} Cr-Branched-From: a861ebb-refs/heads/master@{#47693}
1 parent c01178e commit cfc3404

3 files changed

Lines changed: 39 additions & 13 deletions

File tree

src/builtins/builtins-string-gen.cc

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,9 +1051,9 @@ void StringBuiltinsAssembler::RequireObjectCoercible(Node* const context,
10511051
}
10521052

10531053
void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
1054-
Node* const context, Node* const object, Handle<Symbol> symbol,
1055-
const NodeFunction0& regexp_call, const NodeFunction1& generic_call,
1056-
CodeStubArguments* args) {
1054+
Node* const context, Node* const object, Node* const maybe_string,
1055+
Handle<Symbol> symbol, const NodeFunction0& regexp_call,
1056+
const NodeFunction1& generic_call, CodeStubArguments* args) {
10571057
Label out(this);
10581058

10591059
// Smis definitely don't have an attached symbol.
@@ -1083,14 +1083,21 @@ void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
10831083
}
10841084

10851085
// Take the fast path for RegExps.
1086+
// There's two conditions: {object} needs to be a fast regexp, and
1087+
// {maybe_string} must be a string (we can't call ToString on the fast path
1088+
// since it may mutate {object}).
10861089
{
10871090
Label stub_call(this), slow_lookup(this);
10881091

1092+
GotoIf(TaggedIsSmi(maybe_string), &slow_lookup);
1093+
GotoIfNot(IsString(maybe_string), &slow_lookup);
1094+
10891095
RegExpBuiltinsAssembler regexp_asm(state());
10901096
regexp_asm.BranchIfFastRegExp(context, object, object_map, &stub_call,
10911097
&slow_lookup);
10921098

10931099
BIND(&stub_call);
1100+
// TODO(jgruber): Add a no-JS scope once it exists.
10941101
Node* const result = regexp_call();
10951102
if (args == nullptr) {
10961103
Return(result);
@@ -1196,12 +1203,10 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
11961203
// Redirect to replacer method if {search[@@replace]} is not undefined.
11971204

11981205
MaybeCallFunctionAtSymbol(
1199-
context, search, isolate()->factory()->replace_symbol(),
1206+
context, search, receiver, isolate()->factory()->replace_symbol(),
12001207
[=]() {
1201-
Node* const subject_string = ToString_Inline(context, receiver);
1202-
1203-
return CallBuiltin(Builtins::kRegExpReplace, context, search,
1204-
subject_string, replace);
1208+
return CallBuiltin(Builtins::kRegExpReplace, context, search, receiver,
1209+
replace);
12051210
},
12061211
[=](Node* fn) {
12071212
Callable call_callable = CodeFactory::Call(isolate());
@@ -1439,12 +1444,10 @@ TF_BUILTIN(StringPrototypeSplit, StringBuiltinsAssembler) {
14391444
// Redirect to splitter method if {separator[@@split]} is not undefined.
14401445

14411446
MaybeCallFunctionAtSymbol(
1442-
context, separator, isolate()->factory()->split_symbol(),
1447+
context, separator, receiver, isolate()->factory()->split_symbol(),
14431448
[=]() {
1444-
Node* const subject_string = ToString_Inline(context, receiver);
1445-
1446-
return CallBuiltin(Builtins::kRegExpSplit, context, separator,
1447-
subject_string, limit);
1449+
return CallBuiltin(Builtins::kRegExpSplit, context, separator, receiver,
1450+
limit);
14481451
},
14491452
[=](Node* fn) {
14501453
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
@@ -82,9 +82,11 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
8282
// }
8383
//
8484
// Contains fast paths for Smi and RegExp objects.
85+
// Important: {regexp_call} may not contain any code that can call into JS.
8586
typedef std::function<Node*()> NodeFunction0;
8687
typedef std::function<Node*(Node* fn)> NodeFunction1;
8788
void MaybeCallFunctionAtSymbol(Node* const context, Node* const object,
89+
Node* const maybe_string,
8890
Handle<Symbol> symbol,
8991
const NodeFunction0& regexp_call,
9092
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)