diff options
Diffstat (limited to 'js')
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) { |