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"
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());
44 if (lval
.isBigInt()) {
45 *equal
= JS::BigInt::equal(lval
.toBigInt(), rval
.toBigInt());
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()) {
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");
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
);
90 static bool LooselyEqualBooleanAndOther(JSContext
* cx
,
91 JS::Handle
<JS::Value
> lval
,
92 JS::Handle
<JS::Value
> rval
,
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());
103 // The tail-call would end up in Step 6.
104 if (rval
.isString()) {
106 if (!StringToNumber(cx
, rval
.toString(), &num
)) {
109 *result
= (lvalue
.toNumber() == num
);
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
) {
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());
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
134 *result
= rval
.isNullOrUndefined() ||
135 (rval
.isObject() && EmulatesUndefined(&rval
.toObject()));
140 if (rval
.isNullOrUndefined()) {
141 MOZ_ASSERT(!lval
.isNullOrUndefined());
142 *result
= lval
.isObject() && EmulatesUndefined(&lval
.toObject());
147 if (lval
.isNumber() && rval
.isString()) {
149 if (!StringToNumber(cx
, rval
.toString(), &num
)) {
152 *result
= (lval
.toNumber() == num
);
157 if (lval
.isString() && rval
.isNumber()) {
159 if (!StringToNumber(cx
, lval
.toString(), &num
)) {
162 *result
= (num
== rval
.toNumber());
167 if (lval
.isBoolean()) {
168 return LooselyEqualBooleanAndOther(cx
, lval
, rval
, result
);
172 if (rval
.isBoolean()) {
173 return LooselyEqualBooleanAndOther(cx
, rval
, lval
, result
);
177 if ((lval
.isString() || lval
.isNumber() || lval
.isSymbol()) &&
179 JS::Rooted
<JS::Value
> rvalue(cx
, rval
);
180 if (!ToPrimitive(cx
, &rvalue
)) {
183 return js::LooselyEqual(cx
, lval
, rvalue
, result
);
187 if (lval
.isObject() &&
188 (rval
.isString() || rval
.isNumber() || rval
.isSymbol())) {
189 JS::Rooted
<JS::Value
> lvalue(cx
, lval
);
190 if (!ToPrimitive(cx
, &lvalue
)) {
193 return js::LooselyEqual(cx
, lvalue
, rval
, result
);
196 if (lval
.isBigInt()) {
197 JS::Rooted
<JS::BigInt
*> lbi(cx
, lval
.toBigInt());
199 JS_TRY_VAR_OR_RETURN_FALSE(cx
, tmpResult
,
200 JS::BigInt::looselyEqual(cx
, lbi
, rval
));
205 if (rval
.isBigInt()) {
206 JS::Rooted
<JS::BigInt
*> rbi(cx
, rval
.toBigInt());
208 JS_TRY_VAR_OR_RETURN_FALSE(cx
, tmpResult
,
209 JS::BigInt::looselyEqual(cx
, rbi
, lval
));
219 JS_PUBLIC_API
bool JS::LooselyEqual(JSContext
* cx
, Handle
<Value
> value1
,
220 Handle
<Value
> value2
, bool* equal
) {
221 js::AssertHeapIsIdle();
223 cx
->check(value1
, value2
);
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());
243 JS_PUBLIC_API
bool JS::StrictlyEqual(JSContext
* cx
, Handle
<Value
> value1
,
244 Handle
<Value
> value2
, bool* equal
) {
245 js::AssertHeapIsIdle();
247 cx
->check(value1
, value2
);
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
);
267 if (IsNegativeZero(v2
)) {
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()) {
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");
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()) {
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()) {
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
>());
337 MOZ_ASSERT(CanUseBitwiseCompareForStrictlyEqual(lval
));
338 return lval
.asRawBits() == rval
.asRawBits();
343 JS_PUBLIC_API
bool JS::SameValue(JSContext
* cx
, Handle
<Value
> value1
,
344 Handle
<Value
> value2
, bool* same
) {
345 js::AssertHeapIsIdle();
347 cx
->check(value1
, value2
);
349 return js::SameValue(cx
, value1
, value2
, same
);
352 bool js::SameValueZero(JSContext
* cx
, Handle
<Value
> v1
, Handle
<Value
> v2
,
354 if (IsNaN(v1
) && IsNaN(v2
)) {
359 return js::StrictlyEqual(cx
, v1
, v2
, same
);