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 "jsapi.h"    // js::AssertHeapIsIdle
12 #include "jsnum.h"    // js::StringToNumber
13 #include "jstypes.h"  // JS_PUBLIC_API
14 
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 
24 #include "builtin/Boolean-inl.h"  // js::EmulatesUndefined
25 #include "vm/JSContext-inl.h"     // JSContext::check
26 
EqualGivenSameType(JSContext * cx,JS::Handle<JS::Value> lval,JS::Handle<JS::Value> rval,bool * equal)27 static bool EqualGivenSameType(JSContext* cx, JS::Handle<JS::Value> lval,
28                                JS::Handle<JS::Value> rval, bool* equal) {
29   MOZ_ASSERT(JS::SameType(lval, rval));
30 
31   if (lval.isString()) {
32     return js::EqualStrings(cx, lval.toString(), rval.toString(), equal);
33   }
34 
35   if (lval.isDouble()) {
36     *equal = (lval.toDouble() == rval.toDouble());
37     return true;
38   }
39 
40   if (lval.isBigInt()) {
41     *equal = JS::BigInt::equal(lval.toBigInt(), rval.toBigInt());
42     return true;
43   }
44 
45   if (lval.isGCThing()) {  // objects or symbols
46     *equal = (lval.toGCThing() == rval.toGCThing());
47     return true;
48   }
49 
50   *equal = lval.get().payloadAsRawUint32() == rval.get().payloadAsRawUint32();
51   MOZ_ASSERT_IF(lval.isUndefined() || lval.isNull(), *equal);
52   return true;
53 }
54 
LooselyEqualBooleanAndOther(JSContext * cx,JS::Handle<JS::Value> lval,JS::Handle<JS::Value> rval,bool * result)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 // ES6 draft rev32 7.2.12 Abstract Equality Comparison
LooselyEqual(JSContext * cx,JS::Handle<JS::Value> lval,JS::Handle<JS::Value> rval,bool * result)82 bool js::LooselyEqual(JSContext* cx, JS::Handle<JS::Value> lval,
83                       JS::Handle<JS::Value> rval, bool* result) {
84   // Step 3.
85   if (JS::SameType(lval, rval)) {
86     return EqualGivenSameType(cx, lval, rval, result);
87   }
88 
89   // Handle int32 x double.
90   if (lval.isNumber() && rval.isNumber()) {
91     *result = (lval.toNumber() == rval.toNumber());
92     return true;
93   }
94 
95   // Step 4. This a bit more complex, because of the undefined emulating object.
96   if (lval.isNullOrUndefined()) {
97     // We can return early here, because null | undefined is only equal to the
98     // same set.
99     *result = rval.isNullOrUndefined() ||
100               (rval.isObject() && EmulatesUndefined(&rval.toObject()));
101     return true;
102   }
103 
104   // Step 5.
105   if (rval.isNullOrUndefined()) {
106     MOZ_ASSERT(!lval.isNullOrUndefined());
107     *result = lval.isObject() && EmulatesUndefined(&lval.toObject());
108     return true;
109   }
110 
111   // Step 6.
112   if (lval.isNumber() && rval.isString()) {
113     double num;
114     if (!StringToNumber(cx, rval.toString(), &num)) {
115       return false;
116     }
117     *result = (lval.toNumber() == num);
118     return true;
119   }
120 
121   // Step 7.
122   if (lval.isString() && rval.isNumber()) {
123     double num;
124     if (!StringToNumber(cx, lval.toString(), &num)) {
125       return false;
126     }
127     *result = (num == rval.toNumber());
128     return true;
129   }
130 
131   // Step 8.
132   if (lval.isBoolean()) {
133     return LooselyEqualBooleanAndOther(cx, lval, rval, result);
134   }
135 
136   // Step 9.
137   if (rval.isBoolean()) {
138     return LooselyEqualBooleanAndOther(cx, rval, lval, result);
139   }
140 
141   // Step 10.
142   if ((lval.isString() || lval.isNumber() || lval.isSymbol()) &&
143       rval.isObject()) {
144     JS::Rooted<JS::Value> rvalue(cx, rval);
145     if (!ToPrimitive(cx, &rvalue)) {
146       return false;
147     }
148     return js::LooselyEqual(cx, lval, rvalue, result);
149   }
150 
151   // Step 11.
152   if (lval.isObject() &&
153       (rval.isString() || rval.isNumber() || rval.isSymbol())) {
154     JS::Rooted<JS::Value> lvalue(cx, lval);
155     if (!ToPrimitive(cx, &lvalue)) {
156       return false;
157     }
158     return js::LooselyEqual(cx, lvalue, rval, result);
159   }
160 
161   if (lval.isBigInt()) {
162     JS::Rooted<JS::BigInt*> lbi(cx, lval.toBigInt());
163     bool tmpResult;
164     JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult,
165                                JS::BigInt::looselyEqual(cx, lbi, rval));
166     *result = tmpResult;
167     return true;
168   }
169 
170   if (rval.isBigInt()) {
171     JS::Rooted<JS::BigInt*> rbi(cx, rval.toBigInt());
172     bool tmpResult;
173     JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult,
174                                JS::BigInt::looselyEqual(cx, rbi, lval));
175     *result = tmpResult;
176     return true;
177   }
178 
179   // Step 12.
180   *result = false;
181   return true;
182 }
183 
LooselyEqual(JSContext * cx,Handle<Value> value1,Handle<Value> value2,bool * equal)184 JS_PUBLIC_API bool JS::LooselyEqual(JSContext* cx, Handle<Value> value1,
185                                     Handle<Value> value2, bool* equal) {
186   js::AssertHeapIsIdle();
187   CHECK_THREAD(cx);
188   cx->check(value1, value2);
189   MOZ_ASSERT(equal);
190   return js::LooselyEqual(cx, value1, value2, equal);
191 }
192 
StrictlyEqual(JSContext * cx,JS::Handle<JS::Value> lval,JS::Handle<JS::Value> rval,bool * equal)193 bool js::StrictlyEqual(JSContext* cx, JS::Handle<JS::Value> lval,
194                        JS::Handle<JS::Value> rval, bool* equal) {
195   if (SameType(lval, rval)) {
196     return EqualGivenSameType(cx, lval, rval, equal);
197   }
198 
199   if (lval.isNumber() && rval.isNumber()) {
200     *equal = (lval.toNumber() == rval.toNumber());
201     return true;
202   }
203 
204   *equal = false;
205   return true;
206 }
207 
StrictlyEqual(JSContext * cx,Handle<Value> value1,Handle<Value> value2,bool * equal)208 JS_PUBLIC_API bool JS::StrictlyEqual(JSContext* cx, Handle<Value> value1,
209                                      Handle<Value> value2, bool* equal) {
210   js::AssertHeapIsIdle();
211   CHECK_THREAD(cx);
212   cx->check(value1, value2);
213   MOZ_ASSERT(equal);
214   return js::StrictlyEqual(cx, value1, value2, equal);
215 }
216 
IsNegativeZero(const JS::Value & v)217 static inline bool IsNegativeZero(const JS::Value& v) {
218   return v.isDouble() && mozilla::IsNegativeZero(v.toDouble());
219 }
220 
IsNaN(const JS::Value & v)221 static inline bool IsNaN(const JS::Value& v) {
222   return v.isDouble() && mozilla::IsNaN(v.toDouble());
223 }
224 
SameValue(JSContext * cx,JS::Handle<JS::Value> v1,JS::Handle<JS::Value> v2,bool * same)225 bool js::SameValue(JSContext* cx, JS::Handle<JS::Value> v1,
226                    JS::Handle<JS::Value> v2, bool* same) {
227   if (IsNegativeZero(v1)) {
228     *same = IsNegativeZero(v2);
229     return true;
230   }
231 
232   if (IsNegativeZero(v2)) {
233     *same = false;
234     return true;
235   }
236 
237   if (IsNaN(v1) && IsNaN(v2)) {
238     *same = true;
239     return true;
240   }
241 
242   return js::StrictlyEqual(cx, v1, v2, same);
243 }
244 
SameValue(JSContext * cx,Handle<Value> value1,Handle<Value> value2,bool * same)245 JS_PUBLIC_API bool JS::SameValue(JSContext* cx, Handle<Value> value1,
246                                  Handle<Value> value2, bool* same) {
247   js::AssertHeapIsIdle();
248   CHECK_THREAD(cx);
249   cx->check(value1, value2);
250   MOZ_ASSERT(same);
251   return js::SameValue(cx, value1, value2, same);
252 }
253