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