tor-browser

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

EqualityOperations.cpp (10348B)


      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 "vm/EqualityOperations.h"  // js::LooselyEqual, js::StrictlyEqual, js::SameValue
      8 
      9 #include "mozilla/Assertions.h"  // MOZ_ASSERT, MOZ_ASSERT_IF
     10 
     11 #include "jsnum.h"    // js::StringToNumber
     12 #include "jstypes.h"  // JS_PUBLIC_API
     13 
     14 #include "js/Context.h"   // js::AssertHeapIsIdle
     15 #include "js/Equality.h"  // JS::LooselyEqual, JS::StrictlyEqual, JS::SameValue
     16 #include "js/Result.h"    // JS_TRY_VAR_OR_RETURN_FALSE
     17 #include "js/RootingAPI.h"  // JS::Rooted
     18 #include "js/Value.h"       // JS::Int32Value, JS::SameType, JS::Value
     19 #include "vm/BigIntType.h"  // JS::BigInt
     20 #include "vm/ConstantCompareOperand.h"
     21 #include "vm/JSContext.h"   // CHECK_THREAD
     22 #include "vm/JSObject.h"    // js::ToPrimitive
     23 #include "vm/StringType.h"  // js::EqualStrings
     24 
     25 #include "builtin/Boolean-inl.h"  // js::EmulatesUndefined
     26 #include "vm/JSContext-inl.h"     // JSContext::check
     27 
     28 static bool EqualGivenSameType(JSContext* cx, const JS::Value& lval,
     29                               const JS::Value& rval, bool* equal) {
     30  MOZ_ASSERT(JS::SameType(lval, rval));
     31 
     32  if (lval.isString()) {
     33    return js::EqualStrings(cx, lval.toString(), rval.toString(), equal);
     34  }
     35 
     36  if (lval.isDouble()) {
     37    *equal = (lval.toDouble() == rval.toDouble());
     38    return true;
     39  }
     40 
     41  if (lval.isBigInt()) {
     42    *equal = JS::BigInt::equal(lval.toBigInt(), rval.toBigInt());
     43    return true;
     44  }
     45 
     46  // Note: we can do a bitwise comparison even for Int32Value because both
     47  // Values have the same type.
     48  MOZ_ASSERT(js::CanUseBitwiseCompareForStrictlyEqual(lval) || lval.isInt32());
     49 
     50  *equal = (lval.asRawBits() == rval.asRawBits());
     51  MOZ_ASSERT_IF(lval.isUndefined() || lval.isNull(), *equal);
     52  return true;
     53 }
     54 
     55 static bool LooselyEqualBooleanAndOther(JSContext* cx,
     56                                        JS::Handle<JS::Value> lval,
     57                                        JS::Handle<JS::Value> rval,
     58                                        bool* result) {
     59  MOZ_ASSERT(!rval.isBoolean());
     60 
     61  JS::Rooted<JS::Value> lvalue(cx, JS::Int32Value(lval.toBoolean() ? 1 : 0));
     62 
     63  // The tail-call would end up in Step 3.
     64  if (rval.isNumber()) {
     65    *result = (lvalue.toNumber() == rval.toNumber());
     66    return true;
     67  }
     68  // The tail-call would end up in Step 6.
     69  if (rval.isString()) {
     70    double num;
     71    if (!StringToNumber(cx, rval.toString(), &num)) {
     72      return false;
     73    }
     74    *result = (lvalue.toNumber() == num);
     75    return true;
     76  }
     77 
     78  return js::LooselyEqual(cx, lvalue, rval, result);
     79 }
     80 
     81 // ES2026 Draft rev e936549f1c05ac1b206ad4c5817e77ee3ecbc787
     82 //
     83 // IsLooselyEqual ( x, y )
     84 // https://tc39.es/ecma262/#sec-islooselyequal
     85 bool js::LooselyEqual(JSContext* cx, JS::Handle<JS::Value> lval,
     86                      JS::Handle<JS::Value> rval, bool* result) {
     87  // Step 1. If SameType(x, y) is true, then
     88  if (JS::SameType(lval, rval)) {
     89    // Step 1.a. Return IsStrictlyEqual(x, y).
     90    return EqualGivenSameType(cx, lval, rval, result);
     91  }
     92 
     93  // NOTE: JS::SameType distinguishes between Int32 vs Double,
     94  //       but the spec's SameType doesn't.
     95  if (lval.isNumber() && rval.isNumber()) {
     96    *result = (lval.toNumber() == rval.toNumber());
     97    return true;
     98  }
     99 
    100  // Step 2. If x is null and y is undefined, return true.
    101  // Step 3. If x is undefined and y is null, return true.
    102  // Step 4. Normative Optional
    103  //         If the host is a web browser or otherwise supports The
    104  //         [[IsHTMLDDA]] Internal Slot, then
    105  // Step 4.a. If x is an Object, x has an [[IsHTMLDDA]] internal slot, and y
    106  //           is either undefined or null, return true.
    107  // Step 4.b. If x is either undefined or null, y is an Object, and y has an
    108  //           [[IsHTMLDDA]] internal slot, return true.
    109  if (lval.isNullOrUndefined()) {
    110    *result = rval.isNullOrUndefined() ||
    111              (rval.isObject() && EmulatesUndefined(&rval.toObject()));
    112    return true;
    113  }
    114  if (rval.isNullOrUndefined()) {
    115    MOZ_ASSERT(!lval.isNullOrUndefined());
    116    *result = lval.isObject() && EmulatesUndefined(&lval.toObject());
    117    return true;
    118  }
    119 
    120  // Step 5. If x is a Number and y is a String, return ! IsLooselyEqual(x, !
    121  //         ToNumber(y)).
    122  if (lval.isNumber() && rval.isString()) {
    123    double num;
    124    if (!StringToNumber(cx, rval.toString(), &num)) {
    125      return false;
    126    }
    127    *result = (lval.toNumber() == num);
    128    return true;
    129  }
    130 
    131  // Step 6. If x is a String and y is a Number, return ! IsLooselyEqual(!
    132  //         ToNumber(x), y).
    133  if (lval.isString() && rval.isNumber()) {
    134    double num;
    135    if (!StringToNumber(cx, lval.toString(), &num)) {
    136      return false;
    137    }
    138    *result = (num == rval.toNumber());
    139    return true;
    140  }
    141 
    142  // Step 7. If x is a BigInt and y is a String, then
    143  if (lval.isBigInt() && rval.isString()) {
    144    // Step 7.a. Let n be StringToBigInt(y).
    145    BigInt* n;
    146    JS::Rooted<JSString*> str(cx, rval.toString());
    147    JS_TRY_VAR_OR_RETURN_FALSE(cx, n, StringToBigInt(cx, str));
    148    if (!n) {
    149      // Step 7.b. If n is undefined, return false.
    150      *result = false;
    151      return true;
    152    }
    153    // Step 7.c. Return ! IsLooselyEqual(x, n).
    154    *result = JS::BigInt::equal(lval.toBigInt(), n);
    155    return true;
    156  }
    157 
    158  // Step 8. If x is a String and y is a BigInt, return ! IsLooselyEqual(y,
    159  //         x).
    160  if (lval.isString() && rval.isBigInt()) {
    161    BigInt* n;
    162    JS::Rooted<JSString*> str(cx, lval.toString());
    163    JS_TRY_VAR_OR_RETURN_FALSE(cx, n, StringToBigInt(cx, str));
    164    if (!n) {
    165      *result = false;
    166      return true;
    167    }
    168    *result = JS::BigInt::equal(rval.toBigInt(), n);
    169    return true;
    170  }
    171 
    172  // Step 9. If x is a Boolean, return ! IsLooselyEqual(! ToNumber(x), y).
    173  if (lval.isBoolean()) {
    174    return LooselyEqualBooleanAndOther(cx, lval, rval, result);
    175  }
    176 
    177  // Step 10. If y is a Boolean, return ! IsLooselyEqual(x, ! ToNumber(y)).
    178  if (rval.isBoolean()) {
    179    return LooselyEqualBooleanAndOther(cx, rval, lval, result);
    180  }
    181 
    182  // Step 11. If x is either a String, a Number, a BigInt, or a Symbol and y
    183  //          is an Object, return ! IsLooselyEqual(x, ? ToPrimitive(y)).
    184  if ((lval.isString() || lval.isNumber() || lval.isBigInt() ||
    185       lval.isSymbol()) &&
    186      rval.isObject()) {
    187    JS::Rooted<JS::Value> rvalue(cx, rval);
    188    if (!ToPrimitive(cx, &rvalue)) {
    189      return false;
    190    }
    191    return js::LooselyEqual(cx, lval, rvalue, result);
    192  }
    193 
    194  // Step 12. If x is an Object and y is either a String, a Number, a BigInt,
    195  //          or a Symbol, return ! IsLooselyEqual(? ToPrimitive(x), y).
    196  if (lval.isObject() && (rval.isString() || rval.isNumber() ||
    197                          rval.isBigInt() || rval.isSymbol())) {
    198    JS::Rooted<JS::Value> lvalue(cx, lval);
    199    if (!ToPrimitive(cx, &lvalue)) {
    200      return false;
    201    }
    202    return js::LooselyEqual(cx, lvalue, rval, result);
    203  }
    204 
    205  // Step 13. If x is a BigInt and y is a Number, or if x is a Number and y
    206  //          is a BigInt, then
    207  if (lval.isBigInt() && rval.isNumber()) {
    208    // Step 13.a. If x is not finite or y is not finite, return false.
    209    // Step 13.b. If ℝ(x) = ℝ(y), return true; otherwise return false.
    210    *result = BigInt::equal(lval.toBigInt(), rval.toNumber());
    211    return true;
    212  }
    213  if (lval.isNumber() && rval.isBigInt()) {
    214    *result = BigInt::equal(rval.toBigInt(), lval.toNumber());
    215    return true;
    216  }
    217 
    218  // Step 14. Return false.
    219  *result = false;
    220  return true;
    221 }
    222 
    223 JS_PUBLIC_API bool JS::LooselyEqual(JSContext* cx, Handle<Value> value1,
    224                                    Handle<Value> value2, bool* equal) {
    225  js::AssertHeapIsIdle();
    226  CHECK_THREAD(cx);
    227  cx->check(value1, value2);
    228  MOZ_ASSERT(equal);
    229  return js::LooselyEqual(cx, value1, value2, equal);
    230 }
    231 
    232 bool js::ConstantStrictEqual(const JS::Value& val, uint16_t operand) {
    233  ConstantCompareOperand constant =
    234      ConstantCompareOperand::fromRawValue(operand);
    235 
    236  switch (constant.type()) {
    237    case ConstantCompareOperand::EncodedType::Int32:
    238      return val.isNumber() && val.toNumber() == double(constant.toInt32());
    239    case ConstantCompareOperand::EncodedType::Boolean:
    240      return val == BooleanValue(constant.toBoolean());
    241    case ConstantCompareOperand::EncodedType::Undefined:
    242      return val.isUndefined();
    243    case ConstantCompareOperand::EncodedType::Null:
    244      return val.isNull();
    245  }
    246  MOZ_CRASH("Unknown constant compare operand type");
    247 }
    248 
    249 bool js::StrictlyEqual(JSContext* cx, const JS::Value& lval,
    250                       const JS::Value& rval, bool* equal) {
    251  if (SameType(lval, rval)) {
    252    return EqualGivenSameType(cx, lval, rval, equal);
    253  }
    254 
    255  if (lval.isNumber() && rval.isNumber()) {
    256    *equal = (lval.toNumber() == rval.toNumber());
    257    return true;
    258  }
    259 
    260  *equal = false;
    261  return true;
    262 }
    263 
    264 JS_PUBLIC_API bool JS::StrictlyEqual(JSContext* cx, Handle<Value> value1,
    265                                     Handle<Value> value2, bool* equal) {
    266  js::AssertHeapIsIdle();
    267  CHECK_THREAD(cx);
    268  cx->check(value1, value2);
    269  MOZ_ASSERT(equal);
    270  return js::StrictlyEqual(cx, value1, value2, equal);
    271 }
    272 
    273 static inline bool IsNegativeZero(const JS::Value& v) {
    274  return v.isDouble() && mozilla::IsNegativeZero(v.toDouble());
    275 }
    276 
    277 static inline bool IsNaN(const JS::Value& v) {
    278  return v.isDouble() && std::isnan(v.toDouble());
    279 }
    280 
    281 bool js::SameValue(JSContext* cx, const JS::Value& v1, const JS::Value& v2,
    282                   bool* same) {
    283  if (IsNegativeZero(v1)) {
    284    *same = IsNegativeZero(v2);
    285    return true;
    286  }
    287 
    288  if (IsNegativeZero(v2)) {
    289    *same = false;
    290    return true;
    291  }
    292 
    293  return js::SameValueZero(cx, v1, v2, same);
    294 }
    295 
    296 JS_PUBLIC_API bool JS::SameValue(JSContext* cx, Handle<Value> value1,
    297                                 Handle<Value> value2, bool* same) {
    298  js::AssertHeapIsIdle();
    299  CHECK_THREAD(cx);
    300  cx->check(value1, value2);
    301  MOZ_ASSERT(same);
    302  return js::SameValue(cx, value1, value2, same);
    303 }
    304 
    305 bool js::SameValueZero(JSContext* cx, const Value& v1, const Value& v2,
    306                       bool* same) {
    307  if (IsNaN(v1) && IsNaN(v2)) {
    308    *same = true;
    309    return true;
    310  }
    311 
    312  return js::StrictlyEqual(cx, v1, v2, same);
    313 }