Further ES2015 RegExp spec compliance fixes

- RegExp.prototype.toString() doesn't have any special handling of
  RegExp instances and simply calls the source and flags getters
- Use the original values of global and sticky, rather than based
  on the current flag getters, as specified in
  https://github.com/tc39/ecma262/pull/494

R=yangguo@chromium.org,adamk
LOG=Y
BUG=v8:4602

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

Cr-Commit-Position: refs/heads/master@{#35225}
This commit is contained in:
littledan 2016-04-04 00:05:47 -07:00 committed by Commit bot
parent 42f2261cfe
commit 277f5bd0a3
8 changed files with 69 additions and 40 deletions

View File

@ -194,8 +194,8 @@ function RegExpSubclassExecJS(string) {
// algorithm, step 4) even if the value is discarded for non-global RegExps.
var i = TO_LENGTH(lastIndex);
var global = TO_BOOLEAN(this.global);
var sticky = TO_BOOLEAN(this.sticky);
var global = TO_BOOLEAN(REGEXP_GLOBAL(this));
var sticky = TO_BOOLEAN(REGEXP_STICKY(this));
var updateLastIndex = global || sticky;
if (updateLastIndex) {
if (i > string.length) {
@ -370,27 +370,14 @@ function TrimRegExp(regexp) {
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 '/(?:)/';
}
if (!IS_RECEIVER(this)) {
throw MakeTypeError(
kIncompatibleMethodReceiver, 'RegExp.prototype.toString', this);
}
return '/' + TO_STRING(this.source) + '/' + TO_STRING(this.flags);
if (!IS_RECEIVER(this)) {
throw MakeTypeError(
kIncompatibleMethodReceiver, 'RegExp.prototype.toString', this);
}
var result = '/' + REGEXP_SOURCE(this) + '/';
if (REGEXP_GLOBAL(this)) result += 'g';
if (REGEXP_IGNORE_CASE(this)) result += 'i';
if (REGEXP_MULTILINE(this)) result += 'm';
if (REGEXP_UNICODE(this)) result += 'u';
if (REGEXP_STICKY(this)) result += 'y';
return result;
if (this === GlobalRegExpPrototype) {
%IncrementUseCounter(kRegExpPrototypeToString);
}
return '/' + TO_STRING(this.source) + '/' + TO_STRING(this.flags);
}
@ -1126,7 +1113,7 @@ function RegExpGetSource() {
// TODO(littledan): Remove this RegExp compat workaround
if (this === GlobalRegExpPrototype) {
%IncrementUseCounter(kRegExpPrototypeSourceGetter);
return UNDEFINED;
return "(?:)";
}
throw MakeTypeError(kRegExpNonRegExp, "RegExp.prototype.source");
}

View File

@ -1949,7 +1949,7 @@ TEST(UseCountRegExp) {
// 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(2, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeToString]);
CHECK(resultToString->IsInt32());
CHECK_EQ(6,
@ -1957,7 +1957,7 @@ TEST(UseCountRegExp) {
// .toString() works on normal RegExps
v8::Local<v8::Value> resultReToString = CompileRun("/a/.toString().length");
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(2, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeToString]);
CHECK(resultReToString->IsInt32());
CHECK_EQ(
@ -1969,7 +1969,7 @@ TEST(UseCountRegExp) {
"try { RegExp.prototype.toString.call(null) }"
"catch (e) { exception = e; }"
"exception");
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(2, use_counts[v8::Isolate::kRegExpPrototypeStickyGetter]);
CHECK_EQ(1, use_counts[v8::Isolate::kRegExpPrototypeToString]);
CHECK(resultToStringError->IsObject());
}

View File

@ -919,7 +919,13 @@ function TestMapSetSubclassing(container, is_map) {
var o = Reflect.construct(RegExp, [pattern], f);
assertEquals(["match", "tostring"], log);
assertEquals(/biep/, o);
// TODO(littledan): Is the RegExp constructor correct to create
// the internal slots and do these type checks this way?
assertEquals("biep", %_RegExpSource(o));
assertThrows(() => Object.getOwnPropertyDescriptor(RegExp.prototype,
'source').get(o),
TypeError);
assertEquals("/undefined/undefined", RegExp.prototype.toString.call(o));
assertTrue(o.__proto__ === p2);
assertTrue(f.prototype === p3);
})();

View File

@ -21,32 +21,47 @@ function should_not_be_called() {
})();
(function() {
let allow = false;
class A extends RegExp {
get source() { throw new Error("should not be called") }
get flags() { throw new Error("should not be called") }
get source() {
if (!allow) throw new Error("should not be called");
return super.source;
}
get flags() {
if (!allow) throw new Error("should not be called");
return super.flags
}
}
var r = new A("biep");
var r2 = RegExp(r);
assertFalse(r === r2);
allow = true;
assertEquals(r, r2);
allow = false;
assertTrue(A.prototype === r.__proto__);
assertTrue(RegExp.prototype === r2.__proto__);
var r3 = RegExp(r);
assertFalse(r3 === r);
allow = true;
assertEquals(r3, r);
allow = false;
var r4 = new A(r2);
assertFalse(r4 === r2);
allow = true;
assertEquals(r4, r2);
allow = false;
assertTrue(A.prototype === r4.__proto__);
r[Symbol.match] = false;
var r5 = new A(r);
assertFalse(r5 === r);
allow = true;
assertEquals(r5, r);
allow = false;
assertTrue(A.prototype === r5.__proto__);
})();

View File

@ -50,9 +50,10 @@ assertEquals(4, get_count);
function testName(name) {
// TODO(littledan): For web compatibility, we don't throw an exception,
// but ES2015 expects an exception to be thrown from this getter.
assertEquals(undefined, RegExp.prototype[name]);
// Test for ES2017 RegExp web compatibility semantics
// https://github.com/tc39/ecma262/pull/511
assertEquals(name === "source" ? "(?:)" : undefined,
RegExp.prototype[name]);
assertEquals(
"get " + name,
Object.getOwnPropertyDescriptor(RegExp.prototype, name).get.name);

View File

@ -44,3 +44,14 @@ testThrows(1);
assertEquals("/pattern/flags", RegExp.prototype.toString.call(fake));
assertEquals(["p", "ps", "f", "fs"], log);
// Monkey-patching is also possible on RegExp instances
let weird = /foo/;
Object.defineProperty(weird, 'flags', {value: 'bar'});
Object.defineProperty(weird, 'source', {value: 'baz'});
assertEquals('/baz/bar', weird.toString());
assertEquals('/(?:)/', RegExp.prototype.toString());
assertEquals('(?:)', RegExp.prototype.source);
assertEquals('', RegExp.prototype.flags);

View File

@ -100,20 +100,32 @@
'language/asi/S7.9_A5.7_T1': [PASS, FAIL_OK],
###### BEGIN REGEXP SUBCLASSING SECTION ######
# https://code.google.com/p/v8/issues/detail?id=4602
# Fails due to mismatching [[OriginalFlags]] and getters
'built-ins/RegExp/prototype/exec/get-sticky-coerce': [FAIL],
# Spec change in progress https://github.com/tc39/ecma262/pull/494
# RegExpBuiltinMatch reads flags from [[OriginalFlags]]
'built-ins/RegExp/prototype/Symbol.match/builtin-coerce-sticky': [FAIL],
'built-ins/RegExp/prototype/Symbol.match/builtin-get-global-err': [FAIL],
'built-ins/RegExp/prototype/Symbol.match/builtin-get-sticky-err': [FAIL],
'built-ins/RegExp/prototype/Symbol.match/builtin-success-g-set-lastindex': [FAIL],
'built-ins/RegExp/prototype/Symbol.match/builtin-success-g-set-lastindex-err': [FAIL],
'built-ins/RegExp/prototype/Symbol.match/coerce-sticky': [FAIL],
'built-ins/RegExp/prototype/Symbol.replace/get-sticky-coerce': [FAIL],
'built-ins/RegExp/prototype/Symbol.match/get-sticky-err': [FAIL],
'built-ins/RegExp/prototype/Symbol.replace/coerce-global': [FAIL],
'built-ins/RegExp/prototype/Symbol.replace/coerce-unicode': [FAIL],
'built-ins/RegExp/prototype/Symbol.replace/get-sticky-coerce': [FAIL],
'built-ins/RegExp/prototype/Symbol.replace/get-sticky-err': [SKIP],
'built-ins/RegExp/prototype/Symbol.search/get-sticky-coerce': [FAIL],
'built-ins/RegExp/prototype/Symbol.search/get-sticky-err': [FAIL],
'built-ins/RegExp/prototype/exec/get-sticky-coerce': [FAIL],
'built-ins/RegExp/prototype/exec/get-sticky-err': [FAIL],
'built-ins/RegExp/prototype/test/get-sticky-err': [FAIL],
# Missing lastIndex support
'built-ins/RegExp/prototype/Symbol.split/str-result-coerce-length-err': [FAIL],
# Times out
'built-ins/RegExp/prototype/Symbol.split/str-coerce-lastindex': [SKIP],
'built-ins/RegExp/prototype/Symbol.match/coerce-global': [SKIP],
'built-ins/RegExp/prototype/Symbol.match/builtin-coerce-global': [SKIP],
# Sticky support busted
'built-ins/RegExp/prototype/Symbol.replace/y-init-lastindex': [FAIL],
@ -123,9 +135,6 @@
# happens to be thrown for some other reason.
'built-ins/RegExp/prototype/Symbol.split/str-result-get-length-err': [SKIP],
# Spec change in progress: https://github.com/tc39/ecma262/issues/489
'built-ins/RegExp/prototype/Symbol.replace/get-sticky-err': [SKIP],
#
###### END REGEXP SUBCLASSING SECTION ######
# https://code.google.com/p/v8/issues/detail?id=4360

View File

@ -28,7 +28,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
PASS RegExp('/').source is "\\/"
PASS RegExp('').source is "(?:)"
FAIL RegExp.prototype.source should be (?:) (of type string). Was undefined (of type undefined).
PASS RegExp.prototype.source is "(?:)"
PASS RegExp('/').toString() is "/\\//"
PASS RegExp('').toString() is "/(?:)/"
PASS RegExp.prototype.toString() is "/(?:)/"