/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "builtin/temporal/TemporalFields.h" #include "mozilla/Assertions.h" #include "mozilla/Likely.h" #include "mozilla/Maybe.h" #include "mozilla/Range.h" #include "mozilla/RangedPtr.h" #include #include #include #include #include #include "jsnum.h" #include "jspubtd.h" #include "NamespaceImports.h" #include "builtin/temporal/Temporal.h" #include "ds/Sort.h" #include "gc/Barrier.h" #include "gc/Tracer.h" #include "js/AllocPolicy.h" #include "js/ComparisonOperators.h" #include "js/ErrorReport.h" #include "js/friend/ErrorMessages.h" #include "js/GCVector.h" #include "js/Id.h" #include "js/Printer.h" #include "js/RootingAPI.h" #include "js/TracingAPI.h" #include "js/TypeDecls.h" #include "js/Utility.h" #include "js/Value.h" #include "util/Text.h" #include "vm/BytecodeUtil.h" #include "vm/JSAtomState.h" #include "vm/JSContext.h" #include "vm/JSObject.h" #include "vm/PlainObject.h" #include "vm/StringType.h" #include "vm/SymbolType.h" #include "vm/JSAtomUtils-inl.h" #include "vm/ObjectOperations-inl.h" using namespace js; using namespace js::temporal; void TemporalFields::trace(JSTracer* trc) { TraceNullableRoot(trc, &monthCode, "TemporalFields::monthCode"); TraceNullableRoot(trc, &offset, "TemporalFields::offset"); TraceNullableRoot(trc, &era, "TemporalFields::era"); TraceRoot(trc, &timeZone, "TemporalFields::timeZone"); } static PropertyName* ToPropertyName(JSContext* cx, TemporalField field) { switch (field) { case TemporalField::Year: return cx->names().year; case TemporalField::Month: return cx->names().month; case TemporalField::MonthCode: return cx->names().monthCode; case TemporalField::Day: return cx->names().day; case TemporalField::Hour: return cx->names().hour; case TemporalField::Minute: return cx->names().minute; case TemporalField::Second: return cx->names().second; case TemporalField::Millisecond: return cx->names().millisecond; case TemporalField::Microsecond: return cx->names().microsecond; case TemporalField::Nanosecond: return cx->names().nanosecond; case TemporalField::Offset: return cx->names().offset; case TemporalField::Era: return cx->names().era; case TemporalField::EraYear: return cx->names().eraYear; case TemporalField::TimeZone: return cx->names().timeZone; } MOZ_CRASH("invalid temporal field name"); } static const char* ToCString(TemporalField field) { switch (field) { case TemporalField::Year: return "year"; case TemporalField::Month: return "month"; case TemporalField::MonthCode: return "monthCode"; case TemporalField::Day: return "day"; case TemporalField::Hour: return "hour"; case TemporalField::Minute: return "minute"; case TemporalField::Second: return "second"; case TemporalField::Millisecond: return "millisecond"; case TemporalField::Microsecond: return "microsecond"; case TemporalField::Nanosecond: return "nanosecond"; case TemporalField::Offset: return "offset"; case TemporalField::Era: return "era"; case TemporalField::EraYear: return "eraYear"; case TemporalField::TimeZone: return "timeZone"; } MOZ_CRASH("invalid temporal field name"); } static JS::UniqueChars QuoteString(JSContext* cx, const char* str) { Sprinter sprinter(cx); if (!sprinter.init()) { return nullptr; } mozilla::Range range(reinterpret_cast(str), std::strlen(str)); QuoteString(&sprinter, range); return sprinter.release(); } static JS::UniqueChars QuoteString(JSContext* cx, PropertyKey key) { if (key.isString()) { return QuoteString(cx, key.toString()); } if (key.isInt()) { Int32ToCStringBuf buf; size_t length; const char* str = Int32ToCString(&buf, key.toInt(), &length); return DuplicateString(cx, str, length); } MOZ_ASSERT(key.isSymbol()); return QuoteString(cx, key.toSymbol()->description()); } static mozilla::Maybe ToTemporalField(JSContext* cx, PropertyKey property) { static constexpr TemporalField fieldNames[] = { TemporalField::Year, TemporalField::Month, TemporalField::MonthCode, TemporalField::Day, TemporalField::Hour, TemporalField::Minute, TemporalField::Second, TemporalField::Millisecond, TemporalField::Microsecond, TemporalField::Nanosecond, TemporalField::Offset, TemporalField::Era, TemporalField::EraYear, TemporalField::TimeZone, }; for (const auto& fieldName : fieldNames) { auto* name = ToPropertyName(cx, fieldName); if (property.isAtom(name)) { return mozilla::Some(fieldName); } } return mozilla::Nothing(); } static JSString* ToPrimitiveAndRequireString(JSContext* cx, Handle value) { Rooted primitive(cx, value); if (!ToPrimitive(cx, JSTYPE_STRING, &primitive)) { return nullptr; } if (!primitive.isString()) { ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, primitive, nullptr, "not a string"); return nullptr; } return primitive.toString(); } static Value TemporalFieldDefaultValue(TemporalField field) { switch (field) { case TemporalField::Year: case TemporalField::Month: case TemporalField::MonthCode: case TemporalField::Day: case TemporalField::Offset: case TemporalField::Era: case TemporalField::EraYear: case TemporalField::TimeZone: return UndefinedValue(); case TemporalField::Hour: case TemporalField::Minute: case TemporalField::Second: case TemporalField::Millisecond: case TemporalField::Microsecond: case TemporalField::Nanosecond: return Int32Value(0); } MOZ_CRASH("invalid temporal field name"); } static bool TemporalFieldConvertValue(JSContext* cx, TemporalField field, MutableHandle value) { auto* name = ToCString(field); switch (field) { case TemporalField::Year: case TemporalField::Hour: case TemporalField::Minute: case TemporalField::Second: case TemporalField::Millisecond: case TemporalField::Microsecond: case TemporalField::Nanosecond: case TemporalField::EraYear: { double num; if (!ToIntegerWithTruncation(cx, value, name, &num)) { return false; } value.setNumber(num); return true; } case TemporalField::Month: case TemporalField::Day: { double num; if (!ToPositiveIntegerWithTruncation(cx, value, name, &num)) { return false; } value.setNumber(num); return true; } case TemporalField::MonthCode: case TemporalField::Offset: case TemporalField::Era: { JSString* str = ToPrimitiveAndRequireString(cx, value); if (!str) { return false; } value.setString(str); return true; } case TemporalField::TimeZone: // NB: timeZone has no conversion function. return true; } MOZ_CRASH("invalid temporal field name"); } static int32_t ComparePropertyKey(PropertyKey x, PropertyKey y) { MOZ_ASSERT(x.isAtom() || x.isInt()); MOZ_ASSERT(y.isAtom() || y.isInt()); if (MOZ_LIKELY(x.isAtom() && y.isAtom())) { return CompareStrings(x.toAtom(), y.toAtom()); } if (x.isInt() && y.isInt()) { return x.toInt() - y.toInt(); } uint32_t index = uint32_t(x.isInt() ? x.toInt() : y.toInt()); JSAtom* str = x.isAtom() ? x.toAtom() : y.toAtom(); char16_t buf[UINT32_CHAR_BUFFER_LENGTH]; mozilla::RangedPtr end(std::end(buf), buf, std::end(buf)); mozilla::RangedPtr start = BackfillIndexInCharBuffer(index, end); int32_t result = CompareChars(start.get(), end - start, str); return x.isInt() ? result : -result; } #ifdef DEBUG static bool IsSorted(std::initializer_list fieldNames) { return std::is_sorted(fieldNames.begin(), fieldNames.end(), [](auto x, auto y) { auto* a = ToCString(x); auto* b = ToCString(y); return std::strcmp(a, b) < 0; }); } static bool IsSorted(const TemporalFieldNames& fieldNames) { return std::is_sorted( fieldNames.begin(), fieldNames.end(), [](auto x, auto y) { return ComparePropertyKey(x, y) < 0; }); } #endif // clang-format off // // TODO: |fields| is often a built-in Temporal type, so we likely want to // optimise for this case. // // Consider the case when PlainDate.prototype.toPlainMonthDay is called. The // following steps are applied: // // 1. CalendarFields(calendar, «"day", "monthCode"») is called to retrieve the // relevant calendar fields. For (most?) built-in calendars this will just // return the input list «"day", "monthCode"». // 2. PrepareTemporalFields(plainDate, «"day", "monthCode"») is called. This // will access the properties `plainDate.day` and `plainDate.monthCode`. // a. `plainDate.day` will call CalendarDay(calendar, plainDate). // b. For built-in calendars, this will simply access `plainDate.[[IsoDay]]`. // c. `plainDate.monthCode` will call CalendarMonthCode(calendar, plainDate). // d. For built-in calendars, ISOMonthCode(plainDate.[[IsoMonth]]) is called. // 3. CalendarMonthDayFromFields(calendar, {day, monthCode}) is called. // 4. For built-in calendars, this calls PrepareTemporalFields({day, monthCode}, // «"day", "month", "monthCode", "year"», «"day"»). // 5. The previous PrepareTemporalFields call is a no-op and returns {day, monthCode}. // 6. Then ISOMonthDayFromFields({day, monthCode}, "constrain") gets called. // 7. ResolveISOMonth(monthCode) is called to parse the just created `monthCode`. // 8. RegulateISODate(referenceISOYear, month, day, "constrain") is called. // 9. Finally CreateTemporalMonthDay is called to create the PlainMonthDay instance. // // All these steps could be simplified to just: // 1. CreateTemporalMonthDay(referenceISOYear, plainDate.[[IsoMonth]], plainDate.[[IsoDay]]). // // When the following conditions are true: // 1. The `plainDate` is a Temporal.PlainDate instance and has no overridden methods. // 2. The `calendar` is a Temporal.Calendar instance and has no overridden methods. // 3. Temporal.PlainDate.prototype and Temporal.Calendar.prototype are in their initial state. // 4. Array iteration is still in its initial state. (Required by CalendarFields) // // PlainDate_toPlainMonthDay has an example implementation for this optimisation. // // clang-format on /** * PrepareTemporalFields ( fields, fieldNames, requiredFields ) */ bool js::temporal::PrepareTemporalFields( JSContext* cx, Handle fields, std::initializer_list fieldNames, std::initializer_list requiredFields, MutableHandle result) { // Steps 1-3. (Not applicable in our implementation.) // Step 4. (|fieldNames| is sorted in our implementation.) MOZ_ASSERT(IsSorted(fieldNames)); // Step 5. (The list doesn't contain duplicates in our implementation.) MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) == fieldNames.end()); // |requiredFields| is sorted and doesn't contain any duplicate elements. MOZ_ASSERT(IsSorted(requiredFields)); MOZ_ASSERT(std::adjacent_find(requiredFields.begin(), requiredFields.end()) == requiredFields.end()); // Step 6. Rooted value(cx); for (auto fieldName : fieldNames) { auto* property = ToPropertyName(cx, fieldName); auto* cstr = ToCString(fieldName); // Step 6.a. (Not applicable in our implementation.) // Step 6.b.i. if (!GetProperty(cx, fields, fields, property, &value)) { return false; } // Steps 6.b.ii-iii. if (!value.isUndefined()) { // Step 6.b.ii.1. (Not applicable in our implementation.) // Steps 6.b.ii.2-3. switch (fieldName) { case TemporalField::Year: if (!ToIntegerWithTruncation(cx, value, cstr, &result.year())) { return false; } break; case TemporalField::Month: if (!ToPositiveIntegerWithTruncation(cx, value, cstr, &result.month())) { return false; } break; case TemporalField::MonthCode: { JSString* str = ToPrimitiveAndRequireString(cx, value); if (!str) { return false; } result.monthCode().set(str); break; } case TemporalField::Day: if (!ToPositiveIntegerWithTruncation(cx, value, cstr, &result.day())) { return false; } break; case TemporalField::Hour: if (!ToIntegerWithTruncation(cx, value, cstr, &result.hour())) { return false; } break; case TemporalField::Minute: if (!ToIntegerWithTruncation(cx, value, cstr, &result.minute())) { return false; } break; case TemporalField::Second: if (!ToIntegerWithTruncation(cx, value, cstr, &result.second())) { return false; } break; case TemporalField::Millisecond: if (!ToIntegerWithTruncation(cx, value, cstr, &result.millisecond())) { return false; } break; case TemporalField::Microsecond: if (!ToIntegerWithTruncation(cx, value, cstr, &result.microsecond())) { return false; } break; case TemporalField::Nanosecond: if (!ToIntegerWithTruncation(cx, value, cstr, &result.nanosecond())) { return false; } break; case TemporalField::Offset: { JSString* str = ToPrimitiveAndRequireString(cx, value); if (!str) { return false; } result.offset().set(str); break; } case TemporalField::Era: { JSString* str = ToPrimitiveAndRequireString(cx, value); if (!str) { return false; } result.era().set(str); break; } case TemporalField::EraYear: if (!ToIntegerWithTruncation(cx, value, cstr, &result.eraYear())) { return false; } break; case TemporalField::TimeZone: // NB: TemporalField::TimeZone has no conversion function. result.timeZone().set(value); break; } } else { // Step 6.b.iii.1. if (std::find(requiredFields.begin(), requiredFields.end(), fieldName) != requiredFields.end()) { if (auto chars = QuoteString(cx, cstr)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_MISSING_PROPERTY, chars.get()); } return false; } // `const` can be changed to `constexpr` when we switch to C++20. const TemporalFields FallbackValues{}; // Steps 6.b.iii.2-3. switch (fieldName) { case TemporalField::Year: result.year() = FallbackValues.year; break; case TemporalField::Month: result.month() = FallbackValues.month; break; case TemporalField::MonthCode: result.monthCode().set(FallbackValues.monthCode); break; case TemporalField::Day: result.day() = FallbackValues.day; break; case TemporalField::Hour: result.hour() = FallbackValues.hour; break; case TemporalField::Minute: result.minute() = FallbackValues.minute; break; case TemporalField::Second: result.second() = FallbackValues.second; break; case TemporalField::Millisecond: result.millisecond() = FallbackValues.millisecond; break; case TemporalField::Microsecond: result.microsecond() = FallbackValues.microsecond; break; case TemporalField::Nanosecond: result.nanosecond() = FallbackValues.nanosecond; break; case TemporalField::Offset: result.offset().set(FallbackValues.offset); break; case TemporalField::Era: result.era().set(FallbackValues.era); break; case TemporalField::EraYear: result.eraYear() = FallbackValues.eraYear; break; case TemporalField::TimeZone: result.timeZone().set(FallbackValues.timeZone); break; } } // Steps 6.c-d. (Not applicable in our implementation.) } // Step 7. (Not applicable in our implementation.) // Step 8. return true; } /** * PrepareTemporalFields ( fields, fieldNames, requiredFields [ , * duplicateBehaviour ] ) */ PlainObject* js::temporal::PrepareTemporalFields( JSContext* cx, Handle fields, Handle fieldNames) { // Step 1. (Not applicable in our implementation.) // Step 2. Rooted result(cx, NewPlainObjectWithProto(cx, nullptr)); if (!result) { return nullptr; } // Step 3. (Not applicable in our implementation.) // Step 4. (The list is already sorted in our implementation.) MOZ_ASSERT(IsSorted(fieldNames)); // Step 5. (The list doesn't contain duplicates in our implementation.) MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) == fieldNames.end()); // Step 6. Rooted value(cx); for (size_t i = 0; i < fieldNames.length(); i++) { Handle property = fieldNames[i]; // Step 6.a. MOZ_ASSERT(property != NameToId(cx->names().constructor)); MOZ_ASSERT(property != NameToId(cx->names().proto_)); // Step 6.b.i. if (!GetProperty(cx, fields, fields, property, &value)) { return nullptr; } // Steps 6.b.ii-iii. if (auto fieldName = ToTemporalField(cx, property)) { if (!value.isUndefined()) { // Step 6.b.ii.1. (Not applicable in our implementation.) // Step 6.b.ii.2. if (!TemporalFieldConvertValue(cx, *fieldName, &value)) { return nullptr; } } else { // Step 6.b.iii.1. (Not applicable in our implementation.) // Step 6.b.iii.2. value = TemporalFieldDefaultValue(*fieldName); } } // Steps 6.b.ii.3 and 6.b.iii.3. if (!DefineDataProperty(cx, result, property, value)) { return nullptr; } // Steps 6.c-d. (Not applicable in our implementation.) } // Step 7. (Not applicable in our implementation.) // Step 8. return result; } /** * PrepareTemporalFields ( fields, fieldNames, requiredFields [ , * duplicateBehaviour ] ) */ PlainObject* js::temporal::PrepareTemporalFields( JSContext* cx, Handle fields, Handle fieldNames, std::initializer_list requiredFields) { // Step 1. (Not applicable in our implementation.) // Step 2. Rooted result(cx, NewPlainObjectWithProto(cx, nullptr)); if (!result) { return nullptr; } // Step 3. (Not applicable in our implementation.) // Step 4. (The list is already sorted in our implementation.) MOZ_ASSERT(IsSorted(fieldNames)); // Step 5. (The list doesn't contain duplicates in our implementation.) MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) == fieldNames.end()); // |requiredFields| is sorted and doesn't include any duplicate elements. MOZ_ASSERT(IsSorted(requiredFields)); MOZ_ASSERT(std::adjacent_find(requiredFields.begin(), requiredFields.end()) == requiredFields.end()); // Step 6. Rooted value(cx); for (size_t i = 0; i < fieldNames.length(); i++) { Handle property = fieldNames[i]; // Step 6.a. MOZ_ASSERT(property != NameToId(cx->names().constructor)); MOZ_ASSERT(property != NameToId(cx->names().proto_)); // Step 6.b.i. if (!GetProperty(cx, fields, fields, property, &value)) { return nullptr; } // Steps 6.b.ii-iii. if (auto fieldName = ToTemporalField(cx, property)) { if (!value.isUndefined()) { // Step 6.b.ii.1. (Not applicable in our implementation.) // Step 6.b.ii.2. if (!TemporalFieldConvertValue(cx, *fieldName, &value)) { return nullptr; } } else { // Step 6.b.iii.1. if (std::find(requiredFields.begin(), requiredFields.end(), *fieldName) != requiredFields.end()) { if (auto chars = QuoteString(cx, property.toString())) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_MISSING_PROPERTY, chars.get()); } return nullptr; } // Step 6.b.iii.2. value = TemporalFieldDefaultValue(*fieldName); } } // Steps 6.b.ii.3 and 6.b.iii.3. if (!DefineDataProperty(cx, result, property, value)) { return nullptr; } // Steps 6.c-d. (Not applicable in our implementation.) } // Step 7. (Not applicable in our implementation.) // Step 8. return result; } /** * PrepareTemporalFields ( fields, fieldNames, requiredFields [ , * duplicateBehaviour ] ) */ PlainObject* js::temporal::PreparePartialTemporalFields( JSContext* cx, Handle fields, Handle fieldNames) { // Step 1. (Not applicable in our implementation.) // Step 2. Rooted result(cx, NewPlainObjectWithProto(cx, nullptr)); if (!result) { return nullptr; } // Step 3. bool any = false; // Step 4. (The list is already sorted in our implementation.) MOZ_ASSERT(IsSorted(fieldNames)); // Step 5. (The list doesn't contain duplicates in our implementation.) MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) == fieldNames.end()); // Step 6. Rooted value(cx); for (size_t i = 0; i < fieldNames.length(); i++) { Handle property = fieldNames[i]; // Step 6.a. MOZ_ASSERT(property != NameToId(cx->names().constructor)); MOZ_ASSERT(property != NameToId(cx->names().proto_)); // Step 6.b.i. if (!GetProperty(cx, fields, fields, property, &value)) { return nullptr; } // Steps 6.b.ii-iii. if (!value.isUndefined()) { // Step 6.b.ii.1. any = true; // Step 6.b.ii.2. if (auto fieldName = ToTemporalField(cx, property)) { if (!TemporalFieldConvertValue(cx, *fieldName, &value)) { return nullptr; } } // Steps 6.b.ii.3. if (!DefineDataProperty(cx, result, property, value)) { return nullptr; } } else { // Step 6.b.iii. (Not applicable in our implementation.) } // Steps 6.c-d. (Not applicable in our implementation.) } // Step 7. if (!any) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_MISSING_TEMPORAL_FIELDS); return nullptr; } // Step 8. return result; } /** * Performs list-concatenation, removes any duplicates, and sorts the result. */ bool js::temporal::ConcatTemporalFieldNames( const TemporalFieldNames& receiverFieldNames, const TemporalFieldNames& inputFieldNames, TemporalFieldNames& concatenatedFieldNames) { MOZ_ASSERT(IsSorted(receiverFieldNames)); MOZ_ASSERT(IsSorted(inputFieldNames)); MOZ_ASSERT(concatenatedFieldNames.empty()); auto appendUnique = [&](auto key) { if (concatenatedFieldNames.empty() || concatenatedFieldNames.back() != key) { return concatenatedFieldNames.append(key); } return true; }; size_t i = 0; size_t j = 0; // Append the names from |receiverFieldNames| and |inputFieldNames|. while (i < receiverFieldNames.length() && j < inputFieldNames.length()) { auto x = receiverFieldNames[i]; auto y = inputFieldNames[j]; PropertyKey z; if (ComparePropertyKey(x, y) <= 0) { z = x; i++; } else { z = y; j++; } if (!appendUnique(z)) { return false; } } // Append the remaining names from |receiverFieldNames|. while (i < receiverFieldNames.length()) { if (!appendUnique(receiverFieldNames[i++])) { return false; } } // Append the remaining names from |inputFieldNames|. while (j < inputFieldNames.length()) { if (!appendUnique(inputFieldNames[j++])) { return false; } } return true; } bool js::temporal::AppendSorted( JSContext* cx, TemporalFieldNames& fieldNames, std::initializer_list additionalNames) { // |fieldNames| is sorted and doesn't include any duplicates MOZ_ASSERT(IsSorted(fieldNames)); MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) == fieldNames.end()); // |additionalNames| is non-empty, sorted, and doesn't include any duplicates. MOZ_ASSERT(additionalNames.size() > 0); MOZ_ASSERT(IsSorted(additionalNames)); MOZ_ASSERT( std::adjacent_find(additionalNames.begin(), additionalNames.end()) == additionalNames.end()); // Allocate space for entries from |additionalNames|. if (!fieldNames.growBy(additionalNames.size())) { return false; } auto* left = std::prev(fieldNames.end(), additionalNames.size()); auto* right = additionalNames.end(); auto* out = fieldNames.end(); // Write backwards into the newly allocated space. while (left != fieldNames.begin() && right != additionalNames.begin()) { MOZ_ASSERT(out != fieldNames.begin()); auto x = *std::prev(left); auto y = NameToId(ToPropertyName(cx, *std::prev(right))); int32_t r = ComparePropertyKey(x, y); // Reject duplicates per PrepareTemporalFields, step 6.c. if (r == 0) { if (auto chars = QuoteString(cx, x)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_DUPLICATE_PROPERTY, chars.get()); } return false; } // Insert the lexicographically greater key. PropertyKey z; if (r > 0) { z = x; left--; } else { z = y; right--; } *--out = z; } // Avoid unnecessary copying if possible. if (left == out) { MOZ_ASSERT(right == additionalNames.begin()); return true; } // Prepend the remaining names from |fieldNames|. while (left != fieldNames.begin()) { MOZ_ASSERT(out != fieldNames.begin()); *--out = *--left; } // Prepend the remaining names from |additionalNames|. while (right != additionalNames.begin()) { MOZ_ASSERT(out != fieldNames.begin()); *--out = NameToId(ToPropertyName(cx, *--right)); } // All field names were written into the result list. MOZ_ASSERT(out == fieldNames.begin()); return true; } bool js::temporal::SortTemporalFieldNames(JSContext* cx, TemporalFieldNames& fieldNames) { // Create scratch space for MergeSort(). TemporalFieldNames scratch(cx); if (!scratch.resize(fieldNames.length())) { return false; } // Sort all field names in alphabetical order. auto comparator = [](const auto& x, const auto& y, bool* lessOrEqual) { *lessOrEqual = ComparePropertyKey(x, y) <= 0; return true; }; MOZ_ALWAYS_TRUE(MergeSort(fieldNames.begin(), fieldNames.length(), scratch.begin(), comparator)); for (size_t i = 0; i < fieldNames.length(); i++) { auto property = fieldNames[i]; // Reject "constructor" and "__proto__" per PrepareTemporalFields, step 6.a. if (property == NameToId(cx->names().constructor) || property == NameToId(cx->names().proto_)) { if (auto chars = QuoteString(cx, property)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INVALID_PROPERTY, chars.get()); } return false; } // Reject duplicates per PrepareTemporalFields, step 6.c. if (i > 0 && property == fieldNames[i - 1]) { if (auto chars = QuoteString(cx, property)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_DUPLICATE_PROPERTY, chars.get()); } return false; } } return true; }