tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CalendarFields.cpp (18099B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "builtin/temporal/CalendarFields.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/EnumTypeTraits.h"
     11 #include "mozilla/Maybe.h"
     12 #include "mozilla/Range.h"
     13 #include "mozilla/TextUtils.h"
     14 
     15 #include <stdint.h>
     16 #include <string_view>
     17 
     18 #include "jspubtd.h"
     19 #include "NamespaceImports.h"
     20 
     21 #include "builtin/temporal/Calendar.h"
     22 #include "builtin/temporal/Era.h"
     23 #include "builtin/temporal/Temporal.h"
     24 #include "builtin/temporal/TemporalParser.h"
     25 #include "builtin/temporal/TimeZone.h"
     26 #include "gc/Barrier.h"
     27 #include "gc/Tracer.h"
     28 #include "js/Conversions.h"
     29 #include "js/ErrorReport.h"
     30 #include "js/friend/ErrorMessages.h"
     31 #include "js/GCAPI.h"
     32 #include "js/Printer.h"
     33 #include "js/RootingAPI.h"
     34 #include "js/Value.h"
     35 #include "util/Text.h"
     36 #include "vm/BytecodeUtil.h"
     37 #include "vm/JSAtomState.h"
     38 #include "vm/JSContext.h"
     39 #include "vm/JSObject.h"
     40 #include "vm/StringType.h"
     41 
     42 #include "vm/JSObject-inl.h"
     43 #include "vm/ObjectOperations-inl.h"
     44 
     45 using namespace js;
     46 using namespace js::temporal;
     47 
     48 void CalendarFields::trace(JSTracer* trc) {
     49  TraceNullableRoot(trc, &era_, "CalendarFields::era");
     50  timeZone_.trace(trc);
     51 }
     52 
     53 void CalendarFields::setFrom(CalendarField field,
     54                             const CalendarFields& source) {
     55  MOZ_ASSERT(source.has(field));
     56 
     57  switch (field) {
     58    case CalendarField::Era:
     59      setEra(source.era());
     60      return;
     61    case CalendarField::EraYear:
     62      setEraYear(source.eraYear());
     63      return;
     64    case CalendarField::Year:
     65      setYear(source.year());
     66      return;
     67    case CalendarField::Month:
     68      setMonth(source.month());
     69      return;
     70    case CalendarField::MonthCode:
     71      setMonthCode(source.monthCode());
     72      return;
     73    case CalendarField::Day:
     74      setDay(source.day());
     75      return;
     76    case CalendarField::Hour:
     77      setHour(source.hour());
     78      return;
     79    case CalendarField::Minute:
     80      setMinute(source.minute());
     81      return;
     82    case CalendarField::Second:
     83      setSecond(source.second());
     84      return;
     85    case CalendarField::Millisecond:
     86      setMillisecond(source.millisecond());
     87      return;
     88    case CalendarField::Microsecond:
     89      setMicrosecond(source.microsecond());
     90      return;
     91    case CalendarField::Nanosecond:
     92      setNanosecond(source.nanosecond());
     93      return;
     94    case CalendarField::Offset:
     95      setOffset(source.offset());
     96      return;
     97    case CalendarField::TimeZone:
     98      setTimeZone(source.timeZone());
     99      return;
    100  }
    101  MOZ_CRASH("invalid temporal field");
    102 }
    103 
    104 static PropertyName* ToPropertyName(JSContext* cx, CalendarField field) {
    105  switch (field) {
    106    case CalendarField::Era:
    107      return cx->names().era;
    108    case CalendarField::EraYear:
    109      return cx->names().eraYear;
    110    case CalendarField::Year:
    111      return cx->names().year;
    112    case CalendarField::Month:
    113      return cx->names().month;
    114    case CalendarField::MonthCode:
    115      return cx->names().monthCode;
    116    case CalendarField::Day:
    117      return cx->names().day;
    118    case CalendarField::Hour:
    119      return cx->names().hour;
    120    case CalendarField::Minute:
    121      return cx->names().minute;
    122    case CalendarField::Second:
    123      return cx->names().second;
    124    case CalendarField::Millisecond:
    125      return cx->names().millisecond;
    126    case CalendarField::Microsecond:
    127      return cx->names().microsecond;
    128    case CalendarField::Nanosecond:
    129      return cx->names().nanosecond;
    130    case CalendarField::Offset:
    131      return cx->names().offset;
    132    case CalendarField::TimeZone:
    133      return cx->names().timeZone;
    134  }
    135  MOZ_CRASH("invalid temporal field name");
    136 }
    137 
    138 static constexpr const char* ToCString(CalendarField field) {
    139  switch (field) {
    140    case CalendarField::Era:
    141      return "era";
    142    case CalendarField::EraYear:
    143      return "eraYear";
    144    case CalendarField::Year:
    145      return "year";
    146    case CalendarField::Month:
    147      return "month";
    148    case CalendarField::MonthCode:
    149      return "monthCode";
    150    case CalendarField::Day:
    151      return "day";
    152    case CalendarField::Hour:
    153      return "hour";
    154    case CalendarField::Minute:
    155      return "minute";
    156    case CalendarField::Second:
    157      return "second";
    158    case CalendarField::Millisecond:
    159      return "millisecond";
    160    case CalendarField::Microsecond:
    161      return "microsecond";
    162    case CalendarField::Nanosecond:
    163      return "nanosecond";
    164    case CalendarField::Offset:
    165      return "offset";
    166    case CalendarField::TimeZone:
    167      return "timeZone";
    168  }
    169  MOZ_CRASH("invalid temporal field name");
    170 }
    171 
    172 static constexpr bool CalendarFieldsAreSorted() {
    173  constexpr auto min = mozilla::ContiguousEnumValues<CalendarField>::min;
    174  constexpr auto max = mozilla::ContiguousEnumValues<CalendarField>::max;
    175 
    176  auto field = min;
    177  while (field != max) {
    178    auto next = static_cast<CalendarField>(mozilla::UnderlyingValue(field) + 1);
    179 
    180    auto a = std::string_view{ToCString(field)};
    181    auto b = std::string_view{ToCString(next)};
    182    if (a.compare(b) >= 0) {
    183      return false;
    184    }
    185    field = next;
    186  }
    187  return true;
    188 }
    189 
    190 /**
    191 * CalendarExtraFields ( calendar, fields )
    192 */
    193 static mozilla::EnumSet<CalendarField> CalendarExtraFields(
    194    CalendarId calendar, mozilla::EnumSet<CalendarField> fields) {
    195  // Step 1.
    196  if (calendar == CalendarId::ISO8601) {
    197    return {};
    198  }
    199 
    200  // Step 2.
    201 
    202  // "era" and "eraYear" are relevant for calendars supporting eras when
    203  // "year" is present.
    204  if (fields.contains(CalendarField::Year) && CalendarSupportsEra(calendar)) {
    205    return {CalendarField::Era, CalendarField::EraYear};
    206  }
    207  return {};
    208 }
    209 
    210 /**
    211 * ParseMonthCode ( argument )
    212 */
    213 template <typename CharT>
    214 static mozilla::Maybe<MonthCodeField> ParseMonthCode(
    215    mozilla::Range<const CharT> chars) {
    216  // Steps 1-2. (Not applicable)
    217 
    218  // Steps 3-6.
    219  if (chars.length() < 3 || chars.length() > 4) {
    220    return mozilla::Nothing();
    221  }
    222 
    223  // Starts with capital letter 'M'. Leap months end with capital letter 'L'.
    224  bool isLeapMonth = chars.length() == 4;
    225  if (chars[0] != 'M' || (isLeapMonth && chars[3] != 'L')) {
    226    return mozilla::Nothing();
    227  }
    228 
    229  // Month numbers are ASCII digits.
    230  if (!mozilla::IsAsciiDigit(chars[1]) || !mozilla::IsAsciiDigit(chars[2])) {
    231    return mozilla::Nothing();
    232  }
    233 
    234  // Steps 6-7.
    235  int32_t ordinal =
    236      AsciiDigitToNumber(chars[1]) * 10 + AsciiDigitToNumber(chars[2]);
    237 
    238  // Step 8.
    239  if (ordinal == 0 && !isLeapMonth) {
    240    return mozilla::Nothing();
    241  }
    242 
    243  // Step 9.
    244  return mozilla::Some(MonthCodeField{ordinal, isLeapMonth});
    245 }
    246 
    247 /**
    248 * ParseMonthCode ( argument )
    249 */
    250 static auto ParseMonthCode(const JSLinearString* linear) {
    251  JS::AutoCheckCannotGC nogc;
    252 
    253  if (linear->hasLatin1Chars()) {
    254    return ParseMonthCode(linear->latin1Range(nogc));
    255  }
    256  return ParseMonthCode(linear->twoByteRange(nogc));
    257 }
    258 
    259 /**
    260 * ParseMonthCode ( argument )
    261 */
    262 static bool ParseMonthCode(JSContext* cx, Handle<Value> value,
    263                           MonthCodeField* result) {
    264  auto reportInvalidMonthCode = [&](JSLinearString* monthCode) {
    265    if (auto code = QuoteString(cx, monthCode)) {
    266      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    267                               JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
    268                               code.get());
    269    }
    270    return false;
    271  };
    272 
    273  // Step 1.
    274  Rooted<Value> monthCode(cx, value);
    275  if (!ToPrimitive(cx, JSTYPE_STRING, &monthCode)) {
    276    return false;
    277  }
    278 
    279  // Step 2.
    280  if (!monthCode.isString()) {
    281    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, monthCode,
    282                     nullptr, "not a string");
    283    return false;
    284  }
    285 
    286  JSLinearString* monthCodeStr = monthCode.toString()->ensureLinear(cx);
    287  if (!monthCodeStr) {
    288    return false;
    289  }
    290 
    291  // Steps 3-9.
    292  auto parsed = ParseMonthCode(monthCodeStr);
    293  if (!parsed) {
    294    return reportInvalidMonthCode(monthCodeStr);
    295  }
    296 
    297  *result = *parsed;
    298  return true;
    299 }
    300 
    301 /**
    302 * ToOffsetString ( argument )
    303 */
    304 static bool ToOffsetString(JSContext* cx, Handle<Value> value,
    305                           int64_t* result) {
    306  // Step 1.
    307  Rooted<Value> offset(cx, value);
    308  if (!ToPrimitive(cx, JSTYPE_STRING, &offset)) {
    309    return false;
    310  }
    311 
    312  // Step 2.
    313  if (!offset.isString()) {
    314    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, offset,
    315                     nullptr, "not a string");
    316    return false;
    317  }
    318  Rooted<JSString*> offsetStr(cx, offset.toString());
    319 
    320  // Steps 3-4.
    321  return ParseDateTimeUTCOffset(cx, offsetStr, result);
    322 }
    323 
    324 enum class Partial : bool { No, Yes };
    325 
    326 /**
    327 * PrepareCalendarFields ( calendar, fields, calendarFieldNames,
    328 * nonCalendarFieldNames, requiredFieldNames )
    329 */
    330 static bool PrepareCalendarFields(
    331    JSContext* cx, Handle<CalendarValue> calendar, Handle<JSObject*> fields,
    332    mozilla::EnumSet<CalendarField> fieldNames,
    333    mozilla::EnumSet<CalendarField> requiredFields, Partial partial,
    334    MutableHandle<CalendarFields> result) {
    335  MOZ_ASSERT_IF(partial == Partial::Yes, requiredFields.isEmpty());
    336 
    337  // Steps 1-2. (Not applicable in our implementation.)
    338 
    339  // Step 3.
    340  auto extraFieldNames = CalendarExtraFields(calendar.identifier(), fieldNames);
    341 
    342  // Step 4.
    343  fieldNames += extraFieldNames;
    344 
    345  // Step 5. (Not applicable in our implementation.)
    346 
    347  // Step 6.
    348  //
    349  // Default initialize the result.
    350  result.set(CalendarFields{});
    351 
    352  // Step 7. (Not applicable in our implementation.)
    353 
    354  // Step 8.
    355  static_assert(CalendarFieldsAreSorted(),
    356                "EnumSet<CalendarField> iteration is sorted");
    357 
    358  // Step 9.
    359  Rooted<Value> value(cx);
    360  for (auto fieldName : fieldNames) {
    361    auto* propertyName = ToPropertyName(cx, fieldName);
    362    const auto* cstr = ToCString(fieldName);
    363 
    364    // Step 9.a. (Not applicable in our implementation.)
    365 
    366    // Step 9.b.
    367    if (!GetProperty(cx, fields, fields, propertyName, &value)) {
    368      return false;
    369    }
    370 
    371    // Steps 9.c-d.
    372    if (!value.isUndefined()) {
    373      // Steps 9.c.i-ii. (Not applicable in our implementation.)
    374 
    375      // Steps 9.c.iii-ix.
    376      switch (fieldName) {
    377        case CalendarField::Era: {
    378          JSString* era = ToString(cx, value);
    379          if (!era) {
    380            return false;
    381          }
    382          result.setEra(era);
    383          break;
    384        }
    385        case CalendarField::EraYear: {
    386          double eraYear;
    387          if (!ToIntegerWithTruncation(cx, value, cstr, &eraYear)) {
    388            return false;
    389          }
    390          result.setEraYear(eraYear);
    391          break;
    392        }
    393        case CalendarField::Year: {
    394          double year;
    395          if (!ToIntegerWithTruncation(cx, value, cstr, &year)) {
    396            return false;
    397          }
    398          result.setYear(year);
    399          break;
    400        }
    401        case CalendarField::Month: {
    402          double month;
    403          if (!ToPositiveIntegerWithTruncation(cx, value, cstr, &month)) {
    404            return false;
    405          }
    406          result.setMonth(month);
    407          break;
    408        }
    409        case CalendarField::MonthCode: {
    410          MonthCodeField monthCode;
    411          if (!ParseMonthCode(cx, value, &monthCode)) {
    412            return false;
    413          }
    414          result.setMonthCode(monthCode);
    415          break;
    416        }
    417        case CalendarField::Day: {
    418          double day;
    419          if (!ToPositiveIntegerWithTruncation(cx, value, cstr, &day)) {
    420            return false;
    421          }
    422          result.setDay(day);
    423          break;
    424        }
    425        case CalendarField::Hour: {
    426          double hour;
    427          if (!ToIntegerWithTruncation(cx, value, cstr, &hour)) {
    428            return false;
    429          }
    430          result.setHour(hour);
    431          break;
    432        }
    433        case CalendarField::Minute: {
    434          double minute;
    435          if (!ToIntegerWithTruncation(cx, value, cstr, &minute)) {
    436            return false;
    437          }
    438          result.setMinute(minute);
    439          break;
    440        }
    441        case CalendarField::Second: {
    442          double second;
    443          if (!ToIntegerWithTruncation(cx, value, cstr, &second)) {
    444            return false;
    445          }
    446          result.setSecond(second);
    447          break;
    448        }
    449        case CalendarField::Millisecond: {
    450          double millisecond;
    451          if (!ToIntegerWithTruncation(cx, value, cstr, &millisecond)) {
    452            return false;
    453          }
    454          result.setMillisecond(millisecond);
    455          break;
    456        }
    457        case CalendarField::Microsecond: {
    458          double microsecond;
    459          if (!ToIntegerWithTruncation(cx, value, cstr, &microsecond)) {
    460            return false;
    461          }
    462          result.setMicrosecond(microsecond);
    463          break;
    464        }
    465        case CalendarField::Nanosecond: {
    466          double nanosecond;
    467          if (!ToIntegerWithTruncation(cx, value, cstr, &nanosecond)) {
    468            return false;
    469          }
    470          result.setNanosecond(nanosecond);
    471          break;
    472        }
    473        case CalendarField::Offset: {
    474          int64_t offset;
    475          if (!ToOffsetString(cx, value, &offset)) {
    476            return false;
    477          }
    478          result.setOffset(OffsetField{offset});
    479          break;
    480        }
    481        case CalendarField::TimeZone:
    482          Rooted<TimeZoneValue> timeZone(cx);
    483          if (!ToTemporalTimeZone(cx, value, &timeZone)) {
    484            return false;
    485          }
    486          result.setTimeZone(timeZone);
    487          break;
    488      }
    489    } else if (partial == Partial::No) {
    490      // Step 9.d.i.
    491      if (requiredFields.contains(fieldName)) {
    492        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    493                                  JSMSG_TEMPORAL_MISSING_PROPERTY, cstr);
    494        return false;
    495      }
    496 
    497      // Step 9.d.ii.
    498      result.setDefault(fieldName);
    499    }
    500  }
    501 
    502  // Step 10.
    503  if (partial == Partial::Yes && result.keys().isEmpty()) {
    504    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    505                              JSMSG_TEMPORAL_MISSING_TEMPORAL_FIELDS);
    506    return false;
    507  }
    508 
    509  // Step 11.
    510  return true;
    511 }
    512 
    513 /**
    514 * PrepareCalendarFields ( calendar, fields, calendarFieldNames,
    515 * nonCalendarFieldNames, requiredFieldNames )
    516 */
    517 bool js::temporal::PrepareCalendarFields(
    518    JSContext* cx, Handle<CalendarValue> calendar, Handle<JSObject*> fields,
    519    mozilla::EnumSet<CalendarField> fieldNames,
    520    mozilla::EnumSet<CalendarField> requiredFields,
    521    MutableHandle<CalendarFields> result) {
    522  return PrepareCalendarFields(cx, calendar, fields, fieldNames, requiredFields,
    523                               Partial::No, result);
    524 }
    525 
    526 /**
    527 * PrepareCalendarFields ( calendar, fields, calendarFieldNames,
    528 * nonCalendarFieldNames, requiredFieldNames )
    529 */
    530 bool js::temporal::PreparePartialCalendarFields(
    531    JSContext* cx, Handle<CalendarValue> calendar, Handle<JSObject*> fields,
    532    mozilla::EnumSet<CalendarField> fieldNames,
    533    JS::MutableHandle<CalendarFields> result) {
    534  return PrepareCalendarFields(cx, calendar, fields, fieldNames, {},
    535                               Partial::Yes, result);
    536 }
    537 
    538 /**
    539 * NonISOFieldKeysToIgnore ( calendar, keys )
    540 */
    541 static auto NonISOFieldKeysToIgnore(CalendarId calendar,
    542                                    mozilla::EnumSet<CalendarField> keys) {
    543  static constexpr auto eraOrEraYear = mozilla::EnumSet{
    544      CalendarField::Era,
    545      CalendarField::EraYear,
    546  };
    547 
    548  static constexpr auto eraOrAnyYear = mozilla::EnumSet{
    549      CalendarField::Era,
    550      CalendarField::EraYear,
    551      CalendarField::Year,
    552  };
    553 
    554  static constexpr auto monthOrMonthCode = mozilla::EnumSet{
    555      CalendarField::Month,
    556      CalendarField::MonthCode,
    557  };
    558 
    559  static constexpr auto dayOrAnyMonth = mozilla::EnumSet{
    560      CalendarField::Day,
    561      CalendarField::Month,
    562      CalendarField::MonthCode,
    563  };
    564 
    565  // A field always invalidates at least itself, so start with ignoring all
    566  // input fields.
    567  auto result = keys;
    568 
    569  // "month" and "monthCode" are mutually exclusive.
    570  if (!(keys & monthOrMonthCode).isEmpty()) {
    571    result += monthOrMonthCode;
    572  }
    573 
    574  // "era", "eraYear", and "year" are mutually exclusive when the calendar
    575  // supports eras.
    576  if (CalendarSupportsEra(calendar) && !(keys & eraOrAnyYear).isEmpty()) {
    577    result += eraOrAnyYear;
    578  }
    579 
    580  // If eras can start in the middle of the year, we have to ignore "era" and
    581  // "eraYear" if any of "day", "month", or "monthCode" is present.
    582  if (CalendarHasMidYearEras(calendar) && !(keys & dayOrAnyMonth).isEmpty()) {
    583    result += eraOrEraYear;
    584  }
    585 
    586  return result;
    587 }
    588 
    589 /**
    590 * CalendarFieldKeysToIgnore ( calendar, keys )
    591 */
    592 static auto CalendarFieldKeysToIgnore(CalendarId calendar,
    593                                      mozilla::EnumSet<CalendarField> keys) {
    594  // Step 1.
    595  if (calendar == CalendarId::ISO8601) {
    596    // Steps 1.a and 1.b.i.
    597    auto ignoredKeys = keys;
    598 
    599    // Step 1.b.ii.
    600    if (keys.contains(CalendarField::Month)) {
    601      ignoredKeys += CalendarField::MonthCode;
    602    }
    603 
    604    // Step 1.b.iii.
    605    else if (keys.contains(CalendarField::MonthCode)) {
    606      ignoredKeys += CalendarField::Month;
    607    }
    608 
    609    // Steps 1.c-d.
    610    return ignoredKeys;
    611  }
    612 
    613  // Step 2.
    614  return NonISOFieldKeysToIgnore(calendar, keys);
    615 }
    616 
    617 /**
    618 * CalendarMergeFields ( calendar, fields, additionalFields )
    619 */
    620 CalendarFields js::temporal::CalendarMergeFields(
    621    const CalendarValue& calendar, const CalendarFields& fields,
    622    const CalendarFields& additionalFields) {
    623  auto calendarId = calendar.identifier();
    624 
    625  // Steps 1.
    626  auto additionalKeys = additionalFields.keys();
    627 
    628  // Step 2.
    629  auto overriddenKeys = CalendarFieldKeysToIgnore(calendarId, additionalKeys);
    630  MOZ_ASSERT(overriddenKeys.contains(additionalKeys));
    631 
    632  // Step 3.
    633  auto merged = CalendarFields{};
    634 
    635  // Step 4.
    636  auto fieldsKeys = fields.keys();
    637 
    638  // Step 5.b.
    639  for (auto key : (fieldsKeys - overriddenKeys)) {
    640    merged.setFrom(key, fields);
    641  }
    642 
    643  // Step 5.c.
    644  for (auto key : additionalKeys) {
    645    merged.setFrom(key, additionalFields);
    646  }
    647 
    648  // Step 6.
    649  return merged;
    650 }