summaryrefslogtreecommitdiffstats
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/src/builtin/Eval.cpp48
-rw-r--r--js/src/builtin/IntlTimeZoneData.h4
-rw-r--r--js/src/builtin/RegExp.js21
-rw-r--r--js/src/builtin/String.js109
-rw-r--r--js/src/frontend/TokenStream.cpp150
-rw-r--r--js/src/jit-test/tests/latin1/json.js11
-rw-r--r--js/src/jit/InlinableNatives.h1
-rw-r--r--js/src/jit/IonBuilder.h1
-rw-r--r--js/src/jit/MCallOptimize.cpp40
-rw-r--r--js/src/jit/x86-shared/Constants-x86-shared.h1
-rw-r--r--js/src/js.msg3
-rw-r--r--js/src/jsnum.cpp67
-rw-r--r--js/src/jsnum.h34
-rw-r--r--js/src/json.cpp228
-rw-r--r--js/src/jsstr.cpp261
-rw-r--r--js/src/jsstr.h4
-rw-r--r--js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js3
-rw-r--r--js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js3
-rw-r--r--js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js2
-rw-r--r--js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js2
-rw-r--r--js/src/tests/ecma_2021/manual/test-replaceall.html63
-rw-r--r--js/src/tests/ecma_5/JSON/stringify.js3
-rw-r--r--js/src/tests/ecma_5/eval/line-terminator-paragraph-terminator.js59
-rw-r--r--js/src/vm/JSONParser.cpp4
-rw-r--r--js/src/vm/SelfHosting.cpp173
-rw-r--r--js/src/vm/StringBuffer.h15
-rw-r--r--js/src/vm/Unicode.h13
27 files changed, 956 insertions, 367 deletions
diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp
index 4ee7a35c8..53fa78931 100644
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -141,35 +141,29 @@ template <typename CharT>
static bool
EvalStringMightBeJSON(const mozilla::Range<const CharT> chars)
{
- // If the eval string starts with '(' or '[' and ends with ')' or ']', it may be JSON.
- // Try the JSON parser first because it's much faster. If the eval string
- // isn't JSON, JSON parsing will probably fail quickly, so little time
- // will be lost.
+ // If the eval string starts with '(' or '[' and ends with ')' or ']', it
+ // may be JSON. Try the JSON parser first because it's much faster. If
+ // the eval string isn't JSON, JSON parsing will probably fail quickly, so
+ // little time will be lost.
size_t length = chars.length();
- if (length > 2 &&
- ((chars[0] == '[' && chars[length - 1] == ']') ||
- (chars[0] == '(' && chars[length - 1] == ')')))
- {
- // Remarkably, JavaScript syntax is not a superset of JSON syntax:
- // strings in JavaScript cannot contain the Unicode line and paragraph
- // terminator characters U+2028 and U+2029, but strings in JSON can.
- // Rather than force the JSON parser to handle this quirk when used by
- // eval, we simply don't use the JSON parser when either character
- // appears in the provided string. See bug 657367.
- if (sizeof(CharT) > 1) {
- for (RangedPtr<const CharT> cp = chars.begin() + 1, end = chars.end() - 1;
- cp < end;
- cp++)
- {
- char16_t c = *cp;
- if (c == 0x2028 || c == 0x2029)
- return false;
- }
- }
+ if (length < 2)
+ return false;
- return true;
- }
- return false;
+ // It used to be that strings in JavaScript forbid U+2028 LINE SEPARATOR
+ // and U+2029 PARAGRAPH SEPARATOR, so something like
+ //
+ // eval("['" + "\u2028" + "']");
+ //
+ // i.e. an array containing a string with a line separator in it, *would*
+ // be JSON but *would not* be valid JavaScript. Handing such a string to
+ // the JSON parser would then fail to recognize a syntax error. As of
+ // <https://tc39.github.io/proposal-json-superset/> JavaScript strings may
+ // contain these two code points, so it's safe to JSON-parse eval strings
+ // that contain them.
+
+ CharT first = chars[0], last = chars[length - 1];
+ return (first == '[' && last == ']') ||
+ (first == '(' && last == ')');
}
template <typename CharT>
diff --git a/js/src/builtin/IntlTimeZoneData.h b/js/src/builtin/IntlTimeZoneData.h
index 1612f0f6b..498c30507 100644
--- a/js/src/builtin/IntlTimeZoneData.h
+++ b/js/src/builtin/IntlTimeZoneData.h
@@ -1,5 +1,5 @@
// Generated by make_intl_data.py. DO NOT EDIT.
-// tzdata version = 2019c
+// tzdata version = 2021a
#ifndef builtin_IntlTimeZoneData_h
#define builtin_IntlTimeZoneData_h
@@ -22,6 +22,7 @@ const char* const ianaZonesTreatedAsLinksByICU[] = {
"America/Ensenada", // America/Tijuana [backzone]
"America/Indiana/Indianapolis", // America/Indianapolis [northamerica]
"America/Kentucky/Louisville", // America/Louisville [northamerica]
+ "America/Nuuk", // America/Godthab [europe]
"America/Rosario", // America/Cordoba [backzone]
"Asia/Chongqing", // Asia/Shanghai [backzone]
"Asia/Harbin", // Asia/Shanghai [backzone]
@@ -56,6 +57,7 @@ const LinkAndTarget ianaLinksCanonicalizedDifferentlyByICU[] = {
{ "America/Catamarca", "America/Argentina/Catamarca" }, // America/Catamarca [backward]
{ "America/Cordoba", "America/Argentina/Cordoba" }, // America/Cordoba [backward]
{ "America/Fort_Wayne", "America/Indiana/Indianapolis" }, // America/Indianapolis [backward]
+ { "America/Godthab", "America/Nuuk" }, // America/Godthab [backward]
{ "America/Indianapolis", "America/Indiana/Indianapolis" }, // America/Indianapolis [backward]
{ "America/Jujuy", "America/Argentina/Jujuy" }, // America/Jujuy [backward]
{ "America/Kralendijk", "America/Curacao" }, // America/Kralendijk [southamerica]
diff --git a/js/src/builtin/RegExp.js b/js/src/builtin/RegExp.js
index 91212066c..7218fc0e8 100644
--- a/js/src/builtin/RegExp.js
+++ b/js/src/builtin/RegExp.js
@@ -1166,3 +1166,24 @@ function RegExpStringIteratorNext() {
result.value = match;
return result;
}
+
+// String.prototype.matchAll proposal.
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
+// 7.2.8 IsRegExp ( argument )
+function IsRegExp(argument) {
+ // Step 1.
+ if (!IsObject(argument)) {
+ return false;
+ }
+
+ // Step 2.
+ var matcher = argument[std_match];
+
+ // Step 3.
+ if (matcher !== undefined) {
+ return !!matcher;
+ }
+
+ // Steps 4-5.
+ return IsPossiblyWrappedRegExpObject(argument);
+}
diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js
index d07ec6127..c70f8558b 100644
--- a/js/src/builtin/String.js
+++ b/js/src/builtin/String.js
@@ -224,6 +224,115 @@ function String_generic_replace(thisValue, searchValue, replaceValue) {
return callFunction(String_replace, thisValue, searchValue, replaceValue);
}
+function String_replaceAll(searchValue, replaceValue) {
+
+ // Step 1.
+ RequireObjectCoercible(this);
+
+ // Step 2.
+ if (searchValue !== undefined && searchValue !== null) {
+ // Steps 2.a-b.
+ if (IsRegExp(searchValue)) {
+ // Step 2.b.i.
+ var flags = searchValue.flags;
+
+ // Step 2.b.ii.
+ if (flags === undefined || flags === null) {
+ ThrowTypeError(JSMSG_FLAGS_UNDEFINED_OR_NULL);
+ }
+
+ // Step 2.b.iii.
+ if (!callFunction(std_String_includes, ToString(flags), "g")) {
+ ThrowTypeError(JSMSG_REQUIRES_GLOBAL_REGEXP, "replaceAll");
+ }
+ }
+
+ // Step 2.c.
+ var replacer = GetMethod(searchValue, std_replace);
+
+ // Step 2.b.
+ if (replacer !== undefined) {
+ return callContentFunction(replacer, searchValue, this, replaceValue);
+ }
+ }
+
+ // Step 3.
+ var string = ToString(this);
+
+ // Step 4.
+ var searchString = ToString(searchValue);
+
+ // Steps 5-6.
+ if (!IsCallable(replaceValue)) {
+ // Steps 7-16.
+ return StringReplaceAllString(string, searchString, ToString(replaceValue));
+ }
+
+ // Step 7.
+ var searchLength = searchString.length;
+
+ // Step 8.
+ var advanceBy = std_Math_max(1, searchLength);
+
+ // Step 9 (not needed in this implementation).
+
+ // Step 12.
+ var endOfLastMatch = 0;
+
+ // Step 13.
+ var result = "";
+
+ // Steps 10-11, 14.
+ var position = 0;
+ while (true) {
+ // Steps 10-11.
+ //
+ // StringIndexOf doesn't clamp the |position| argument to the input
+ // string length, i.e. |StringIndexOf("abc", "", 4)| returns -1,
+ // whereas |"abc".indexOf("", 4)| returns 3. That means we need to
+ // exit the loop when |nextPosition| is smaller than |position| and
+ // not just when |nextPosition| is -1.
+ var nextPosition = callFunction(std_String_indexOf, string, searchString, position);
+ if (nextPosition < position) {
+ break;
+ }
+ position = nextPosition;
+
+ // Step 14.a.
+ var replacement = ToString(callContentFunction(replaceValue, undefined, searchString,
+ position, string));
+
+ // Step 14.b (not applicable).
+
+ // Step 14.c.
+ var stringSlice = Substring(string, endOfLastMatch, position - endOfLastMatch);
+
+ // Step 14.d.
+ result += stringSlice + replacement;
+
+ // Step 14.e.
+ endOfLastMatch = position + searchLength;
+
+ // Step 11.b.
+ position += advanceBy;
+ }
+
+ // Step 15.
+ if (endOfLastMatch < string.length) {
+ // Step 15.a.
+ result += Substring(string, endOfLastMatch, string.length - endOfLastMatch);
+ }
+
+ // Step 16.
+ return result;
+}
+
+function String_generic_replace(thisValue, searchValue, replaceValue) {
+ if (thisValue === undefined)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.replace');
+ return callFunction(String_replace, thisValue, searchValue, replaceValue);
+}
+
function StringProtoHasNoSearch() {
var ObjectProto = GetBuiltinPrototype("Object");
var StringProto = GetBuiltinPrototype("String");
diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp
index d65511a8c..083bcd504 100644
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -603,6 +603,10 @@ TokenStream::TokenBuf::findEOLMax(size_t start, size_t max)
if (n >= max)
break;
n++;
+
+ // This stops at U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR in
+ // string and template literals. These code points do affect line and
+ // column coordinates, even as they encode their literal values.
if (TokenBuf::isRawEOLChar(*p++))
break;
}
@@ -1472,21 +1476,45 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
// Look for a decimal number.
//
if (c1kind == Dec) {
+ MOZ_ASSERT(JS7_ISDEC(c));
tp = newToken(-1);
numStart = userbuf.addressOfNextRawChar() - 1;
-
- decimal:
decimalPoint = NoDecimal;
hasExp = false;
- while (JS7_ISDEC(c))
+ do {
+ c = getCharIgnoreEOL();
+ if (JS7_ISDEC(c))
+ continue;
+ if (c != '_')
+ break;
c = getCharIgnoreEOL();
+ if (!JS7_ISDEC(c)) {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_DIGIT_AFTER_SEPARATOR);
+ goto error;
+ }
+ } while (true);
+ decimal_rest:
if (c == '.') {
decimalPoint = HasDecimal;
- decimal_dot:
- do {
- c = getCharIgnoreEOL();
- } while (JS7_ISDEC(c));
+ c = getCharIgnoreEOL();
+ if (JS7_ISDEC(c)) {
+ decimal_dot:
+ do {
+ c = getCharIgnoreEOL();
+ if (JS7_ISDEC(c))
+ continue;
+ if (c != '_')
+ break;
+ c = getCharIgnoreEOL();
+ if (!JS7_ISDEC(c)) {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_DIGIT_AFTER_SEPARATOR);
+ goto error;
+ }
+ } while (true);
+ }
}
if (c == 'e' || c == 'E') {
hasExp = true;
@@ -1500,7 +1528,17 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
}
do {
c = getCharIgnoreEOL();
- } while (JS7_ISDEC(c));
+ if (JS7_ISDEC(c))
+ continue;
+ if (c != '_')
+ break;
+ c = getCharIgnoreEOL();
+ if (!JS7_ISDEC(c)) {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_DIGIT_AFTER_SEPARATOR);
+ goto error;
+ }
+ } while (true);
}
ungetCharIgnoreEOL(c);
@@ -1529,8 +1567,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
if (!GetDecimalInteger(cx, numStart, userbuf.addressOfNextRawChar(), &dval))
goto error;
} else {
- const char16_t* dummy;
- if (!js_strtod(cx, numStart, userbuf.addressOfNextRawChar(), &dummy, &dval))
+ if (!GetDecimalNonInteger(cx, numStart, userbuf.addressOfNextRawChar(), &dval))
goto error;
}
tp->type = TOK_NUMBER;
@@ -1572,8 +1609,19 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
goto error;
}
numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0x'
- while (JS7_ISHEX(c))
+ do {
c = getCharIgnoreEOL();
+ if (JS7_ISHEX(c))
+ continue;
+ if (c != '_')
+ break;
+ c = getCharIgnoreEOL();
+ if (!JS7_ISHEX(c)) {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_DIGIT_AFTER_SEPARATOR);
+ goto error;
+ }
+ } while (true);
} else if (c == 'b' || c == 'B') {
radix = 2;
c = getCharIgnoreEOL();
@@ -1583,8 +1631,19 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
goto error;
}
numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0b'
- while (c == '0' || c == '1')
+ do {
+ c = getCharIgnoreEOL();
+ if (c == '0' || c == '1')
+ continue;
+ if (c != '_')
+ break;
c = getCharIgnoreEOL();
+ if (c != '0' && c != '1') {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_DIGIT_AFTER_SEPARATOR);
+ goto error;
+ }
+ } while (true);
} else if (c == 'o' || c == 'O') {
radix = 8;
c = getCharIgnoreEOL();
@@ -1594,33 +1653,50 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
goto error;
}
numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0o'
- while ('0' <= c && c <= '7')
+ do {
c = getCharIgnoreEOL();
+ if ('0' <= c && c <= '7')
+ continue;
+ if (c != '_')
+ break;
+ c = getCharIgnoreEOL();
+ if (c < '0' || c > '7') {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_DIGIT_AFTER_SEPARATOR);
+ goto error;
+ }
+ } while (true);
} else if (JS7_ISDEC(c)) {
+ // Octal integer literals are not permitted in strict mode.
+ // Maybe one day we can get rid of this base-8 madness for good.
+ if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
+ goto error;
+
radix = 8;
numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0'
+ bool nonOctalDecimalIntegerLiteral = false;
while (JS7_ISDEC(c)) {
- // Octal integer literals are not permitted in strict mode code.
- if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
- goto error;
-
- // Outside strict mode, we permit 08 and 09 as decimal numbers,
- // which makes our behaviour a superset of the ECMA numeric
- // grammar. We might not always be so permissive, so we warn
- // about it.
- if (c >= '8') {
- if (!warning(JSMSG_BAD_OCTAL, c == '8' ? "08" : "09"))
- goto error;
-
- // Use the decimal scanner for the rest of the number.
- goto decimal;
- }
+ if (c >= '8')
+ nonOctalDecimalIntegerLiteral = true;
c = getCharIgnoreEOL();
}
+
+ if (c == '_') {
+ reportError(JSMSG_INVALID_NUMERIC_SEPARATOR);
+ goto error;
+ }
+ if (nonOctalDecimalIntegerLiteral) {
+ // Use the decimal scanner for the rest of the number.
+ decimalPoint = NoDecimal;
+ hasExp = false;
+ goto decimal_rest;
+ }
} else {
// '0' not followed by 'x', 'X' or a digit; scan as a decimal number.
numStart = userbuf.addressOfNextRawChar() - 1;
- goto decimal;
+ decimalPoint = NoDecimal;
+ hasExp = false;
+ goto decimal_rest;
}
ungetCharIgnoreEOL(c);
@@ -1643,8 +1719,11 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
double dval;
const char16_t* dummy;
- if (!GetPrefixInteger(cx, numStart, userbuf.addressOfNextRawChar(), radix, &dummy, &dval))
+ if (!GetPrefixInteger(cx, numStart, userbuf.addressOfNextRawChar(), radix,
+ PrefixIntegerSeparatorHandling::SkipUnderscore, &dummy, &dval))
+ {
goto error;
+ }
tp->type = TOK_NUMBER;
tp->setNumber(dval, NoDecimal);
goto out;
@@ -2129,8 +2208,9 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
}
break;
}
- } else if (TokenBuf::isRawEOLChar(c)) {
+ } else if (c == '\r' || c == '\n') {
if (!parsingTemplate) {
+ // String literals don't allow ASCII line breaks.
ungetCharIgnoreEOL(c);
error(JSMSG_UNTERMINATED_STRING);
return false;
@@ -2138,10 +2218,18 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
if (c == '\r') {
c = '\n';
if (userbuf.peekRawChar() == '\n')
+ // Treat CRLF as a single line break.
skipCharsIgnoreEOL(1);
}
updateLineInfoForEOL();
updateFlagsForEOL();
+ } else if (c == LINE_SEPARATOR || c == PARA_SEPARATOR) {
+ // U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR encode
+ // their literal values in template literals and (as of the
+ // JSON superset proposal) string literals, but they still count
+ // as line terminators when computing line/column coordinates.
+ updateLineInfoForEOL();
+ updateFlagsForEOL();
} else if (parsingTemplate && c == '$') {
if ((nc = getCharIgnoreEOL()) == '{')
break;
diff --git a/js/src/jit-test/tests/latin1/json.js b/js/src/jit-test/tests/latin1/json.js
index 16559890d..ab7610cd5 100644
--- a/js/src/jit-test/tests/latin1/json.js
+++ b/js/src/jit-test/tests/latin1/json.js
@@ -55,13 +55,10 @@ function testEvalHackNotJSON() {
arr = eval("[]; var z; [1, 2, 3, \"abc\u1200\"]");
assertEq(JSON.stringify(arr), '[1,2,3,"abc\u1200"]');
- try {
- eval("[1, 2, 3, \"abc\u2028\"]");
- throw new Error("U+2028 shouldn't eval");
- } catch (e) {
- assertEq(e instanceof SyntaxError, true,
- "should have thrown a SyntaxError, instead got " + e);
- }
+ // JSON superset
+ var arr = eval("[1, 2, 3, \"abc\u2028\"]");
+ assertEq(arr.length, 4);
+ assertEq(arr[3], "abc\u2028");
}
testEvalHackNotJSON();
diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h
index e4fdf7ee2..01b5f7852 100644
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -66,6 +66,7 @@
_(RegExpSearcher) \
_(RegExpTester) \
_(IsRegExpObject) \
+ _(IntrinsicIsPossiblyWrappedRegExpObject)\
_(RegExpPrototypeOptimizable) \
_(RegExpInstanceOptimizable) \
_(GetFirstDollarIndex) \
diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h
index a262f8166..402fbbf1a 100644
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -867,6 +867,7 @@ class IonBuilder
InliningStatus inlineRegExpSearcher(CallInfo& callInfo);
InliningStatus inlineRegExpTester(CallInfo& callInfo);
InliningStatus inlineIsRegExpObject(CallInfo& callInfo);
+ InliningStatus inlineIsPossiblyWrappedRegExpObject(CallInfo& callInfo);
InliningStatus inlineRegExpPrototypeOptimizable(CallInfo& callInfo);
InliningStatus inlineRegExpInstanceOptimizable(CallInfo& callInfo);
InliningStatus inlineGetFirstDollarIndex(CallInfo& callInfo);
diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp
index 182fa2fd5..23e894ffb 100644
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -185,6 +185,8 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target)
return inlineRegExpTester(callInfo);
case InlinableNative::IsRegExpObject:
return inlineIsRegExpObject(callInfo);
+ case InlinableNative::IntrinsicIsPossiblyWrappedRegExpObject:
+ return inlineIsPossiblyWrappedRegExpObject(callInfo);
case InlinableNative::RegExpPrototypeOptimizable:
return inlineRegExpPrototypeOptimizable(callInfo);
case InlinableNative::RegExpInstanceOptimizable:
@@ -1902,6 +1904,44 @@ IonBuilder::inlineIsRegExpObject(CallInfo& callInfo)
callInfo.setImplicitlyUsedUnchecked();
return InliningStatus_Inlined;
}
+IonBuilder::InliningStatus
+IonBuilder::inlineIsPossiblyWrappedRegExpObject(CallInfo& callInfo)
+{
+ MOZ_ASSERT(!callInfo.constructing());
+ MOZ_ASSERT(callInfo.argc() == 1);
+
+ if (callInfo.getArg(0)->type() != MIRType::Object)
+ return InliningStatus_NotInlined;
+ if (getInlineReturnType() != MIRType::Boolean)
+ return InliningStatus_NotInlined;
+
+ MDefinition* arg = callInfo.getArg(0);
+ if (arg->type() != MIRType::Object) {
+ return InliningStatus_NotInlined;
+ }
+
+ TemporaryTypeSet* types = arg->resultTypeSet();
+ if (!types) {
+ return InliningStatus_NotInlined;
+ }
+
+ // Don't inline if the argument might be a wrapper.
+ if (types->forAllClasses(constraints(), IsProxyClass) !=
+ TemporaryTypeSet::ForAllResult::ALL_FALSE) {
+ return InliningStatus_NotInlined;
+ }
+
+ if (const Class* clasp = types->getKnownClass(constraints())) {
+ pushConstant(BooleanValue(clasp == &RegExpObject::class_));
+ } else {
+ MHasClass* hasClass = MHasClass::New(alloc(), arg, &RegExpObject::class_);
+ current->add(hasClass);
+ current->push(hasClass);
+ }
+
+ callInfo.setImplicitlyUsedUnchecked();
+ return InliningStatus_Inlined;
+}
IonBuilder::InliningStatus
IonBuilder::inlineRegExpPrototypeOptimizable(CallInfo& callInfo)
diff --git a/js/src/jit/x86-shared/Constants-x86-shared.h b/js/src/jit/x86-shared/Constants-x86-shared.h
index e5f1d7cd8..0874baf72 100644
--- a/js/src/jit/x86-shared/Constants-x86-shared.h
+++ b/js/src/jit/x86-shared/Constants-x86-shared.h
@@ -47,6 +47,7 @@ inline const char* XMMRegName(XMMRegisterID reg)
#ifdef JS_CODEGEN_X64
,"%xmm8", "%xmm9", "%xmm10", "%xmm11", "%xmm12", "%xmm13", "%xmm14", "%xmm15"
#endif
+ ,"invalid"
};
MOZ_ASSERT(size_t(reg) < mozilla::ArrayLength(names));
return names[reg];
diff --git a/js/src/js.msg b/js/src/js.msg
index 587481864..b03da4153 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -205,7 +205,6 @@ MSG_DEF(JSMSG_BAD_GENERATOR_RETURN, 0, JSEXN_TYPEERR, "generator function c
MSG_DEF(JSMSG_BAD_GENEXP_BODY, 1, JSEXN_SYNTAXERR, "illegal use of {0} in generator expression")
MSG_DEF(JSMSG_BAD_INCOP_OPERAND, 0, JSEXN_SYNTAXERR, "invalid increment/decrement operand")
MSG_DEF(JSMSG_BAD_METHOD_DEF, 0, JSEXN_SYNTAXERR, "bad method definition")
-MSG_DEF(JSMSG_BAD_OCTAL, 1, JSEXN_SYNTAXERR, "{0} is not a legal ECMA-262 octal constant")
MSG_DEF(JSMSG_BAD_POW_LEFTSIDE, 0, JSEXN_SYNTAXERR, "unparenthesized unary expression can't appear on the left-hand side of '**'")
MSG_DEF(JSMSG_BAD_PROP_ID, 0, JSEXN_SYNTAXERR, "invalid property id")
MSG_DEF(JSMSG_BAD_RETURN_OR_YIELD, 1, JSEXN_SYNTAXERR, "{0} not in function")
@@ -270,6 +269,7 @@ MSG_DEF(JSMSG_OF_AFTER_FOR_LOOP_DECL, 0, JSEXN_SYNTAXERR, "a declaration in the
MSG_DEF(JSMSG_IN_AFTER_LEXICAL_FOR_DECL,0,JSEXN_SYNTAXERR, "a lexical declaration in the head of a for-in loop can't have an initializer")
MSG_DEF(JSMSG_INVALID_FOR_IN_DECL_WITH_INIT,0,JSEXN_SYNTAXERR,"for-in loop head declarations may not have initializers")
MSG_DEF(JSMSG_INVALID_ID, 1, JSEXN_SYNTAXERR, "{0} is an invalid identifier")
+MSG_DEF(JSMSG_INVALID_NUMERIC_SEPARATOR, 0, JSEXN_SYNTAXERR, "numeric separators are not allowed in \"0\"-prefixed octal literals")
MSG_DEF(JSMSG_LABEL_NOT_FOUND, 0, JSEXN_SYNTAXERR, "label not found")
MSG_DEF(JSMSG_LET_COMP_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable")
MSG_DEF(JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, 1, JSEXN_SYNTAXERR, "{0} declaration not directly within block")
@@ -281,6 +281,7 @@ MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW, 0, JSEXN_SYNTAXERR, "no line break is all
MSG_DEF(JSMSG_LINE_BREAK_BEFORE_ARROW, 0, JSEXN_SYNTAXERR, "no line break is allowed before '=>'")
MSG_DEF(JSMSG_MALFORMED_ESCAPE, 1, JSEXN_SYNTAXERR, "malformed {0} character escape sequence")
MSG_DEF(JSMSG_MISSING_BINARY_DIGITS, 0, JSEXN_SYNTAXERR, "missing binary digits after '0b'")
+MSG_DEF(JSMSG_MISSING_DIGIT_AFTER_SEPARATOR, 0, JSEXN_SYNTAXERR, "missing digit after '_' numeric separator")
MSG_DEF(JSMSG_MISSING_EXPONENT, 0, JSEXN_SYNTAXERR, "missing exponent")
MSG_DEF(JSMSG_MISSING_EXPR_AFTER_THROW,0, JSEXN_SYNTAXERR, "throw statement is missing an expression")
MSG_DEF(JSMSG_MISSING_FORMAL, 0, JSEXN_SYNTAXERR, "missing formal parameter")
diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp
index c15b3de0d..549596e57 100644
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -71,12 +71,16 @@ ComputeAccurateDecimalInteger(ExclusiveContext* cx, const CharT* start, const Ch
if (!cstr)
return false;
+ size_t j = 0;
for (size_t i = 0; i < length; i++) {
char c = char(start[i]);
+ if (c == '_') {
+ continue;
+ }
MOZ_ASSERT(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'));
- cstr[i] = c;
+ cstr[j++] = c;
}
- cstr[length] = 0;
+ cstr[j] = 0;
char* estr;
int err = 0;
@@ -111,8 +115,14 @@ class BinaryDigitReader
if (digitMask == 0) {
if (start == end)
return -1;
-
int c = *start++;
+ if (c == '_') {
+ // Skip over one _ and one _ only.
+ if (start == end)
+ return -1;
+ c = *start++;
+ }
+
MOZ_ASSERT(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'));
if ('0' <= c && c <= '9')
digit = c - '0';
@@ -209,7 +219,8 @@ js::ParseDecimalNumber(const mozilla::Range<const char16_t> chars);
template <typename CharT>
bool
js::GetPrefixInteger(ExclusiveContext* cx, const CharT* start, const CharT* end, int base,
- const CharT** endp, double* dp)
+ PrefixIntegerSeparatorHandling separatorHandling, const CharT** endp,
+ double* dp)
{
MOZ_ASSERT(start <= end);
MOZ_ASSERT(2 <= base && base <= 36);
@@ -225,6 +236,8 @@ js::GetPrefixInteger(ExclusiveContext* cx, const CharT* start, const CharT* end,
digit = c - 'a' + 10;
else if ('A' <= c && c <= 'Z')
digit = c - 'A' + 10;
+ else if (c == '_' && separatorHandling == PrefixIntegerSeparatorHandling::SkipUnderscore)
+ continue;
else
break;
if (digit >= base)
@@ -242,7 +255,7 @@ js::GetPrefixInteger(ExclusiveContext* cx, const CharT* start, const CharT* end,
/*
* Otherwise compute the correct integer from the prefix of valid digits
* if we're computing for base ten or a power of two. Don't worry about
- * other bases; see 15.1.2.2 step 13.
+ * other bases; see ES2018, 18.2.5 `parseInt(string, radix)`, step 13.
*/
if (base == 10)
return ComputeAccurateDecimalInteger(cx, start, s, dp);
@@ -255,11 +268,13 @@ js::GetPrefixInteger(ExclusiveContext* cx, const CharT* start, const CharT* end,
template bool
js::GetPrefixInteger(ExclusiveContext* cx, const char16_t* start, const char16_t* end, int base,
- const char16_t** endp, double* dp);
+ PrefixIntegerSeparatorHandling separatorHandling, const char16_t** endp,
+ double* dp);
template bool
-js::GetPrefixInteger(ExclusiveContext* cx, const Latin1Char* start, const Latin1Char* end,
- int base, const Latin1Char** endp, double* dp);
+js::GetPrefixInteger(ExclusiveContext* cx, const Latin1Char* start, const Latin1Char* end, int base,
+ PrefixIntegerSeparatorHandling separatorHandling, const Latin1Char** endp,
+ double* dp);
bool
js::GetDecimalInteger(ExclusiveContext* cx, const char16_t* start, const char16_t* end, double* dp)
@@ -270,6 +285,8 @@ js::GetDecimalInteger(ExclusiveContext* cx, const char16_t* start, const char16_
double d = 0.0;
for (; s < end; s++) {
char16_t c = *s;
+ if (c == '_')
+ continue;
MOZ_ASSERT('0' <= c && c <= '9');
int digit = c - '0';
d = d * 10 + digit;
@@ -285,6 +302,36 @@ js::GetDecimalInteger(ExclusiveContext* cx, const char16_t* start, const char16_
return ComputeAccurateDecimalInteger(cx, start, s, dp);
}
+bool
+js::GetDecimalNonInteger(ExclusiveContext* cx, const char16_t* start, const char16_t* end, double* dp)
+{
+ MOZ_ASSERT(start <= end);
+
+ size_t length = end - start;
+ Vector<char, 32> chars(cx);
+ if (!chars.growByUninitialized(length + 1))
+ return false;
+
+ const char16_t* s = start;
+ size_t i = 0;
+ for (; s < end; s++) {
+ char16_t c = *s;
+ if (c == '_')
+ continue;
+ MOZ_ASSERT(('0' <= c && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' ||
+ c == '-');
+ chars[i++] = char(c);
+ }
+ chars[i] = 0;
+
+ char* ep;
+ int err; // unused
+ *dp = js_strtod_harder(cx->dtoaState(), chars.begin(), &ep, &err);
+ MOZ_ASSERT(ep >= chars.begin());
+
+ return true;
+}
+
static bool
num_parseFloat(JSContext* cx, unsigned argc, Value* vp)
{
@@ -355,7 +402,7 @@ ParseIntImpl(JSContext* cx, const CharT* chars, size_t length, bool stripPrefix,
/* Steps 11-15. */
const CharT* actualEnd;
double d;
- if (!GetPrefixInteger(cx, s, end, radix, &actualEnd, &d))
+ if (!GetPrefixInteger(cx, s, end, radix, PrefixIntegerSeparatorHandling::None, &actualEnd, &d))
return false;
if (s == actualEnd)
@@ -1322,7 +1369,7 @@ CharsToNumber(ExclusiveContext* cx, const CharT* chars, size_t length, double* r
*/
const CharT* endptr;
double d;
- if (!GetPrefixInteger(cx, bp + 2, end, radix, &endptr, &d) ||
+ if (!GetPrefixInteger(cx, bp + 2, end, radix, PrefixIntegerSeparatorHandling::None, &endptr, &d) ||
endptr == bp + 2 ||
SkipSpace(endptr, end) != end)
{
diff --git a/js/src/jsnum.h b/js/src/jsnum.h
index ed8e9d18e..f169a9c38 100644
--- a/js/src/jsnum.h
+++ b/js/src/jsnum.h
@@ -131,31 +131,51 @@ template <typename CharT>
extern double
ParseDecimalNumber(const mozilla::Range<const CharT> chars);
+/* Separator handling for integers. Either skip underscores for numerical
+ * separators (ES2021) or not
+ */
+enum class PrefixIntegerSeparatorHandling : bool {
+ None, SkipUnderscore
+};
+
/*
* Compute the positive integer of the given base described immediately at the
- * start of the range [start, end) -- no whitespace-skipping, no magical
+ * start of the range [start, end] -- no whitespace-skipping, no magical
* leading-"0" octal or leading-"0x" hex behavior, no "+"/"-" parsing, just
* reading the digits of the integer. Return the index one past the end of the
* digits of the integer in *endp, and return the integer itself in *dp. If
* base is 10 or a power of two the returned integer is the closest possible
* double; otherwise extremely large integers may be slightly inaccurate.
*
- * If [start, end) does not begin with a number with the specified base,
- * *dp == 0 and *endp == start upon return.
+ * The |separatorHandling| controls whether or not numeric separators can be
+ * part of an integer string. If the option is enabled, all individual '_'
+ * characters in the string are ignored.
+ *
+ * If [start, end] does not begin with a number with the specified base,
+ * then upon return *dp == 0 and *endp == start.
*/
template <typename CharT>
extern MOZ_MUST_USE bool
GetPrefixInteger(ExclusiveContext* cx, const CharT* start, const CharT* end, int base,
- const CharT** endp, double* dp);
+ PrefixIntegerSeparatorHandling separatorHandling, const CharT** endp,
+ double* dp);
/*
- * This is like GetPrefixInteger, but only deals with base 10, and doesn't have
- * and |endp| outparam. It should only be used when the characters are known to
- * only contain digits.
+ * This is like GetPrefixInteger, but only deals with base 10, always ignores
+ * '_', and doesn't have an |endp| outparam. It should only be used when the
+ * characters are known to only contain digits and '_'.
*/
extern MOZ_MUST_USE bool
GetDecimalInteger(ExclusiveContext* cx, const char16_t* start, const char16_t* end, double* dp);
+/*
+ * This is like GetDecimalInteger, but also allows non-integer numbers. It
+ * should only be used when the characters are known to only contain digits,
+ * '.', 'e' or 'E', '+' or '-', and '_'.
+ */
+extern MOZ_MUST_USE bool
+GetDecimalNonInteger(ExclusiveContext* cx, const char16_t* start, const char16_t* end, double* dp);
+
extern MOZ_MUST_USE bool
StringToNumber(ExclusiveContext* cx, JSString* str, double* result);
diff --git a/js/src/json.cpp b/js/src/json.cpp
index 5f1238f9f..e32994e90 100644
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -39,78 +39,122 @@ const Class js::JSONClass = {
JSCLASS_HAS_CACHED_PROTO(JSProto_JSON)
};
-static inline bool
-IsQuoteSpecialCharacter(char16_t c)
+/* ES5 15.12.3 Quote.
+ * Requires that the destination has enough space allocated for src after escaping
+ * (that is, `2 + 6 * (srcEnd - srcBegin)` characters).
+ */
+template <typename SrcCharT, typename DstCharT>
+static MOZ_ALWAYS_INLINE RangedPtr<DstCharT>
+InfallibleQuote(RangedPtr<const SrcCharT> srcBegin, RangedPtr<const SrcCharT> srcEnd, RangedPtr<DstCharT> dstPtr)
{
- static_assert('\b' < ' ', "'\\b' must be treated as special below");
- static_assert('\f' < ' ', "'\\f' must be treated as special below");
- static_assert('\n' < ' ', "'\\n' must be treated as special below");
- static_assert('\r' < ' ', "'\\r' must be treated as special below");
- static_assert('\t' < ' ', "'\\t' must be treated as special below");
-
- return c == '"' || c == '\\' || c < ' ';
-}
-
-/* ES5 15.12.3 Quote. */
-template <typename CharT>
-static bool
-Quote(StringBuffer& sb, JSLinearString* str)
-{
- size_t len = str->length();
+ // Maps characters < 256 to the value that must follow the '\\' in the quoted string.
+ // Entries with 'u' are handled as \\u00xy, and entries with 0 are not escaped in any way.
+ // Characters >= 256 are all assumed to be unescaped.
+ static const Latin1Char escapeLookup[256] = {
+ 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't',
+ 'n', 'u', 'f', 'r', 'u', 'u', 'u', 'u', 'u', 'u',
+ 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u',
+ 'u', 'u', 0, 0, '\"', 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, '\\', // rest are all zeros
+ };
/* Step 1. */
- if (!sb.append('"'))
- return false;
+ *dstPtr++ = '"';
+
+ // XXX: This is a rather ugly in-line definition. Move it somewhere better?
+ auto ToLowerHex = [](uint8_t u) {
+ MOZ_ASSERT(u <= 0xF);
+ return "0123456789abcdef"[u];
+ };
/* Step 2. */
- JS::AutoCheckCannotGC nogc;
- const RangedPtr<const CharT> buf(str->chars<CharT>(nogc), len);
- for (size_t i = 0; i < len; ++i) {
- /* Batch-append maximal character sequences containing no escapes. */
- size_t mark = i;
- do {
- if (IsQuoteSpecialCharacter(buf[i]))
- break;
- } while (++i < len);
- if (i > mark) {
- if (!sb.appendSubstring(str, mark, i - mark))
- return false;
- if (i == len)
- break;
- }
+ while (srcBegin != srcEnd) {
+ const SrcCharT c = *srcBegin++;
- char16_t c = buf[i];
- if (c == '"' || c == '\\') {
- if (!sb.append('\\') || !sb.append(c))
- return false;
- } else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') {
- char16_t abbrev = (c == '\b')
- ? 'b'
- : (c == '\f')
- ? 'f'
- : (c == '\n')
- ? 'n'
- : (c == '\r')
- ? 'r'
- : 't';
- if (!sb.append('\\') || !sb.append(abbrev))
- return false;
- } else {
- MOZ_ASSERT(c < ' ');
- if (!sb.append("\\u00"))
- return false;
- MOZ_ASSERT((c >> 4) < 10);
- uint8_t x = c >> 4, y = c % 16;
- if (!sb.append(Latin1Char('0' + x)) ||
- !sb.append(Latin1Char(y < 10 ? '0' + y : 'a' + (y - 10))))
- {
- return false;
+ // Handle the Latin-1 cases.
+ if (MOZ_LIKELY(c < sizeof(escapeLookup))) {
+ Latin1Char escaped = escapeLookup[c];
+
+ // Directly copy non-escaped code points.
+ if (escaped == 0) {
+ *dstPtr++ = c;
+ continue;
+ }
+
+ // Escape the rest, elaborating Unicode escapes when needed.
+ *dstPtr++ = '\\';
+ *dstPtr++ = escaped;
+ if (escaped == 'u') {
+ *dstPtr++ = '0';
+ *dstPtr++ = '0';
+
+ uint8_t x = c >> 4;
+ MOZ_ASSERT(x < 10);
+ *dstPtr++ = '0' + x;
+
+ *dstPtr++ = ToLowerHex(c & 0xF);
}
+
+ continue;
+ }
+
+ // Non-ASCII non-surrogates are directly copied.
+ if (!unicode::IsSurrogate(c)) {
+ *dstPtr++ = c;
+ continue;
+ }
+
+ // So too for complete surrogate pairs.
+ if (MOZ_LIKELY(unicode::IsLeadSurrogate(c) &&
+ srcBegin < srcEnd &&
+ unicode::IsTrailSurrogate(*srcBegin)))
+ {
+ *dstPtr++ = c;
+ *dstPtr++ = *srcBegin++;
+ continue;
}
+
+ // But lone surrogates are Unicode-escaped.
+ char32_t as32 = char32_t(c);
+ *dstPtr++ = '\\';
+ *dstPtr++ = 'u';
+ *dstPtr++ = ToLowerHex(as32 >> 12);
+ *dstPtr++ = ToLowerHex((as32 >> 8) & 0xF);
+ *dstPtr++ = ToLowerHex((as32 >> 4) & 0xF);
+ *dstPtr++ = ToLowerHex(as32 & 0xF);
}
/* Steps 3-4. */
- return sb.append('"');
+ *dstPtr++ = '"';
+ return dstPtr;
+}
+
+template <typename SrcCharT, typename CharVectorT>
+static bool
+Quote(CharVectorT& sb, JSLinearString* str)
+{
+ // We resize the backing buffer to the maximum size we could possibly need,
+ // write the escaped string into it, and shrink it back to the size we ended
+ // up needing.
+ size_t len = str->length();
+ size_t sbInitialLen = sb.length();
+ if (!sb.growByUninitialized(len * 6 + 2))
+ return false;
+
+ typedef typename CharVectorT::ElementType DstCharT;
+
+ JS::AutoCheckCannotGC nogc;
+ RangedPtr<const SrcCharT> srcBegin{str->chars<SrcCharT>(nogc), len};
+ RangedPtr<DstCharT> dstBegin{sb.begin(), sb.begin(), sb.end()};
+ RangedPtr<DstCharT> dstEnd = InfallibleQuote(srcBegin, srcBegin + len, dstBegin + sbInitialLen);
+ size_t newSize = dstEnd - dstBegin;
+ sb.shrinkTo(newSize);
+ return true;
}
static bool
@@ -120,14 +164,23 @@ Quote(JSContext* cx, StringBuffer& sb, JSString* str)
if (!linear)
return false;
- return linear->hasLatin1Chars()
- ? Quote<Latin1Char>(sb, linear)
- : Quote<char16_t>(sb, linear);
+ // Check if either has non-latin1 before calling ensure, so that the buffer's
+ // hasEnsured flag is set if the converstion to twoByte was automatic.
+ if (!sb.isUnderlyingBufferLatin1() || linear->hasTwoByteChars()) {
+ if (!sb.ensureTwoByteChars())
+ return false;
+ }
+ if (linear->hasTwoByteChars())
+ return Quote<char16_t>(sb.rawTwoByteBuffer(), linear);
+
+ return sb.isUnderlyingBufferLatin1()
+ ? Quote<Latin1Char>(sb.latin1Chars(), linear)
+ : Quote<Latin1Char>(sb.rawTwoByteBuffer(), linear);
}
namespace {
-using ObjectSet = GCHashSet<JSObject*, MovableCellHasher<JSObject*>, SystemAllocPolicy>;
+using ObjectVector = GCVector<JSObject*, 8>;
class StringifyContext
{
@@ -138,7 +191,7 @@ class StringifyContext
: sb(sb),
gap(gap),
replacer(cx, replacer),
- stack(cx),
+ stack(cx, ObjectVector(cx)),
propertyList(propertyList),
depth(0),
maybeSafely(maybeSafely)
@@ -147,14 +200,10 @@ class StringifyContext
MOZ_ASSERT_IF(maybeSafely, gap.empty());
}
- bool init() {
- return stack.init(8);
- }
-
StringBuffer& sb;
const StringBuffer& gap;
RootedObject replacer;
- Rooted<ObjectSet> stack;
+ Rooted<ObjectVector> stack;
const AutoIdVector& propertyList;
uint32_t depth;
bool maybeSafely;
@@ -302,29 +351,34 @@ class CycleDetector
{
public:
CycleDetector(StringifyContext* scx, HandleObject obj)
- : stack(&scx->stack), obj_(obj) {
- }
-
- bool foundCycle(JSContext* cx) {
- auto addPtr = stack.lookupForAdd(obj_);
- if (addPtr) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_JSON_CYCLIC_VALUE);
- return false;
- }
- if (!stack.add(addPtr, obj_)) {
- ReportOutOfMemory(cx);
- return false;
+ : stack_(&scx->stack)
+ , obj_(obj)
+ , appended_(false)
+ {}
+
+ MOZ_ALWAYS_INLINE bool foundCycle(JSContext* cx) {
+ JSObject* obj = obj_;
+ for (JSObject* obj2 : stack_) {
+ if (MOZ_UNLIKELY(obj == obj2)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_JSON_CYCLIC_VALUE);
+ return false;
+ }
}
- return true;
+ appended_ = stack_.append(obj);
+ return appended_;
}
~CycleDetector() {
- stack.remove(obj_);
+ if (MOZ_LIKELY(appended_)) {
+ MOZ_ASSERT(stack_.back() == obj_);
+ stack_.popBack();
+ }
}
private:
- MutableHandle<ObjectSet> stack;
+ MutableHandle<ObjectVector> stack_;
HandleObject obj_;
+ bool appended_;
};
/* ES5 15.12.3 JO. */
@@ -747,8 +801,6 @@ js::Stringify(JSContext* cx, MutableHandleValue vp, JSObject* replacer_, const V
/* Step 12. */
StringifyContext scx(cx, sb, gap, replacer, propertyList,
stringifyBehavior == StringifyBehavior::RestrictedSafe);
- if (!scx.init())
- return false;
if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx))
return false;
if (IsFilteredValue(vp))
diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp
index 7a0861998..6eb11b1cc 100644
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2111,9 +2111,13 @@ AppendDollarReplacement(StringBuffer& newReplaceChars, size_t firstDollarIndex,
const CharT* repChars, size_t repLength)
{
MOZ_ASSERT(firstDollarIndex < repLength);
+ MOZ_ASSERT(matchStart <= matchLimit);
+ MOZ_ASSERT(matchLimit <= text->length());
/* Move the pre-dollar chunk in bulk. */
- newReplaceChars.infallibleAppend(repChars, firstDollarIndex);
+ if (!newReplaceChars.append(repChars, firstDollarIndex)) {
+ return false;
+ }
/* Move the rest char-by-char, interpreting dollars as we encounter them. */
const CharT* repLimit = repChars + repLength;
@@ -2329,6 +2333,200 @@ js::str_flat_replace_string(JSContext *cx, HandleString string, HandleString pat
return str;
}
+// https://tc39.es/proposal-string-replaceall/#sec-string.prototype.replaceall
+// Steps 7-16 when functionalReplace is false and searchString is not empty.
+//
+// The steps are quite different, for performance. Loops in steps 11 and 14
+// are fused. GetSubstitution is optimized away when possible.
+template <typename StrChar, typename RepChar>
+static JSString* ReplaceAll(JSContext* cx, JSLinearString* str,
+ JSLinearString* pat,
+ JSLinearString* rep) {
+ // Step 7.
+ const size_t stringLength = str->length();
+ const size_t searchLength = pat->length();
+ const size_t replaceLength = rep->length();
+
+ MOZ_ASSERT(stringLength > 0);
+ MOZ_ASSERT(searchLength > 0);
+ MOZ_ASSERT(stringLength >= searchLength);
+
+ // Step 8 (advanceBy is equal to searchLength when searchLength > 0).
+
+ // Step 9 (not needed in this implementation).
+
+ // Step 10.
+ // Find the first match.
+ int32_t position = StringMatch(str, pat, 0);
+
+ // Nothing to replace, so return early.
+ if (position < 0) {
+ return str;
+ }
+
+ // Step 11 (moved below).
+
+ // Step 12.
+ uint32_t endOfLastMatch = 0;
+
+ // Step 13.
+ StringBuffer result(cx);
+ if (std::is_same<StrChar, char16_t>::value ||
+ std::is_same<RepChar, char16_t>::value) {
+ if (!result.ensureTwoByteChars()) {
+ return nullptr;
+ }
+ }
+
+ {
+ AutoCheckCannotGC nogc;
+ const StrChar* strChars = str->chars<StrChar>(nogc);
+ const RepChar* repChars = rep->chars<RepChar>(nogc);
+
+ uint32_t dollarIndex = FindDollarIndex(repChars, replaceLength);
+
+ // If it's true, we are sure that the result's length is, at least, the same
+ // length as |str->length()|.
+ if (replaceLength >= searchLength) {
+ if (!result.reserve(stringLength)) {
+ return nullptr;
+ }
+ }
+
+ do {
+ // Step 14.c.
+ // Append the substring before the current match.
+ if (!result.append(strChars + endOfLastMatch,
+ position - endOfLastMatch)) {
+ return nullptr;
+ }
+
+ // Steps 14.a-b and 14.d.
+ // Append the replacement.
+ if (dollarIndex != UINT32_MAX) {
+ size_t matchLimit = position + searchLength;
+ if (!AppendDollarReplacement(result, dollarIndex, position, matchLimit,
+ str, repChars, replaceLength)) {
+ return nullptr;
+ }
+ } else {
+ if (!result.append(repChars, replaceLength)) {
+ return nullptr;
+ }
+ }
+
+ // Step 14.e.
+ endOfLastMatch = position + searchLength;
+
+ // Step 11.
+ // Find the next match.
+ position = StringMatch(str, pat, endOfLastMatch);
+ } while (position >= 0);
+
+ // Step 15.
+ // Append the substring after the last match.
+ if (!result.append(strChars + endOfLastMatch,
+ stringLength - endOfLastMatch)) {
+ return nullptr;
+ }
+ }
+
+ // Step 16.
+ return result.finishString();
+}
+
+// https://tc39.es/proposal-string-replaceall/#sec-string.prototype.replaceall
+// Steps 7-16 when functionalReplace is false and searchString is the empty
+// string.
+//
+// The steps are quite different, for performance. Loops in steps 11 and 14
+// are fused. GetSubstitution is optimized away when possible.
+template <typename StrChar, typename RepChar>
+static JSString* ReplaceAllInterleave(JSContext* cx, JSLinearString* str,
+ JSLinearString* rep) {
+ // Step 7.
+ const size_t stringLength = str->length();
+ const size_t replaceLength = rep->length();
+
+ // Step 8 (advanceBy is 1 when searchString is the empty string).
+
+ // Steps 9-12 (trivial when searchString is the empty string).
+
+ // Step 13.
+ StringBuffer result(cx);
+ if (std::is_same<StrChar, char16_t>::value ||
+ std::is_same<RepChar, char16_t>::value) {
+ if (!result.ensureTwoByteChars()) {
+ return nullptr;
+ }
+ }
+
+ {
+ AutoCheckCannotGC nogc;
+ const StrChar* strChars = str->chars<StrChar>(nogc);
+ const RepChar* repChars = rep->chars<RepChar>(nogc);
+
+ uint32_t dollarIndex = FindDollarIndex(repChars, replaceLength);
+
+ if (dollarIndex != UINT32_MAX) {
+ if (!result.reserve(stringLength)) {
+ return nullptr;
+ }
+ } else {
+ // Compute the exact result length when no substitutions take place.
+ CheckedInt<uint32_t> strLength(stringLength);
+ CheckedInt<uint32_t> repLength(replaceLength);
+ CheckedInt<uint32_t> length = strLength + (strLength + 1) * repLength;
+ if (!length.isValid()) {
+ ReportAllocationOverflow(cx);
+ return nullptr;
+ }
+
+ if (!result.reserve(length.value())) {
+ return nullptr;
+ }
+ }
+
+ auto appendReplacement = [&](size_t match) {
+ if (dollarIndex != UINT32_MAX) {
+ return AppendDollarReplacement(result, dollarIndex, match, match,
+ str, repChars, replaceLength);
+ }
+ return result.append(repChars, replaceLength);
+ };
+
+ for (size_t index = 0; index < stringLength; index++) {
+ // Steps 11, 14.a-b and 14.d.
+ // The empty string matches before each character.
+ if (!appendReplacement(index)) {
+ return nullptr;
+ }
+
+ // Step 14.c.
+ if (!result.append(strChars[index])) {
+ return nullptr;
+ }
+ }
+
+ // Steps 11, 14.a-b and 14.d.
+ // The empty string also matches at the end of the string.
+ if (!appendReplacement(stringLength)) {
+ return nullptr;
+ }
+
+ // Step 15 (not applicable when searchString is the empty string).
+ }
+
+ // Step 16.
+ return result.finishString();
+}
+
+// String.prototype.replaceAll (Stage 3 proposal)
+// https://tc39.es/proposal-string-replaceall/
+//
+// String.prototype.replaceAll ( searchValue, replaceValue )
+//
+// Steps 7-16 when functionalReplace is false.o
JSString*
js::str_replace_string_raw(JSContext* cx, HandleString string, HandleString pattern,
HandleString replacement)
@@ -2371,6 +2569,62 @@ js::str_replace_string_raw(JSContext* cx, HandleString string, HandleString patt
return BuildFlatReplacement(cx, string, repl, match, patternLength);
}
+JSString*
+js::str_replaceAll_string_raw(JSContext* cx, HandleString string, HandleString pattern,
+ HandleString replacement)
+{
+ const size_t stringLength = string->length();
+ const size_t searchLength = pattern->length();
+
+ // Directly return when we're guaranteed to find no match.
+ if (searchLength > stringLength) {
+ return string;
+ }
+
+ RootedLinearString str(cx, string->ensureLinear(cx));
+ if (!str) {
+ return nullptr;
+ }
+
+ RootedLinearString repl(cx, replacement->ensureLinear(cx));
+ if (!repl) {
+ return nullptr;
+ }
+
+ RootedLinearString search(cx, pattern->ensureLinear(cx));
+ if (!search) {
+ return nullptr;
+ }
+
+ // The pattern is empty, so we interleave the replacement string in-between
+ // each character.
+ if (searchLength == 0) {
+ if (str->hasTwoByteChars()) {
+ if (repl->hasTwoByteChars()) {
+ return ReplaceAllInterleave<char16_t, char16_t>(cx, str, repl);
+ }
+ return ReplaceAllInterleave<char16_t, Latin1Char>(cx, str, repl);
+ }
+ if (repl->hasTwoByteChars()) {
+ return ReplaceAllInterleave<Latin1Char, char16_t>(cx, str, repl);
+ }
+ return ReplaceAllInterleave<Latin1Char, Latin1Char>(cx, str, repl);
+ }
+
+ MOZ_ASSERT(stringLength > 0);
+
+ if (str->hasTwoByteChars()) {
+ if (repl->hasTwoByteChars()) {
+ return ReplaceAll<char16_t, char16_t>(cx, str, search, repl);
+ }
+ return ReplaceAll<char16_t, Latin1Char>(cx, str, search, repl);
+ }
+ if (repl->hasTwoByteChars()) {
+ return ReplaceAll<Latin1Char, char16_t>(cx, str, search, repl);
+ }
+ return ReplaceAll<Latin1Char, Latin1Char>(cx, str, search, repl);
+}
+
// ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
static JSObject*
SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearString sep,
@@ -2572,9 +2826,9 @@ static const JSFunctionSpec string_methods[] = {
JS_FN("endsWith", str_endsWith, 1,0),
JS_FN("trim", str_trim, 0,0),
JS_FN("trimLeft", str_trimStart, 0,0),
- JS_FN("trimStart", str_trimStart, 0,0),
+ JS_FN("trimStart", str_trimStart, 0,0),
JS_FN("trimRight", str_trimEnd, 0,0),
- JS_FN("trimEnd", str_trimEnd, 0,0),
+ JS_FN("trimEnd", str_trimEnd, 0,0),
JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,0),
JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,0),
JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
@@ -2586,6 +2840,7 @@ static const JSFunctionSpec string_methods[] = {
JS_SELF_HOSTED_FN("matchAll", "String_matchAll", 1,0),
JS_SELF_HOSTED_FN("search", "String_search", 1,0),
JS_SELF_HOSTED_FN("replace", "String_replace", 2,0),
+ JS_SELF_HOSTED_FN("replaceAll", "String_replaceAll", 2,0),
JS_SELF_HOSTED_FN("split", "String_split", 2,0),
JS_SELF_HOSTED_FN("substr", "String_substr", 2,0),
diff --git a/js/src/jsstr.h b/js/src/jsstr.h
index 42891900f..0e31276a8 100644
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -476,6 +476,10 @@ JSString*
str_replace_string_raw(JSContext* cx, HandleString string, HandleString pattern,
HandleString replacement);
+JSString*
+str_replaceAll_string_raw(JSContext* cx, HandleString string, HandleString pattern,
+ HandleString replacement);
+
extern bool
StringConstructor(JSContext* cx, unsigned argc, Value* vp);
diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js
index 8bd0512c5..2a0f982c5 100644
--- a/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js
@@ -1,7 +1,7 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
// Generated by make_intl_data.py. DO NOT EDIT.
-// tzdata version = 2019c
+// tzdata version = 2021a
const tzMapper = [
x => x,
@@ -17,6 +17,7 @@ const links = {
"America/Catamarca": "America/Argentina/Catamarca",
"America/Cordoba": "America/Argentina/Cordoba",
"America/Fort_Wayne": "America/Indiana/Indianapolis",
+ "America/Godthab": "America/Nuuk",
"America/Indianapolis": "America/Indiana/Indianapolis",
"America/Jujuy": "America/Argentina/Jujuy",
"America/Knox_IN": "America/Indiana/Knox",
diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js
index c760fd85e..ad5c0d1d4 100644
--- a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js
@@ -1,7 +1,7 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
// Generated by make_intl_data.py. DO NOT EDIT.
-// tzdata version = 2019c
+// tzdata version = 2021a
const tzMapper = [
x => x,
@@ -79,6 +79,7 @@ const links = {
"Asia/Vientiane": "Asia/Vientiane",
"Atlantic/Jan_Mayen": "Atlantic/Jan_Mayen",
"Atlantic/St_Helena": "Atlantic/St_Helena",
+ "Australia/Currie": "Australia/Currie",
"Europe/Belfast": "Europe/Belfast",
"Europe/Guernsey": "Europe/Guernsey",
"Europe/Isle_of_Man": "Europe/Isle_of_Man",
diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js
index 38db5e77d..98357774d 100644
--- a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js
@@ -1,7 +1,7 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
// Generated by make_intl_data.py. DO NOT EDIT.
-// tzdata version = 2019c
+// tzdata version = 2021a
const tzMapper = [
x => x,
diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js
index 64a25c241..17eee4dd7 100644
--- a/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js
@@ -1,7 +1,7 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
// Generated by make_intl_data.py. DO NOT EDIT.
-// tzdata version = 2019c
+// tzdata version = 2021a
const tzMapper = [
x => x,
diff --git a/js/src/tests/ecma_2021/manual/test-replaceall.html b/js/src/tests/ecma_2021/manual/test-replaceall.html
new file mode 100644
index 000000000..9f9052549
--- /dev/null
+++ b/js/src/tests/ecma_2021/manual/test-replaceall.html
@@ -0,0 +1,63 @@
+<html><head>
+<script>
+function passfail(test) {
+if (test) {
+ document.writeln('<span style="color:#008000;">PASS</span>');
+} else {
+ document.writeln('<span style="color:#BF0000;">FAIL</span>');
+}
+}
+</script>
+</head>
+<body>
+Replace "a" with "b":
+<script>
+var input='1a234abcd';
+var match='a';
+var expected='1b234bbcd';
+passfail(input.replaceAll(match,'b') === expected);
+</script><br>
+Replace "a" with "aa":
+<script>
+var input='aabbccdd';
+var match='a';
+var expected='aaaabbccdd';
+passfail(input.replaceAll(match,'aa') === expected);
+</script><br>
+Replace "" with "b":
+<script>
+var input='aaaa';
+var match='';
+var expected='babababab';
+passfail(input.replaceAll(match,'b') === expected);
+</script><br>
+Replace "old" with special pattern "new (was: $&)":
+<script>
+var input='This is the old thing of the old time.';
+var match='old';
+var expected='This is the new (was: old) thing of the new (was: old) time.';
+passfail(input.replaceAll(match,'new (was: $&)') === expected);
+</script><br>
+Replace "/[0-9]/g" with "b":
+<script>
+var input='1a234abcd';
+var match=/[0-9]/g;
+var expected='babbbabcd';
+passfail(input.replaceAll(match,'b') === expected);
+</script><br>
+Replace "/[0-9]/" with "b" (Throws):
+<script>
+var input='1a234abcd';
+var match=/[0-9]/;
+var expected='1a234abcd';
+try {
+ test=input.replaceAll(match,'b');
+ passfail(false);
+} catch(e) {
+ passfail(true);
+ document.writeln('('+e+')');
+}
+</script><br>
+</body>
+</html>
+
diff --git a/js/src/tests/ecma_5/JSON/stringify.js b/js/src/tests/ecma_5/JSON/stringify.js
index 1a7e9b150..3b2147420 100644
--- a/js/src/tests/ecma_5/JSON/stringify.js
+++ b/js/src/tests/ecma_5/JSON/stringify.js
@@ -34,6 +34,9 @@ assertStringify({'mmm\\mmm':"hmm"}, '{"mmm\\\\mmm":"hmm"}');
assertStringify({'mmm\\mmm\\mmm':"hmm"}, '{"mmm\\\\mmm\\\\mmm":"hmm"}');
assertStringify({"mm\u000bmm":"hmm"}, '{"mm\\u000bmm":"hmm"}');
assertStringify({"mm\u0000mm":"hmm"}, '{"mm\\u0000mm":"hmm"}');
+assertStringify({"\u0000\u000b":""}, '{"\\u0000\\u000b":""}');
+assertStringify({"\u000b\ufdfd":"hmm"}, '{"\\u000b\ufdfd":"hmm"}');
+assertStringify({"\u000b\ufdfd":"h\xfc\ufdfdm"}, '{"\\u000b\ufdfd":"h\xfc\ufdfdm"}');
var x = {"free":"variable"};
assertStringify(x, '{"free":"variable"}');
diff --git a/js/src/tests/ecma_5/eval/line-terminator-paragraph-terminator.js b/js/src/tests/ecma_5/eval/line-terminator-paragraph-terminator.js
index 61047fb10..9a904073a 100644
--- a/js/src/tests/ecma_5/eval/line-terminator-paragraph-terminator.js
+++ b/js/src/tests/ecma_5/eval/line-terminator-paragraph-terminator.js
@@ -3,7 +3,9 @@
//-----------------------------------------------------------------------------
var BUGNUMBER = 657367;
-var summary = "eval must not parse strings containing U+2028 or U+2029";
+var summary =
+ "eval via the JSON parser should parse strings containing U+2028/U+2029 " +
+ "(as of <https://tc39.github.io/proposal-json-superset/>, that is)";
print(BUGNUMBER + ": " + summary);
@@ -11,59 +13,8 @@ print(BUGNUMBER + ": " + summary);
* BEGIN TEST *
**************/
-function esc(s)
-{
- return s.split("").map(function(v)
- {
- var code =
- ("000" + v.charCodeAt(0).toString(16)).slice(-4);
- return "\\u" + code;
- }).join("");
-}
-
-try
-{
- var r = eval('"\u2028"');
- throw new Error("\"\\u2028\" didn't throw, returned " + esc(r));
-}
-catch (e)
-{
- assertEq(e instanceof SyntaxError, true,
- "U+2028 is not a valid string character");
-}
-
-try
-{
- var r = eval('("\u2028")');
- throw new Error("(\"\\u2028\") didn't throw, returned " + esc(r));
-}
-catch (e)
-{
- assertEq(e instanceof SyntaxError, true,
- "U+2028 is not a valid string character");
-}
-
-try
-{
- var r = eval('"\u2029"');
- throw new Error("\"\\u2029\" didn't throw, returned " + esc(r));
-}
-catch (e)
-{
- assertEq(e instanceof SyntaxError, true,
- "U+2029 is not a valid string character");
-}
-
-try
-{
- var r = eval('("\u2029")');
- throw new Error("(\"\\u2029\") didn't throw, returned " + esc(r));
-}
-catch (e)
-{
- assertEq(e instanceof SyntaxError, true,
- "U+2029 is not a valid string character");
-}
+assertEq(eval('("\u2028")'), "\u2028");
+assertEq(eval('("\u2029")'), "\u2029");
/******************************************************************************/
diff --git a/js/src/vm/JSONParser.cpp b/js/src/vm/JSONParser.cpp
index 975fecb56..680a34fef 100644
--- a/js/src/vm/JSONParser.cpp
+++ b/js/src/vm/JSONParser.cpp
@@ -288,8 +288,10 @@ JSONParser<CharT>::readNumber()
double d;
const CharT* dummy;
- if (!GetPrefixInteger(cx, digitStart.get(), current.get(), 10, &dummy, &d))
+ if (!GetPrefixInteger(cx, digitStart.get(), current.get(), 10,
+ PrefixIntegerSeparatorHandling::None, &dummy, &d)) {
return token(OOM);
+ }
MOZ_ASSERT(current == dummy);
return numberToken(negative ? -d : d);
}
diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index df326a69e..38785a822 100644
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -191,6 +191,28 @@ intrinsic_IsInstanceOfBuiltin(JSContext* cx, unsigned argc, Value* vp)
template<typename T>
static bool
+intrinsic_IsPossiblyWrappedBuiltin(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+
+ bool isTypeT = false;
+ if (args[0].isObject()) {
+ JSObject* obj = CheckedUnwrap(&args[0].toObject());
+ if (!obj) {
+ JS_ReportErrorASCII(cx, "Permission denied to access object");
+ return false;
+ }
+
+ isTypeT = obj->is<T>();
+ }
+
+ args.rval().setBoolean(isTypeT);
+ return true;
+}
+
+template<typename T>
+static bool
intrinsic_GuardToBuiltin(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
@@ -1173,27 +1195,6 @@ intrinsic_IsFloat32TypedArray(JSContext* cx, unsigned argc, Value* vp)
}
static bool
-intrinsic_IsPossiblyWrappedTypedArray(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 1);
-
- bool isTypedArray = false;
- if (args[0].isObject()) {
- JSObject* obj = CheckedUnwrap(&args[0].toObject());
- if (!obj) {
- JS_ReportErrorASCII(cx, "Permission denied to access object");
- return false;
- }
-
- isTypedArray = obj->is<TypedArrayObject>();
- }
-
- args.rval().setBoolean(isTypedArray);
- return true;
-}
-
-static bool
intrinsic_TypedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
@@ -1756,6 +1757,24 @@ intrinsic_StringReplaceString(JSContext* cx, unsigned argc, Value* vp)
return true;
}
+static bool
+intrinsic_StringReplaceAllString(JSContext * cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+
+ RootedString string(cx, args[0].toString());
+ RootedString pattern(cx, args[1].toString());
+ RootedString replacement(cx, args[2].toString());
+ JSString* result = str_replaceAll_string_raw(cx, string, pattern, replacement);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setString(result);
+ return true;
+}
+
bool
js::intrinsic_StringSplitString(JSContext* cx, unsigned argc, Value* vp)
{
@@ -2156,105 +2175,6 @@ intrinsic_PromiseResolve(JSContext* cx, unsigned argc, Value* vp)
return true;
}
-static bool
-intrinsic_CreatePendingPromise(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 0);
- RootedObject promise(cx, PromiseObject::createSkippingExecutor(cx));
- if (!promise)
- return false;
- args.rval().setObject(*promise);
- return true;
-}
-
-static bool
-intrinsic_CreatePromiseResolvedWith(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 1);
- RootedObject promise(cx, PromiseObject::unforgeableResolve(cx, args[0]));
- if (!promise)
- return false;
- args.rval().setObject(*promise);
- return true;
-}
-
-static bool
-intrinsic_CreatePromiseRejectedWith(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 1);
- RootedObject promise(cx, PromiseObject::unforgeableReject(cx, args[0]));
- if (!promise)
- return false;
- args.rval().setObject(*promise);
- return true;
-}
-
-static bool
-intrinsic_ResolvePromise(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 2);
- Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>());
- if (!PromiseObject::resolve(cx, promise, args[1]))
- return false;
- args.rval().setUndefined();
- return true;
-}
-
-static bool
-intrinsic_RejectPromise(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 2);
- Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>());
- if (!PromiseObject::reject(cx, promise, args[1]))
- return false;
- args.rval().setUndefined();
- return true;
-}
-
-static bool
-intrinsic_CallOriginalPromiseThen(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() >= 2);
-
- RootedObject promise(cx, &args[0].toObject());
- Value val = args[1];
- RootedObject onResolvedObj(cx, val.isUndefined() ? nullptr : val.toObjectOrNull());
- val = args.get(2);
- RootedObject onRejectedObj(cx, val.isUndefined() ? nullptr : val.toObjectOrNull());
-
- RootedObject resultPromise(cx, JS::CallOriginalPromiseThen(cx, promise, onResolvedObj,
- onRejectedObj));
- if (!resultPromise)
- return false;
- args.rval().setObject(*resultPromise);
- return true;
-}
-
-static bool
-intrinsic_AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() >= 2);
-
- RootedObject promise(cx, &args[0].toObject());
- Value val = args[1];
- RootedObject onResolvedObj(cx, val.isUndefined() ? nullptr : val.toObjectOrNull());
- val = args.get(2);
- RootedObject onRejectedObj(cx, val.isUndefined() ? nullptr : val.toObjectOrNull());
-
- bool result = JS::AddPromiseReactions(cx, promise, onResolvedObj, onRejectedObj);
- if (!result)
- return false;
- args.rval().setUndefined();
- return true;
-}
-
// The self-hosting global isn't initialized with the normal set of builtins.
// Instead, individual C++-implemented functions that're required by
// self-hosted code are defined as global functions. Accessing these
@@ -2500,7 +2420,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_INLINABLE_FN("IsTypedArray",
intrinsic_IsInstanceOfBuiltin<TypedArrayObject>, 1,0,
IntrinsicIsTypedArray),
- JS_INLINABLE_FN("IsPossiblyWrappedTypedArray",intrinsic_IsPossiblyWrappedTypedArray,1,0,
+ JS_INLINABLE_FN("IsPossiblyWrappedTypedArray",intrinsic_IsPossiblyWrappedBuiltin<TypedArrayObject>,1,0,
IntrinsicIsPossiblyWrappedTypedArray),
JS_FN("TypedArrayBuffer", intrinsic_TypedArrayBuffer, 1,0),
@@ -2612,6 +2532,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_INLINABLE_FN("IsRegExpObject",
intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
IsRegExpObject),
+ JS_INLINABLE_FN("IsPossiblyWrappedRegExpObject", intrinsic_IsPossiblyWrappedBuiltin<RegExpObject>,1,0,
+ IntrinsicIsPossiblyWrappedRegExpObject),
JS_FN("CallRegExpMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<RegExpObject>>, 2,0),
JS_INLINABLE_FN("RegExpMatcher", RegExpMatcher, 4,0,
@@ -2635,6 +2557,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("FlatStringSearch", FlatStringSearch, 2,0),
JS_INLINABLE_FN("StringReplaceString", intrinsic_StringReplaceString, 3, 0,
IntrinsicStringReplaceString),
+ JS_FN("StringReplaceAllString", intrinsic_StringReplaceAllString, 3, 0),
JS_INLINABLE_FN("StringSplitString", intrinsic_StringSplitString, 2, 0,
IntrinsicStringSplitString),
JS_FN("StringSplitStringLimit", intrinsic_StringSplitStringLimit, 3, 0),
@@ -2659,14 +2582,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0),
JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0),
- JS_FN("CreatePendingPromise", intrinsic_CreatePendingPromise, 0, 0),
- JS_FN("CreatePromiseResolvedWith", intrinsic_CreatePromiseResolvedWith, 1, 0),
- JS_FN("CreatePromiseRejectedWith", intrinsic_CreatePromiseRejectedWith, 1, 0),
- JS_FN("ResolvePromise", intrinsic_ResolvePromise, 2, 0),
- JS_FN("RejectPromise", intrinsic_RejectPromise, 2, 0),
- JS_FN("AddPromiseReactions", intrinsic_AddPromiseReactions, 3, 0),
- JS_FN("CallOriginalPromiseThen", intrinsic_CallOriginalPromiseThen, 3, 0),
-
JS_FN("IsPromiseObject", intrinsic_IsInstanceOfBuiltin<PromiseObject>, 1, 0),
JS_FN("CallPromiseMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<PromiseObject>>, 2, 0),
JS_FN("PromiseResolve", intrinsic_PromiseResolve, 2, 0),
diff --git a/js/src/vm/StringBuffer.h b/js/src/vm/StringBuffer.h
index f2437384a..502a3bc6f 100644
--- a/js/src/vm/StringBuffer.h
+++ b/js/src/vm/StringBuffer.h
@@ -61,12 +61,8 @@ class StringBuffer
MOZ_ALWAYS_INLINE bool isLatin1() const { return cb.constructed<Latin1CharBuffer>(); }
MOZ_ALWAYS_INLINE bool isTwoByte() const { return !isLatin1(); }
- MOZ_ALWAYS_INLINE Latin1CharBuffer& latin1Chars() { return cb.ref<Latin1CharBuffer>(); }
MOZ_ALWAYS_INLINE TwoByteCharBuffer& twoByteChars() { return cb.ref<TwoByteCharBuffer>(); }
- MOZ_ALWAYS_INLINE const Latin1CharBuffer& latin1Chars() const {
- return cb.ref<Latin1CharBuffer>();
- }
MOZ_ALWAYS_INLINE const TwoByteCharBuffer& twoByteChars() const {
return cb.ref<TwoByteCharBuffer>();
}
@@ -84,6 +80,12 @@ class StringBuffer
cb.construct<Latin1CharBuffer>(cx);
}
+ MOZ_ALWAYS_INLINE Latin1CharBuffer& latin1Chars() { return cb.ref<Latin1CharBuffer>(); }
+
+ MOZ_ALWAYS_INLINE const Latin1CharBuffer& latin1Chars() const {
+ return cb.ref<Latin1CharBuffer>();
+ }
+
void clear() {
if (isLatin1())
latin1Chars().clear();
@@ -134,6 +136,11 @@ class StringBuffer
return append(Latin1Char(c));
}
+ TwoByteCharBuffer& rawTwoByteBuffer() {
+ MOZ_ASSERT(hasEnsuredTwoByteChars_);
+ return twoByteChars();
+ }
+
inline MOZ_MUST_USE bool append(const char16_t* begin, const char16_t* end);
MOZ_MUST_USE bool append(const char16_t* chars, size_t len) {
diff --git a/js/src/vm/Unicode.h b/js/src/vm/Unicode.h
index e470f4341..d8807a4de 100644
--- a/js/src/vm/Unicode.h
+++ b/js/src/vm/Unicode.h
@@ -466,6 +466,19 @@ IsTrailSurrogate(uint32_t codePoint)
return codePoint >= TrailSurrogateMin && codePoint <= TrailSurrogateMax;
}
+/**
+ * Returns true if the given value is a UTF-16 surrogate.
+ *
+ * This function is intended to be used in contexts where 32-bit values may
+ * need to be tested to see if they reside in the surrogate range, so it
+ * doesn't just take char16_t.
+ */
+inline bool
+IsSurrogate(uint32_t codePoint)
+{
+ return LeadSurrogateMin <= codePoint && codePoint <= TrailSurrogateMax;
+}
+
inline char16_t
LeadSurrogate(uint32_t codePoint)
{