Bug 1842773 - Part 32: Allow constructing growable SharedArrayBuffers. r=sfink
[gecko.git] / js / src / vm / EqualityOperations.cpp
blob66438879ce9c247a5bb5c624053e1d28caddc554
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/. */
7 #include "vm/EqualityOperations.h" // js::LooselyEqual, js::StrictlyEqual, js::SameValue
9 #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF
11 #include "jsnum.h" // js::StringToNumber
12 #include "jstypes.h" // JS_PUBLIC_API
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/JSContext.h" // CHECK_THREAD
21 #include "vm/JSObject.h" // js::ToPrimitive
22 #include "vm/StringType.h" // js::EqualStrings
23 #ifdef ENABLE_RECORD_TUPLE
24 # include "vm/RecordType.h"
25 # include "vm/TupleType.h"
26 #endif
28 #include "builtin/Boolean-inl.h" // js::EmulatesUndefined
29 #include "vm/JSContext-inl.h" // JSContext::check
31 static bool EqualGivenSameType(JSContext* cx, JS::Handle<JS::Value> lval,
32 JS::Handle<JS::Value> rval, bool* equal) {
33 MOZ_ASSERT(JS::SameType(lval, rval));
35 if (lval.isString()) {
36 return js::EqualStrings(cx, lval.toString(), rval.toString(), equal);
39 if (lval.isDouble()) {
40 *equal = (lval.toDouble() == rval.toDouble());
41 return true;
44 if (lval.isBigInt()) {
45 *equal = JS::BigInt::equal(lval.toBigInt(), rval.toBigInt());
46 return true;
49 #ifdef ENABLE_RECORD_TUPLE
50 // Record & Tuple proposal, section 3.2.6 (Strict Equality Comparison), step 3
51 // - https://tc39.es/proposal-record-tuple/#sec-strict-equality-comparison
53 // When computing equality, records and tuples are compared using the
54 // SameValueZero algorithm.
56 // NOTE: Since Records and Tuples are impemented as ExtendedPrimitives,
57 // "SameType" refers to the fact that both lval and rval are
58 // ExtendedPrimitives. They can still be different types (for example, a
59 // Record and a Tuple).
60 if (lval.isExtendedPrimitive()) {
61 JSObject* lobj = &lval.toExtendedPrimitive();
62 JSObject* robj = &rval.toExtendedPrimitive();
64 if (lobj->getClass() != robj->getClass()) {
65 *equal = false;
66 return true;
69 if (lobj->is<js::RecordType>()) {
70 return js::RecordType::sameValueZero(cx, &lobj->as<js::RecordType>(),
71 &robj->as<js::RecordType>(), equal);
73 if (lobj->is<js::TupleType>()) {
74 return js::TupleType::sameValueZero(cx, &lobj->as<js::TupleType>(),
75 &robj->as<js::TupleType>(), equal);
77 MOZ_CRASH("Unknown ExtendedPrimitive type");
79 #endif
81 // Note: we can do a bitwise comparison even for Int32Value because both
82 // Values have the same type.
83 MOZ_ASSERT(CanUseBitwiseCompareForStrictlyEqual(lval) || lval.isInt32());
85 *equal = (lval.asRawBits() == rval.asRawBits());
86 MOZ_ASSERT_IF(lval.isUndefined() || lval.isNull(), *equal);
87 return true;
90 static bool LooselyEqualBooleanAndOther(JSContext* cx,
91 JS::Handle<JS::Value> lval,
92 JS::Handle<JS::Value> rval,
93 bool* result) {
94 MOZ_ASSERT(!rval.isBoolean());
96 JS::Rooted<JS::Value> lvalue(cx, JS::Int32Value(lval.toBoolean() ? 1 : 0));
98 // The tail-call would end up in Step 3.
99 if (rval.isNumber()) {
100 *result = (lvalue.toNumber() == rval.toNumber());
101 return true;
103 // The tail-call would end up in Step 6.
104 if (rval.isString()) {
105 double num;
106 if (!StringToNumber(cx, rval.toString(), &num)) {
107 return false;
109 *result = (lvalue.toNumber() == num);
110 return true;
113 return js::LooselyEqual(cx, lvalue, rval, result);
116 // ES6 draft rev32 7.2.12 Abstract Equality Comparison
117 bool js::LooselyEqual(JSContext* cx, JS::Handle<JS::Value> lval,
118 JS::Handle<JS::Value> rval, bool* result) {
119 // Step 3.
120 if (JS::SameType(lval, rval)) {
121 return EqualGivenSameType(cx, lval, rval, result);
124 // Handle int32 x double.
125 if (lval.isNumber() && rval.isNumber()) {
126 *result = (lval.toNumber() == rval.toNumber());
127 return true;
130 // Step 4. This a bit more complex, because of the undefined emulating object.
131 if (lval.isNullOrUndefined()) {
132 // We can return early here, because null | undefined is only equal to the
133 // same set.
134 *result = rval.isNullOrUndefined() ||
135 (rval.isObject() && EmulatesUndefined(&rval.toObject()));
136 return true;
139 // Step 5.
140 if (rval.isNullOrUndefined()) {
141 MOZ_ASSERT(!lval.isNullOrUndefined());
142 *result = lval.isObject() && EmulatesUndefined(&lval.toObject());
143 return true;
146 // Step 6.
147 if (lval.isNumber() && rval.isString()) {
148 double num;
149 if (!StringToNumber(cx, rval.toString(), &num)) {
150 return false;
152 *result = (lval.toNumber() == num);
153 return true;
156 // Step 7.
157 if (lval.isString() && rval.isNumber()) {
158 double num;
159 if (!StringToNumber(cx, lval.toString(), &num)) {
160 return false;
162 *result = (num == rval.toNumber());
163 return true;
166 // Step 8.
167 if (lval.isBoolean()) {
168 return LooselyEqualBooleanAndOther(cx, lval, rval, result);
171 // Step 9.
172 if (rval.isBoolean()) {
173 return LooselyEqualBooleanAndOther(cx, rval, lval, result);
176 // Step 10.
177 if ((lval.isString() || lval.isNumber() || lval.isSymbol()) &&
178 rval.isObject()) {
179 JS::Rooted<JS::Value> rvalue(cx, rval);
180 if (!ToPrimitive(cx, &rvalue)) {
181 return false;
183 return js::LooselyEqual(cx, lval, rvalue, result);
186 // Step 11.
187 if (lval.isObject() &&
188 (rval.isString() || rval.isNumber() || rval.isSymbol())) {
189 JS::Rooted<JS::Value> lvalue(cx, lval);
190 if (!ToPrimitive(cx, &lvalue)) {
191 return false;
193 return js::LooselyEqual(cx, lvalue, rval, result);
196 if (lval.isBigInt()) {
197 JS::Rooted<JS::BigInt*> lbi(cx, lval.toBigInt());
198 bool tmpResult;
199 JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult,
200 JS::BigInt::looselyEqual(cx, lbi, rval));
201 *result = tmpResult;
202 return true;
205 if (rval.isBigInt()) {
206 JS::Rooted<JS::BigInt*> rbi(cx, rval.toBigInt());
207 bool tmpResult;
208 JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult,
209 JS::BigInt::looselyEqual(cx, rbi, lval));
210 *result = tmpResult;
211 return true;
214 // Step 12.
215 *result = false;
216 return true;
219 JS_PUBLIC_API bool JS::LooselyEqual(JSContext* cx, Handle<Value> value1,
220 Handle<Value> value2, bool* equal) {
221 js::AssertHeapIsIdle();
222 CHECK_THREAD(cx);
223 cx->check(value1, value2);
224 MOZ_ASSERT(equal);
225 return js::LooselyEqual(cx, value1, value2, equal);
228 bool js::StrictlyEqual(JSContext* cx, JS::Handle<JS::Value> lval,
229 JS::Handle<JS::Value> rval, bool* equal) {
230 if (SameType(lval, rval)) {
231 return EqualGivenSameType(cx, lval, rval, equal);
234 if (lval.isNumber() && rval.isNumber()) {
235 *equal = (lval.toNumber() == rval.toNumber());
236 return true;
239 *equal = false;
240 return true;
243 JS_PUBLIC_API bool JS::StrictlyEqual(JSContext* cx, Handle<Value> value1,
244 Handle<Value> value2, bool* equal) {
245 js::AssertHeapIsIdle();
246 CHECK_THREAD(cx);
247 cx->check(value1, value2);
248 MOZ_ASSERT(equal);
249 return js::StrictlyEqual(cx, value1, value2, equal);
252 static inline bool IsNegativeZero(const JS::Value& v) {
253 return v.isDouble() && mozilla::IsNegativeZero(v.toDouble());
256 static inline bool IsNaN(const JS::Value& v) {
257 return v.isDouble() && std::isnan(v.toDouble());
260 bool js::SameValue(JSContext* cx, JS::Handle<JS::Value> v1,
261 JS::Handle<JS::Value> v2, bool* same) {
262 if (IsNegativeZero(v1)) {
263 *same = IsNegativeZero(v2);
264 return true;
267 if (IsNegativeZero(v2)) {
268 *same = false;
269 return true;
272 #ifdef ENABLE_RECORD_TUPLE
273 if (v1.isExtendedPrimitive()) {
274 JSObject* lobj = &v1.toExtendedPrimitive();
275 JSObject* robj = &v2.toExtendedPrimitive();
277 if (lobj->getClass() != robj->getClass()) {
278 *same = false;
279 return true;
282 if (lobj->is<js::RecordType>()) {
283 return js::RecordType::sameValue(cx, &lobj->as<js::RecordType>(),
284 &robj->as<js::RecordType>(), same);
286 if (lobj->is<js::TupleType>()) {
287 return js::TupleType::sameValue(cx, &lobj->as<js::TupleType>(),
288 &robj->as<js::TupleType>(), same);
290 MOZ_CRASH("Unknown ExtendedPrimitive type");
292 #endif
294 return js::SameValueZero(cx, v1, v2, same);
297 #ifdef ENABLE_RECORD_TUPLE
298 bool js::SameValueZeroLinear(const JS::Value& lval, const JS::Value& rval) {
299 if (lval.isNumber() && rval.isNumber()) {
300 return IsNaN(lval) ? IsNaN(rval) : lval.toNumber() == rval.toNumber();
303 if (lval.type() != rval.type()) {
304 return false;
307 switch (lval.type()) {
308 case ValueType::Double:
309 return IsNaN(lval) ? IsNaN(rval) : lval.toDouble() == rval.toDouble();
311 case ValueType::BigInt:
312 // BigInt values are considered equal if they represent the same
313 // mathematical value.
314 return BigInt::equal(lval.toBigInt(), rval.toBigInt());
316 case ValueType::String:
317 MOZ_ASSERT(lval.toString()->isLinear() && rval.toString()->isLinear());
318 return EqualStrings(&lval.toString()->asLinear(),
319 &rval.toString()->asLinear());
321 case ValueType::ExtendedPrimitive: {
322 JSObject& lobj = lval.toExtendedPrimitive();
323 JSObject& robj = rval.toExtendedPrimitive();
324 if (lobj.getClass() != robj.getClass()) {
325 return false;
327 if (lobj.is<RecordType>()) {
328 return RecordType::sameValueZero(&lobj.as<RecordType>(),
329 &robj.as<RecordType>());
331 MOZ_ASSERT(lobj.is<TupleType>());
332 return TupleType::sameValueZero(&lobj.as<TupleType>(),
333 &robj.as<TupleType>());
336 default:
337 MOZ_ASSERT(CanUseBitwiseCompareForStrictlyEqual(lval));
338 return lval.asRawBits() == rval.asRawBits();
341 #endif
343 JS_PUBLIC_API bool JS::SameValue(JSContext* cx, Handle<Value> value1,
344 Handle<Value> value2, bool* same) {
345 js::AssertHeapIsIdle();
346 CHECK_THREAD(cx);
347 cx->check(value1, value2);
348 MOZ_ASSERT(same);
349 return js::SameValue(cx, value1, value2, same);
352 bool js::SameValueZero(JSContext* cx, Handle<Value> v1, Handle<Value> v2,
353 bool* same) {
354 if (IsNaN(v1) && IsNaN(v2)) {
355 *same = true;
356 return true;
359 return js::StrictlyEqual(cx, v1, v2, same);