Commit 424ef009 authored by littledan's avatar littledan Committed by Commit bot

Reland of Add web compat workarounds for ES2015 RegExp semantics (patchset #3...

Reland of Add web compat workarounds for ES2015 RegExp semantics (patchset #3 id:40001 of https://codereview.chromium.org/1543723002/ )

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.

This reland replaces problematic legacy const usage with var, to
avoid issues with nosnap builds.

R=yangguo
CC=bmeurer
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/1545633002

Cr-Commit-Position: refs/heads/master@{#33002}
parent f4cd91c7
......@@ -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);
......
......@@ -12,6 +12,7 @@
var FLAG_harmony_tolength;
var GlobalObject = global.Object;
var GlobalRegExp = global.RegExp;
var GlobalRegExpPrototype;
var InternalArray = utils.InternalArray;
var InternalPackedArray = utils.InternalPackedArray;
var MakeTypeError;
......@@ -270,8 +271,17 @@ function TrimRegExp(regexp) {
}
var 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 +501,8 @@ function RegExpGetSource() {
// -------------------------------------------------------------------
%FunctionSetInstanceClassName(GlobalRegExp, 'RegExp');
%FunctionSetPrototype(GlobalRegExp, new GlobalObject());
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