Commit 98f819c3 authored by littledan's avatar littledan Committed by Commit bot

Add web compat workarounds for ES2015 RegExp semantics

Unexpectedly, websites depend on doing feature testing with
RegExp.prototype.sticky and browser testing with RegExp.prototype.toString().
ES2015 newly throws exceptions for both of these. In order to enable shipping
new ES2015 semantics, this patch puts in narrow workarounds for those two
cases, keeping their old behavior. UseCounters are added for how often
those particular cases come up, so we can see if it can be deprecated.

R=yangguo
BUG=v8:4637,v8:4617
LOG=Y
CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:linux_chromium_rel_ng;tryserver.blink:linux_blink_rel

Review URL: https://codereview.chromium.org/1543723002

Cr-Commit-Position: refs/heads/master@{#32997}
parent 4acca53e
......@@ -5411,6 +5411,8 @@ class V8_EXPORT Isolate {
kSloppyMode = 8,
kStrictMode = 9,
kStrongMode = 10,
kRegExpPrototypeStickyGetter = 11,
kRegExpPrototypeToString = 12,
kUseCounterFeatureCount // This enum value must be last.
};
......
......@@ -12,6 +12,7 @@
// Imports
var GlobalRegExp = global.RegExp;
var GlobalRegExpPrototype = GlobalRegExp.prototype;
var MakeTypeError;
var regExpFlagsSymbol = utils.ImportNow("regexp_flags_symbol");
......@@ -37,10 +38,17 @@ function RegExpGetFlags() {
return result;
}
const kRegExpPrototypeStickyGetter = 11;
// ES6 21.2.5.12.
function RegExpGetSticky() {
if (!IS_REGEXP(this)) {
// Compat fix: RegExp.prototype.sticky == undefined; UseCounter tracks it
// TODO(littledan): Remove this workaround or standardize it
if (this === GlobalRegExpPrototype) {
%IncrementUseCounter(kRegExpPrototypeStickyGetter);
return UNDEFINED;
}
throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.sticky");
}
return !!REGEXP_STICKY(this);
......
......@@ -270,8 +270,17 @@ function TrimRegExp(regexp) {
}
const kRegExpPrototypeToString = 12;
function RegExpToString() {
if (!IS_REGEXP(this)) {
// RegExp.prototype.toString() returns '/(?:)/' as a compatibility fix;
// a UseCounter is incremented to track it.
// TODO(littledan): Remove this workaround or standardize it
if (this === GlobalRegExpPrototype) {
%IncrementUseCounter(kRegExpPrototypeToString);
return '/(?:)/';
}
throw MakeTypeError(kIncompatibleMethodReceiver,
'RegExp.prototype.toString', this);
}
......@@ -491,7 +500,8 @@ function RegExpGetSource() {
// -------------------------------------------------------------------
%FunctionSetInstanceClassName(GlobalRegExp, 'RegExp');
%FunctionSetPrototype(GlobalRegExp, new GlobalObject());
const GlobalRegExpPrototype = new GlobalObject();
%FunctionSetPrototype(GlobalRegExp, GlobalRegExpPrototype);
%AddNamedProperty(
GlobalRegExp.prototype, 'constructor', GlobalRegExp, DONT_ENUM);
%SetCode(GlobalRegExp, RegExpConstructor);
......
......@@ -460,5 +460,14 @@ RUNTIME_FUNCTION(Runtime_CreateListFromArrayLike) {
return *result;
}
RUNTIME_FUNCTION(Runtime_IncrementUseCounter) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_SMI_ARG_CHECKED(counter, 0);
isolate->CountUsage(static_cast<v8::Isolate::UseCounterFeature>(counter));
return isolate->heap()->undefined_value();
}
} // namespace internal
} // namespace v8
......@@ -346,7 +346,8 @@ namespace internal {
F(IncrementStatsCounter, 1, 1) \
F(ThrowConstructedNonConstructable, 1, 1) \
F(ThrowCalledNonCallable, 1, 1) \
F(CreateListFromArrayLike, 1, 1)
F(CreateListFromArrayLike, 1, 1) \
F(IncrementUseCounter, 1, 1)
#define FOR_EACH_INTRINSIC_JSON(F) \
......
......@@ -28,6 +28,7 @@
#include <cstdlib>
#include <sstream>
#include "include/v8.h"
#include "src/v8.h"
#include "src/ast/ast.h"
......@@ -1847,3 +1848,82 @@ TEST(CharacterRangeMerge) {
TEST(Graph) {
Execute("\\b\\w+\\b", false, true, true);
}
namespace {
int* global_use_counts = NULL;
void MockUseCounterCallback(v8::Isolate* isolate,
v8::Isolate::UseCounterFeature feature) {
++global_use_counts[feature];
}
}
// Test that ES2015 RegExp compatibility fixes are in place, that they
// are not overly broad, and the appropriate UseCounters are incremented
TEST(UseCountRegExp) {
i::FLAG_harmony_regexps = true;
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
LocalContext env;
int use_counts[v8::Isolate::kUseCounterFeatureCount] = {};
global_use_counts = use_counts;
CcTest::isolate()->SetUseCounterCallback(MockUseCounterCallback);
// Compat fix: RegExp.prototype.sticky == undefined; UseCounter tracks it
v8::Local<v8::Value> resultSticky = CompileRun("RegExp.prototype.sticky");
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(0, use_counts[v8::Isolate::kRegExpPrototypeToString]);
CHECK(resultSticky->IsUndefined());
// re.sticky has approriate value and doesn't touch UseCounter
v8::Local<v8::Value> resultReSticky = CompileRun("/a/.sticky");
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(0, use_counts[v8::Isolate::kRegExpPrototypeToString]);
CHECK(resultReSticky->IsFalse());
// When the getter is caleld on another object, throw an exception
// and don't increment the UseCounter
v8::Local<v8::Value> resultStickyError = CompileRun(
"var exception;"
"try { "
" Object.getOwnPropertyDescriptor(RegExp.prototype, 'sticky')"
" .get.call(null);"
"} catch (e) {"
" exception = e;"
"}"
"exception");
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(0, use_counts[v8::Isolate::kRegExpPrototypeToString]);
CHECK(resultStickyError->IsObject());
// RegExp.prototype.toString() returns '/(?:)/' as a compatibility fix;
// a UseCounter is incremented to track it.
v8::Local<v8::Value> resultToString =
CompileRun("RegExp.prototype.toString().length");
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeToString]);
CHECK(resultToString->IsInt32());
CHECK_EQ(6,
resultToString->Int32Value(isolate->GetCurrentContext()).FromJust());
// .toString() works on normal RegExps
v8::Local<v8::Value> resultReToString = CompileRun("/a/.toString().length");
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeToString]);
CHECK(resultReToString->IsInt32());
CHECK_EQ(
3, resultReToString->Int32Value(isolate->GetCurrentContext()).FromJust());
// .toString() throws on non-RegExps that aren't RegExp.prototype
v8::Local<v8::Value> resultToStringError = CompileRun(
"var exception;"
"try { RegExp.prototype.toString.call(null) }"
"catch (e) { exception = e; }"
"exception");
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeToString]);
CHECK(resultToStringError->IsObject());
}
......@@ -50,7 +50,11 @@ assertEquals(4, get_count);
function testName(name) {
assertThrows(() => RegExp.prototype[name], TypeError);
if (name === "sticky") {
assertEquals(undefined, RegExp.prototype[name]);
} else {
assertThrows(() => RegExp.prototype[name], TypeError);
}
assertEquals(
"get " + name,
Object.getOwnPropertyDescriptor(RegExp.prototype, name).get.name);
......
......@@ -114,7 +114,7 @@ PASS str.match(/d/gi).toString() is 'D,d'
PASS /\u0061/.source is '\\u0061'
PASS 'abc'.match(/\u0062/).toString() is 'b'
FAIL Object.prototype.toString.apply(RegExp.prototype) should be [object RegExp]. Was [object Object].
FAIL typeof RegExp.prototype.toString() should be string. Threw exception TypeError: Method RegExp.prototype.toString called on incompatible receiver [object Object]
PASS typeof RegExp.prototype.toString() is 'string'
PASS new RegExp().toString() is '/(?:)/'
PASS (new RegExp('(?:)')).source is '(?:)'
PASS /(?:)/.toString() is '/(?:)/'
......
......@@ -31,7 +31,7 @@ PASS RegExp('').source is "(?:)"
FAIL RegExp.prototype.source should be (?:). Threw exception TypeError: RegExp.prototype.source getter called on non-RegExp object
PASS RegExp('/').toString() is "/\\//"
PASS RegExp('').toString() is "/(?:)/"
FAIL RegExp.prototype.toString() should be /(?:)/. Threw exception TypeError: Method RegExp.prototype.toString called on incompatible receiver [object Object]
PASS RegExp.prototype.toString() is "/(?:)/"
PASS testForwardSlash("^/$", "/"); is true
PASS testForwardSlash("^/$", "/"); is true
PASS testForwardSlash("^\/$", "/"); is true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment