diff options
Diffstat (limited to 'js/src/builtin/temporal/TemporalFields.cpp')
-rw-r--r-- | js/src/builtin/temporal/TemporalFields.cpp | 939 |
1 files changed, 939 insertions, 0 deletions
diff --git a/js/src/builtin/temporal/TemporalFields.cpp b/js/src/builtin/temporal/TemporalFields.cpp new file mode 100644 index 0000000000..9ac2e44639 --- /dev/null +++ b/js/src/builtin/temporal/TemporalFields.cpp @@ -0,0 +1,939 @@ +/* -*- 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 <algorithm> +#include <cstring> +#include <iterator> +#include <stdint.h> +#include <utility> + +#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<const Latin1Char*>(str), + std::strlen(str)); + QuoteString<QuoteTarget::String>(&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<TemporalField> 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> value) { + Rooted<Value> 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> 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<char16_t> end(std::end(buf), buf, std::end(buf)); + mozilla::RangedPtr<char16_t> 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<TemporalField> 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<JSObject*> fields, + std::initializer_list<TemporalField> fieldNames, + std::initializer_list<TemporalField> requiredFields, + MutableHandle<TemporalFields> 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> 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<JSObject*> fields, + Handle<TemporalFieldNames> fieldNames) { + // Step 1. (Not applicable in our implementation.) + + // Step 2. + Rooted<PlainObject*> 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> value(cx); + for (size_t i = 0; i < fieldNames.length(); i++) { + Handle<PropertyKey> 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<JSObject*> fields, + Handle<TemporalFieldNames> fieldNames, + std::initializer_list<TemporalField> requiredFields) { + // Step 1. (Not applicable in our implementation.) + + // Step 2. + Rooted<PlainObject*> 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> value(cx); + for (size_t i = 0; i < fieldNames.length(); i++) { + Handle<PropertyKey> 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<JSObject*> fields, + Handle<TemporalFieldNames> fieldNames) { + // Step 1. (Not applicable in our implementation.) + + // Step 2. + Rooted<PlainObject*> 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> value(cx); + for (size_t i = 0; i < fieldNames.length(); i++) { + Handle<PropertyKey> 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<TemporalField> 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; +} |