summaryrefslogtreecommitdiffstats
path: root/js/src/vm/EqualityOperations.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/EqualityOperations.cpp')
-rw-r--r--js/src/vm/EqualityOperations.cpp360
1 files changed, 360 insertions, 0 deletions
diff --git a/js/src/vm/EqualityOperations.cpp b/js/src/vm/EqualityOperations.cpp
new file mode 100644
index 0000000000..66438879ce
--- /dev/null
+++ b/js/src/vm/EqualityOperations.cpp
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/EqualityOperations.h" // js::LooselyEqual, js::StrictlyEqual, js::SameValue
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF
+
+#include "jsnum.h" // js::StringToNumber
+#include "jstypes.h" // JS_PUBLIC_API
+
+#include "js/Context.h" // js::AssertHeapIsIdle
+#include "js/Equality.h" // JS::LooselyEqual, JS::StrictlyEqual, JS::SameValue
+#include "js/Result.h" // JS_TRY_VAR_OR_RETURN_FALSE
+#include "js/RootingAPI.h" // JS::Rooted
+#include "js/Value.h" // JS::Int32Value, JS::SameType, JS::Value
+#include "vm/BigIntType.h" // JS::BigInt
+#include "vm/JSContext.h" // CHECK_THREAD
+#include "vm/JSObject.h" // js::ToPrimitive
+#include "vm/StringType.h" // js::EqualStrings
+#ifdef ENABLE_RECORD_TUPLE
+# include "vm/RecordType.h"
+# include "vm/TupleType.h"
+#endif
+
+#include "builtin/Boolean-inl.h" // js::EmulatesUndefined
+#include "vm/JSContext-inl.h" // JSContext::check
+
+static bool EqualGivenSameType(JSContext* cx, JS::Handle<JS::Value> lval,
+ JS::Handle<JS::Value> rval, bool* equal) {
+ MOZ_ASSERT(JS::SameType(lval, rval));
+
+ if (lval.isString()) {
+ return js::EqualStrings(cx, lval.toString(), rval.toString(), equal);
+ }
+
+ if (lval.isDouble()) {
+ *equal = (lval.toDouble() == rval.toDouble());
+ return true;
+ }
+
+ if (lval.isBigInt()) {
+ *equal = JS::BigInt::equal(lval.toBigInt(), rval.toBigInt());
+ return true;
+ }
+
+#ifdef ENABLE_RECORD_TUPLE
+ // Record & Tuple proposal, section 3.2.6 (Strict Equality Comparison), step 3
+ // - https://tc39.es/proposal-record-tuple/#sec-strict-equality-comparison
+ //
+ // When computing equality, records and tuples are compared using the
+ // SameValueZero algorithm.
+ //
+ // NOTE: Since Records and Tuples are impemented as ExtendedPrimitives,
+ // "SameType" refers to the fact that both lval and rval are
+ // ExtendedPrimitives. They can still be different types (for example, a
+ // Record and a Tuple).
+ if (lval.isExtendedPrimitive()) {
+ JSObject* lobj = &lval.toExtendedPrimitive();
+ JSObject* robj = &rval.toExtendedPrimitive();
+
+ if (lobj->getClass() != robj->getClass()) {
+ *equal = false;
+ return true;
+ }
+
+ if (lobj->is<js::RecordType>()) {
+ return js::RecordType::sameValueZero(cx, &lobj->as<js::RecordType>(),
+ &robj->as<js::RecordType>(), equal);
+ }
+ if (lobj->is<js::TupleType>()) {
+ return js::TupleType::sameValueZero(cx, &lobj->as<js::TupleType>(),
+ &robj->as<js::TupleType>(), equal);
+ }
+ MOZ_CRASH("Unknown ExtendedPrimitive type");
+ }
+#endif
+
+ // Note: we can do a bitwise comparison even for Int32Value because both
+ // Values have the same type.
+ MOZ_ASSERT(CanUseBitwiseCompareForStrictlyEqual(lval) || lval.isInt32());
+
+ *equal = (lval.asRawBits() == rval.asRawBits());
+ MOZ_ASSERT_IF(lval.isUndefined() || lval.isNull(), *equal);
+ return true;
+}
+
+static bool LooselyEqualBooleanAndOther(JSContext* cx,
+ JS::Handle<JS::Value> lval,
+ JS::Handle<JS::Value> rval,
+ bool* result) {
+ MOZ_ASSERT(!rval.isBoolean());
+
+ JS::Rooted<JS::Value> lvalue(cx, JS::Int32Value(lval.toBoolean() ? 1 : 0));
+
+ // The tail-call would end up in Step 3.
+ if (rval.isNumber()) {
+ *result = (lvalue.toNumber() == rval.toNumber());
+ return true;
+ }
+ // The tail-call would end up in Step 6.
+ if (rval.isString()) {
+ double num;
+ if (!StringToNumber(cx, rval.toString(), &num)) {
+ return false;
+ }
+ *result = (lvalue.toNumber() == num);
+ return true;
+ }
+
+ return js::LooselyEqual(cx, lvalue, rval, result);
+}
+
+// ES6 draft rev32 7.2.12 Abstract Equality Comparison
+bool js::LooselyEqual(JSContext* cx, JS::Handle<JS::Value> lval,
+ JS::Handle<JS::Value> rval, bool* result) {
+ // Step 3.
+ if (JS::SameType(lval, rval)) {
+ return EqualGivenSameType(cx, lval, rval, result);
+ }
+
+ // Handle int32 x double.
+ if (lval.isNumber() && rval.isNumber()) {
+ *result = (lval.toNumber() == rval.toNumber());
+ return true;
+ }
+
+ // Step 4. This a bit more complex, because of the undefined emulating object.
+ if (lval.isNullOrUndefined()) {
+ // We can return early here, because null | undefined is only equal to the
+ // same set.
+ *result = rval.isNullOrUndefined() ||
+ (rval.isObject() && EmulatesUndefined(&rval.toObject()));
+ return true;
+ }
+
+ // Step 5.
+ if (rval.isNullOrUndefined()) {
+ MOZ_ASSERT(!lval.isNullOrUndefined());
+ *result = lval.isObject() && EmulatesUndefined(&lval.toObject());
+ return true;
+ }
+
+ // Step 6.
+ if (lval.isNumber() && rval.isString()) {
+ double num;
+ if (!StringToNumber(cx, rval.toString(), &num)) {
+ return false;
+ }
+ *result = (lval.toNumber() == num);
+ return true;
+ }
+
+ // Step 7.
+ if (lval.isString() && rval.isNumber()) {
+ double num;
+ if (!StringToNumber(cx, lval.toString(), &num)) {
+ return false;
+ }
+ *result = (num == rval.toNumber());
+ return true;
+ }
+
+ // Step 8.
+ if (lval.isBoolean()) {
+ return LooselyEqualBooleanAndOther(cx, lval, rval, result);
+ }
+
+ // Step 9.
+ if (rval.isBoolean()) {
+ return LooselyEqualBooleanAndOther(cx, rval, lval, result);
+ }
+
+ // Step 10.
+ if ((lval.isString() || lval.isNumber() || lval.isSymbol()) &&
+ rval.isObject()) {
+ JS::Rooted<JS::Value> rvalue(cx, rval);
+ if (!ToPrimitive(cx, &rvalue)) {
+ return false;
+ }
+ return js::LooselyEqual(cx, lval, rvalue, result);
+ }
+
+ // Step 11.
+ if (lval.isObject() &&
+ (rval.isString() || rval.isNumber() || rval.isSymbol())) {
+ JS::Rooted<JS::Value> lvalue(cx, lval);
+ if (!ToPrimitive(cx, &lvalue)) {
+ return false;
+ }
+ return js::LooselyEqual(cx, lvalue, rval, result);
+ }
+
+ if (lval.isBigInt()) {
+ JS::Rooted<JS::BigInt*> lbi(cx, lval.toBigInt());
+ bool tmpResult;
+ JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult,
+ JS::BigInt::looselyEqual(cx, lbi, rval));
+ *result = tmpResult;
+ return true;
+ }
+
+ if (rval.isBigInt()) {
+ JS::Rooted<JS::BigInt*> rbi(cx, rval.toBigInt());
+ bool tmpResult;
+ JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult,
+ JS::BigInt::looselyEqual(cx, rbi, lval));
+ *result = tmpResult;
+ return true;
+ }
+
+ // Step 12.
+ *result = false;
+ return true;
+}
+
+JS_PUBLIC_API bool JS::LooselyEqual(JSContext* cx, Handle<Value> value1,
+ Handle<Value> value2, bool* equal) {
+ js::AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(value1, value2);
+ MOZ_ASSERT(equal);
+ return js::LooselyEqual(cx, value1, value2, equal);
+}
+
+bool js::StrictlyEqual(JSContext* cx, JS::Handle<JS::Value> lval,
+ JS::Handle<JS::Value> rval, bool* equal) {
+ if (SameType(lval, rval)) {
+ return EqualGivenSameType(cx, lval, rval, equal);
+ }
+
+ if (lval.isNumber() && rval.isNumber()) {
+ *equal = (lval.toNumber() == rval.toNumber());
+ return true;
+ }
+
+ *equal = false;
+ return true;
+}
+
+JS_PUBLIC_API bool JS::StrictlyEqual(JSContext* cx, Handle<Value> value1,
+ Handle<Value> value2, bool* equal) {
+ js::AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(value1, value2);
+ MOZ_ASSERT(equal);
+ return js::StrictlyEqual(cx, value1, value2, equal);
+}
+
+static inline bool IsNegativeZero(const JS::Value& v) {
+ return v.isDouble() && mozilla::IsNegativeZero(v.toDouble());
+}
+
+static inline bool IsNaN(const JS::Value& v) {
+ return v.isDouble() && std::isnan(v.toDouble());
+}
+
+bool js::SameValue(JSContext* cx, JS::Handle<JS::Value> v1,
+ JS::Handle<JS::Value> v2, bool* same) {
+ if (IsNegativeZero(v1)) {
+ *same = IsNegativeZero(v2);
+ return true;
+ }
+
+ if (IsNegativeZero(v2)) {
+ *same = false;
+ return true;
+ }
+
+#ifdef ENABLE_RECORD_TUPLE
+ if (v1.isExtendedPrimitive()) {
+ JSObject* lobj = &v1.toExtendedPrimitive();
+ JSObject* robj = &v2.toExtendedPrimitive();
+
+ if (lobj->getClass() != robj->getClass()) {
+ *same = false;
+ return true;
+ }
+
+ if (lobj->is<js::RecordType>()) {
+ return js::RecordType::sameValue(cx, &lobj->as<js::RecordType>(),
+ &robj->as<js::RecordType>(), same);
+ }
+ if (lobj->is<js::TupleType>()) {
+ return js::TupleType::sameValue(cx, &lobj->as<js::TupleType>(),
+ &robj->as<js::TupleType>(), same);
+ }
+ MOZ_CRASH("Unknown ExtendedPrimitive type");
+ }
+#endif
+
+ return js::SameValueZero(cx, v1, v2, same);
+}
+
+#ifdef ENABLE_RECORD_TUPLE
+bool js::SameValueZeroLinear(const JS::Value& lval, const JS::Value& rval) {
+ if (lval.isNumber() && rval.isNumber()) {
+ return IsNaN(lval) ? IsNaN(rval) : lval.toNumber() == rval.toNumber();
+ }
+
+ if (lval.type() != rval.type()) {
+ return false;
+ }
+
+ switch (lval.type()) {
+ case ValueType::Double:
+ return IsNaN(lval) ? IsNaN(rval) : lval.toDouble() == rval.toDouble();
+
+ case ValueType::BigInt:
+ // BigInt values are considered equal if they represent the same
+ // mathematical value.
+ return BigInt::equal(lval.toBigInt(), rval.toBigInt());
+
+ case ValueType::String:
+ MOZ_ASSERT(lval.toString()->isLinear() && rval.toString()->isLinear());
+ return EqualStrings(&lval.toString()->asLinear(),
+ &rval.toString()->asLinear());
+
+ case ValueType::ExtendedPrimitive: {
+ JSObject& lobj = lval.toExtendedPrimitive();
+ JSObject& robj = rval.toExtendedPrimitive();
+ if (lobj.getClass() != robj.getClass()) {
+ return false;
+ }
+ if (lobj.is<RecordType>()) {
+ return RecordType::sameValueZero(&lobj.as<RecordType>(),
+ &robj.as<RecordType>());
+ }
+ MOZ_ASSERT(lobj.is<TupleType>());
+ return TupleType::sameValueZero(&lobj.as<TupleType>(),
+ &robj.as<TupleType>());
+ }
+
+ default:
+ MOZ_ASSERT(CanUseBitwiseCompareForStrictlyEqual(lval));
+ return lval.asRawBits() == rval.asRawBits();
+ }
+}
+#endif
+
+JS_PUBLIC_API bool JS::SameValue(JSContext* cx, Handle<Value> value1,
+ Handle<Value> value2, bool* same) {
+ js::AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(value1, value2);
+ MOZ_ASSERT(same);
+ return js::SameValue(cx, value1, value2, same);
+}
+
+bool js::SameValueZero(JSContext* cx, Handle<Value> v1, Handle<Value> v2,
+ bool* same) {
+ if (IsNaN(v1) && IsNaN(v2)) {
+ *same = true;
+ return true;
+ }
+
+ return js::StrictlyEqual(cx, v1, v2, same);
+}