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/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
27 
28 #include "builtin/Boolean-inl.h"  // js::EmulatesUndefined
29 #include "vm/JSContext-inl.h"     // JSContext::check
30 
EqualGivenSameType(JSContext * cx,JS::Handle<JS::Value> lval,JS::Handle<JS::Value> rval,bool * equal)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));
34 
35   if (lval.isString()) {
36     return js::EqualStrings(cx, lval.toString(), rval.toString(), equal);
37   }
38 
39   if (lval.isDouble()) {
40     *equal = (lval.toDouble() == rval.toDouble());
41     return true;
42   }
43 
44   if (lval.isBigInt()) {
45     *equal = JS::BigInt::equal(lval.toBigInt(), rval.toBigInt());
46     return true;
47   }
48 
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
52   //
53   // When computing equality, records and tuples are compared using the
54   // SameValueZero algorithm.
55   //
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();
63 
64     if (lobj->getClass() != robj->getClass()) {
65       *equal = false;
66       return true;
67     }
68 
69     if (lobj->is<js::RecordType>()) {
70       return js::RecordType::sameValueZero(cx, &lobj->as<js::RecordType>(),
71                                            &robj->as<js::RecordType>(), equal);
72     }
73     if (lobj->is<js::TupleType>()) {
74       return js::TupleType::sameValueZero(cx, &lobj->as<js::TupleType>(),
75                                           &robj->as<js::TupleType>(), equal);
76     }
77     MOZ_CRASH("Unknown ExtendedPrimitive type");
78   }
79 #endif
80 
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());
84 
85   *equal = (lval.asRawBits() == rval.asRawBits());
86   MOZ_ASSERT_IF(lval.isUndefined() || lval.isNull(), *equal);
87   return true;
88 }
89 
LooselyEqualBooleanAndOther(JSContext * cx,JS::Handle<JS::Value> lval,JS::Handle<JS::Value> rval,bool * result)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());
95 
96   JS::Rooted<JS::Value> lvalue(cx, JS::Int32Value(lval.toBoolean() ? 1 : 0));
97 
98   // The tail-call would end up in Step 3.
99   if (rval.isNumber()) {
100     *result = (lvalue.toNumber() == rval.toNumber());
101     return true;
102   }
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;
108     }
109     *result = (lvalue.toNumber() == num);
110     return true;
111   }
112 
113   return js::LooselyEqual(cx, lvalue, rval, result);
114 }
115 
116 // ES6 draft rev32 7.2.12 Abstract Equality Comparison
LooselyEqual(JSContext * cx,JS::Handle<JS::Value> lval,JS::Handle<JS::Value> rval,bool * result)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);
122   }
123 
124   // Handle int32 x double.
125   if (lval.isNumber() && rval.isNumber()) {
126     *result = (lval.toNumber() == rval.toNumber());
127     return true;
128   }
129 
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;
137   }
138 
139   // Step 5.
140   if (rval.isNullOrUndefined()) {
141     MOZ_ASSERT(!lval.isNullOrUndefined());
142     *result = lval.isObject() && EmulatesUndefined(&lval.toObject());
143     return true;
144   }
145 
146   // Step 6.
147   if (lval.isNumber() && rval.isString()) {
148     double num;
149     if (!StringToNumber(cx, rval.toString(), &num)) {
150       return false;
151     }
152     *result = (lval.toNumber() == num);
153     return true;
154   }
155 
156   // Step 7.
157   if (lval.isString() && rval.isNumber()) {
158     double num;
159     if (!StringToNumber(cx, lval.toString(), &num)) {
160       return false;
161     }
162     *result = (num == rval.toNumber());
163     return true;
164   }
165 
166   // Step 8.
167   if (lval.isBoolean()) {
168     return LooselyEqualBooleanAndOther(cx, lval, rval, result);
169   }
170 
171   // Step 9.
172   if (rval.isBoolean()) {
173     return LooselyEqualBooleanAndOther(cx, rval, lval, result);
174   }
175 
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;
182     }
183     return js::LooselyEqual(cx, lval, rvalue, result);
184   }
185 
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;
192     }
193     return js::LooselyEqual(cx, lvalue, rval, result);
194   }
195 
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;
203   }
204 
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;
212   }
213 
214   // Step 12.
215   *result = false;
216   return true;
217 }
218 
LooselyEqual(JSContext * cx,Handle<Value> value1,Handle<Value> value2,bool * equal)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);
226 }
227 
StrictlyEqual(JSContext * cx,JS::Handle<JS::Value> lval,JS::Handle<JS::Value> rval,bool * 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);
232   }
233 
234   if (lval.isNumber() && rval.isNumber()) {
235     *equal = (lval.toNumber() == rval.toNumber());
236     return true;
237   }
238 
239   *equal = false;
240   return true;
241 }
242 
StrictlyEqual(JSContext * cx,Handle<Value> value1,Handle<Value> value2,bool * equal)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);
250 }
251 
IsNegativeZero(const JS::Value & v)252 static inline bool IsNegativeZero(const JS::Value& v) {
253   return v.isDouble() && mozilla::IsNegativeZero(v.toDouble());
254 }
255 
IsNaN(const JS::Value & v)256 static inline bool IsNaN(const JS::Value& v) {
257   return v.isDouble() && mozilla::IsNaN(v.toDouble());
258 }
259 
SameValue(JSContext * cx,JS::Handle<JS::Value> v1,JS::Handle<JS::Value> v2,bool * same)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;
265   }
266 
267   if (IsNegativeZero(v2)) {
268     *same = false;
269     return true;
270   }
271 
272 #ifdef ENABLE_RECORD_TUPLE
273   if (v1.isExtendedPrimitive()) {
274     JSObject* lobj = &v1.toExtendedPrimitive();
275     JSObject* robj = &v2.toExtendedPrimitive();
276 
277     if (lobj->getClass() != robj->getClass()) {
278       *same = false;
279       return true;
280     }
281 
282     if (lobj->is<js::RecordType>()) {
283       return js::RecordType::sameValue(cx, &lobj->as<js::RecordType>(),
284                                        &robj->as<js::RecordType>(), same);
285     }
286     if (lobj->is<js::TupleType>()) {
287       return js::TupleType::sameValue(cx, &lobj->as<js::TupleType>(),
288                                       &robj->as<js::TupleType>(), same);
289     }
290     MOZ_CRASH("Unknown ExtendedPrimitive type");
291   }
292 #endif
293 
294   return js::SameValueZero(cx, v1, v2, same);
295 }
296 
297 #ifdef ENABLE_RECORD_TUPLE
SameValueZeroLinear(const JS::Value & lval,const JS::Value & rval)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();
301   }
302 
303   if (lval.type() != rval.type()) {
304     return false;
305   }
306 
307   switch (lval.type()) {
308     case ValueType::Double:
309       return IsNaN(lval) ? IsNaN(rval) : lval.toDouble() == rval.toDouble();
310 
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());
315 
316     case ValueType::String:
317       MOZ_ASSERT(lval.toString()->isLinear() && rval.toString()->isLinear());
318       return EqualStrings(&lval.toString()->asLinear(),
319                           &rval.toString()->asLinear());
320 
321     case ValueType::ExtendedPrimitive: {
322       JSObject& lobj = lval.toExtendedPrimitive();
323       JSObject& robj = rval.toExtendedPrimitive();
324       if (lobj.getClass() != robj.getClass()) {
325         return false;
326       }
327       if (lobj.is<RecordType>()) {
328         return RecordType::sameValueZero(&lobj.as<RecordType>(),
329                                          &robj.as<RecordType>());
330       }
331       MOZ_ASSERT(lobj.is<TupleType>());
332       return TupleType::sameValueZero(&lobj.as<TupleType>(),
333                                       &robj.as<TupleType>());
334     }
335 
336     default:
337       MOZ_ASSERT(CanUseBitwiseCompareForStrictlyEqual(lval));
338       return lval.asRawBits() == rval.asRawBits();
339   }
340 }
341 #endif
342 
SameValue(JSContext * cx,Handle<Value> value1,Handle<Value> value2,bool * same)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);
350 }
351 
SameValueZero(JSContext * cx,Handle<Value> v1,Handle<Value> v2,bool * 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;
357   }
358 
359   return js::StrictlyEqual(cx, v1, v2, same);
360 }
361