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 }