Bug 642274: Add loose equality to JSAPI. (r=Waldo)
authorChris Leary <cdleary@mozilla.com>
Mon, 21 Mar 2011 10:04:43 -0700
changeset 64551 d10c089a6888f29f4e097990b49afb1a5c16396b
parent 64550 8359361f8a7b64fe63ea20d9ebf6ffb770d42e7b
child 64552 1e5ded29dd9f2c15c9fa2453e844832217417b28
push idunknown
push userunknown
push dateunknown
reviewersWaldo
bugs642274
milestone2.2a1pre
Bug 642274: Add loose equality to JSAPI. (r=Waldo)
js/src/jsapi-tests/Makefile.in
js/src/jsapi-tests/testLooselyEqual.cpp
js/src/jsapi-tests/testVersion.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsinterp.cpp
js/src/jsinterp.h
--- a/js/src/jsapi-tests/Makefile.in
+++ b/js/src/jsapi-tests/Makefile.in
@@ -59,16 +59,17 @@ CPPSRCS = \
   testDefineGetterSetterNonEnumerable.cpp \
   testDefineProperty.cpp \
   testExtendedEq.cpp \
   testFuncCallback.cpp \
   testGCChunkAlloc.cpp \
   testGetPropertyDefault.cpp \
   testIntString.cpp \
   testLookup.cpp \
+  testLooselyEqual.cpp \
   testNewObject.cpp \
   testOps.cpp \
   testPropCache.cpp \
   testRegExpInstanceProperties.cpp \
   testResolveRecursion.cpp \
   testSameValue.cpp \
   testScriptObject.cpp \
   testSetProperty.cpp \
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testLooselyEqual.cpp
@@ -0,0 +1,168 @@
+#include "tests.h"
+#include <limits>
+#include <math.h>
+
+using namespace std;
+
+struct LooseEqualityFixture : public JSAPITest
+{
+    jsval qNaN;
+    jsval sNaN;
+    jsval d42;
+    jsval i42;
+    jsval undef;
+    jsval null;
+    jsval obj;
+    jsval poszero;
+    jsval negzero;
+
+    virtual ~LooseEqualityFixture() {}
+
+    virtual bool init() {
+        if (!JSAPITest::init())
+            return false;
+        qNaN = DOUBLE_TO_JSVAL(numeric_limits<double>::quiet_NaN());
+        sNaN = DOUBLE_TO_JSVAL(numeric_limits<double>::signaling_NaN());
+        d42 = DOUBLE_TO_JSVAL(42.0);
+        i42 = INT_TO_JSVAL(42);
+        undef = JSVAL_VOID;
+        null = JSVAL_NULL;
+        obj = OBJECT_TO_JSVAL(global);
+        poszero = DOUBLE_TO_JSVAL(0.0);
+        negzero = DOUBLE_TO_JSVAL(-0.0);
+#ifdef XP_WIN
+# define copysign _copysign
+#endif
+        JS_ASSERT(copysign(1.0, JSVAL_TO_DOUBLE(poszero)) == 1.0);
+        JS_ASSERT(copysign(1.0, JSVAL_TO_DOUBLE(negzero)) == -1.0);
+#ifdef XP_WIN
+# undef copysign
+#endif
+        return true;
+    }
+
+    bool leq(jsval x, jsval y) {
+        JSBool equal;
+        CHECK(JS_LooselyEqual(cx, x, y, &equal) && equal);
+        CHECK(JS_LooselyEqual(cx, y, x, &equal) && equal);
+        return true;
+    }
+
+    bool nleq(jsval x, jsval y) {
+        JSBool equal;
+        CHECK(JS_LooselyEqual(cx, x, y, &equal) && !equal);
+        CHECK(JS_LooselyEqual(cx, y, x, &equal) && !equal);
+        return true;
+    }
+};
+
+// 11.9.3 1a
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_undef)
+{
+    CHECK(leq(JSVAL_VOID, JSVAL_VOID));
+    return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_undef)
+
+// 11.9.3 1b
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_null)
+{
+    CHECK(leq(JSVAL_NULL, JSVAL_NULL));
+    return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_null)
+
+// 11.9.3 1ci
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_nan_nleq_all)
+{
+    CHECK(nleq(qNaN, qNaN));
+    CHECK(nleq(qNaN, sNaN));
+
+    CHECK(nleq(sNaN, sNaN));
+    CHECK(nleq(sNaN, qNaN));
+
+    CHECK(nleq(qNaN, d42));
+    CHECK(nleq(qNaN, i42));
+    CHECK(nleq(qNaN, undef));
+    CHECK(nleq(qNaN, null));
+    CHECK(nleq(qNaN, obj));
+
+    CHECK(nleq(sNaN, d42));
+    CHECK(nleq(sNaN, i42));
+    CHECK(nleq(sNaN, undef));
+    CHECK(nleq(sNaN, null));
+    CHECK(nleq(sNaN, obj));
+    return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_nan_nleq_all)
+
+// 11.9.3 1cii
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_all_nleq_nan)
+{
+    CHECK(nleq(qNaN, qNaN));
+    CHECK(nleq(qNaN, sNaN));
+
+    CHECK(nleq(sNaN, sNaN));
+    CHECK(nleq(sNaN, qNaN));
+
+    CHECK(nleq(d42,   qNaN));
+    CHECK(nleq(i42,   qNaN));
+    CHECK(nleq(undef, qNaN));
+    CHECK(nleq(null,  qNaN));
+    CHECK(nleq(obj,   qNaN));
+
+    CHECK(nleq(d42,   sNaN));
+    CHECK(nleq(i42,   sNaN));
+    CHECK(nleq(undef, sNaN));
+    CHECK(nleq(null,  sNaN));
+    CHECK(nleq(obj,   sNaN));
+    return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_all_nleq_nan)
+
+// 11.9.3 1ciii
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_leq_same_nums)
+{
+    CHECK(leq(d42, d42));
+    CHECK(leq(i42, i42));
+    CHECK(leq(d42, i42));
+    CHECK(leq(i42, d42));
+    return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_leq_same_nums)
+
+// 11.9.3 1civ
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_pz_leq_nz)
+{
+    CHECK(leq(poszero, negzero));
+    return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_pz_leq_nz)
+
+// 11.9.3 1cv
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_nz_leq_pz)
+{
+    CHECK(leq(negzero, poszero));
+    return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_nz_leq_pz)
+
+// 1cvi onwards NOT TESTED
+
+// 11.9.3 2
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_undef)
+{
+    CHECK(leq(null, undef));
+    return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_undef)
+
+// 11.9.3 3
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_null)
+{
+    CHECK(leq(undef, null));
+    return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_null)
+
+// 4 onwards NOT TESTED
--- a/js/src/jsapi-tests/testVersion.cpp
+++ b/js/src/jsapi-tests/testVersion.cpp
@@ -22,17 +22,18 @@ JSBool CaptureVersion(JSContext *cx, uin
 JSBool CheckOverride(JSContext *cx, uintN argc, jsval *vp);
 JSBool EvalScriptVersion16(JSContext *cx, uintN argc, jsval *vp);
 
 struct VersionFixture : public JSAPITest
 {
     JSVersion captured;
 
     virtual bool init() {
-        JSAPITest::init();
+        if (!JSAPITest::init())
+            return false;
         callbackData = this;
         captured = JSVERSION_UNKNOWN;
         return JS_DefineFunction(cx, global, "checkVersionHasXML", CheckVersionHasXML, 0, 0) &&
                JS_DefineFunction(cx, global, "disableXMLOption", DisableXMLOption, 0, 0) &&
                JS_DefineFunction(cx, global, "callSetVersion17", CallSetVersion17, 0, 0) &&
                JS_DefineFunction(cx, global, "checkNewScriptNoXML", CheckNewScriptNoXML, 0, 0) &&
                JS_DefineFunction(cx, global, "overrideVersion15", OverrideVersion15, 0, 0) &&
                JS_DefineFunction(cx, global, "captureVersion", CaptureVersion, 0, 0) &&
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -600,16 +600,23 @@ JS_GetTypeName(JSContext *cx, JSType typ
 JS_PUBLIC_API(JSBool)
 JS_StrictlyEqual(JSContext *cx, jsval v1, jsval v2, JSBool *equal)
 {
     assertSameCompartment(cx, v1, v2);
     return StrictlyEqual(cx, Valueify(v1), Valueify(v2), equal);
 }
 
 JS_PUBLIC_API(JSBool)
+JS_LooselyEqual(JSContext *cx, jsval v1, jsval v2, JSBool *equal)
+{
+    assertSameCompartment(cx, v1, v2);
+    return LooselyEqual(cx, Valueify(v1), Valueify(v2), equal);
+}
+
+JS_PUBLIC_API(JSBool)
 JS_SameValue(JSContext *cx, jsval v1, jsval v2, JSBool *same)
 {
     assertSameCompartment(cx, v1, v2);
     return SameValue(cx, Valueify(v1), Valueify(v2), same);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_IsBuiltinEvalFunction(JSFunction *fun)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -706,16 +706,19 @@ JS_TypeOfValue(JSContext *cx, jsval v);
 
 extern JS_PUBLIC_API(const char *)
 JS_GetTypeName(JSContext *cx, JSType type);
 
 extern JS_PUBLIC_API(JSBool)
 JS_StrictlyEqual(JSContext *cx, jsval v1, jsval v2, JSBool *equal);
 
 extern JS_PUBLIC_API(JSBool)
+JS_LooselyEqual(JSContext *cx, jsval v1, jsval v2, JSBool *equal);
+
+extern JS_PUBLIC_API(JSBool)
 JS_SameValue(JSContext *cx, jsval v1, jsval v2, JSBool *same);
 
 /* True iff fun is the global eval function. */
 extern JS_PUBLIC_API(JSBool)
 JS_IsBuiltinEvalFunction(JSFunction *fun);
 
 /* True iff fun is the Function constructor. */
 extern JS_PUBLIC_API(JSBool)
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -1080,16 +1080,90 @@ HasInstance(JSContext *cx, JSObject *obj
     if (clasp->hasInstance)
         return clasp->hasInstance(cx, obj, v, bp);
     js_ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
                         JSDVG_SEARCH_STACK, ObjectValue(*obj), NULL);
     return JS_FALSE;
 }
 
 bool
+LooselyEqual(JSContext *cx, const Value &lval, const Value &rval, JSBool *result)
+{
+#if JS_HAS_XML_SUPPORT
+    if (JS_UNLIKELY(lval.isObject() && lval.toObject().isXML()) ||
+                    (rval.isObject() && rval.toObject().isXML())) {
+        return js_TestXMLEquality(cx, lval, rval, result);
+    }
+#endif
+
+    if (SameType(lval, rval)) {
+        if (lval.isString()) {
+            JSString *l = lval.toString();
+            JSString *r = rval.toString();
+            return EqualStrings(cx, l, r, result);
+        }
+        
+        if (lval.isDouble()) {
+            double l = lval.toDouble(), r = rval.toDouble();
+            *result = JSDOUBLE_COMPARE(l, ==, r, false);
+            return true;
+        }
+        
+        if (lval.isObject()) {
+            JSObject *l = &lval.toObject();
+            JSObject *r = &rval.toObject();
+            l->assertSpecialEqualitySynced();
+
+            if (EqualityOp eq = l->getClass()->ext.equality) {
+                return eq(cx, l, &rval, result);
+            }
+
+            *result = l == r;
+            return true;
+        }
+
+        *result = lval.payloadAsRawUint32() == rval.payloadAsRawUint32();
+        return true;
+    }
+
+    if (lval.isNullOrUndefined()) {
+        *result = rval.isNullOrUndefined();
+        return true;
+    }
+    
+    if (rval.isNullOrUndefined()) {
+        *result = false;
+        return true;
+    }
+
+    Value lvalue = lval;
+    Value rvalue = rval;
+
+    if (lvalue.isObject() && !DefaultValue(cx, &lvalue.toObject(), JSTYPE_VOID, &lvalue))
+        return false;
+
+    if (rvalue.isObject() && !DefaultValue(cx, &rvalue.toObject(), JSTYPE_VOID, &rvalue))
+        return false;
+
+    if (lvalue.isString() && rvalue.isString()) {
+        JSString *l = lvalue.toString();
+        JSString *r = rvalue.toString();
+        return EqualStrings(cx, l, r, result);
+    }
+
+    double l, r;
+    if (!ValueToNumber(cx, lvalue, &l) ||
+        !ValueToNumber(cx, rvalue, &r)) {
+        return false;
+    }
+    *result = JSDOUBLE_COMPARE(l, ==, r, false);
+    return true;
+}
+
+bool
 StrictlyEqual(JSContext *cx, const Value &lref, const Value &rref, JSBool *equal)
 {
     Value lval = lref, rval = rref;
     if (SameType(lval, rval)) {
         if (lval.isString())
             return EqualStrings(cx, lval.toString(), rval.toString(), equal);
         if (lval.isDouble()) {
             *equal = JSDOUBLE_COMPARE(lval.toDouble(), ==, rval.toDouble(), JS_FALSE);
@@ -3368,104 +3442,38 @@ BEGIN_CASE(JSOP_BITXOR)
 END_CASE(JSOP_BITXOR)
 
 BEGIN_CASE(JSOP_BITAND)
     BITWISE_OP(&);
 END_CASE(JSOP_BITAND)
 
 #undef BITWISE_OP
 
-/*
- * NB: These macros can't use JS_BEGIN_MACRO/JS_END_MACRO around their bodies
- * because they begin if/else chains, so callers must not put semicolons after
- * the call expressions!
- */
-#if JS_HAS_XML_SUPPORT
-#define XML_EQUALITY_OP(OP)                                                   \
-    if ((lval.isObject() && lval.toObject().isXML()) ||                       \
-        (rval.isObject() && rval.toObject().isXML())) {                       \
-        if (!js_TestXMLEquality(cx, lval, rval, &cond))                       \
-            goto error;                                                       \
-        cond = cond OP JS_TRUE;                                               \
-    } else
-#else
-#define XML_EQUALITY_OP(OP)             /* nothing */
-#endif
-
-#define EQUALITY_OP(OP, IFNAN)                                                \
+#define EQUALITY_OP(OP)                                                       \
     JS_BEGIN_MACRO                                                            \
-        JSBool cond;                                                          \
         Value rval = regs.sp[-1];                                             \
         Value lval = regs.sp[-2];                                             \
-        XML_EQUALITY_OP(OP)                                                   \
-        if (SameType(lval, rval)) {                                           \
-            if (lval.isString()) {                                            \
-                JSString *l = lval.toString(), *r = rval.toString();          \
-                JSBool equal;                                                 \
-                if (!EqualStrings(cx, l, r, &equal))                          \
-                    goto error;                                               \
-                cond = equal OP JS_TRUE;                                      \
-            } else if (lval.isDouble()) {                                     \
-                double l = lval.toDouble(), r = rval.toDouble();              \
-                cond = JSDOUBLE_COMPARE(l, OP, r, IFNAN);                     \
-            } else if (lval.isObject()) {                                     \
-                JSObject *l = &lval.toObject(), *r = &rval.toObject();        \
-                l->assertSpecialEqualitySynced();                             \
-                if (EqualityOp eq = l->getClass()->ext.equality) {            \
-                    if (!eq(cx, l, &rval, &cond))                             \
-                        goto error;                                           \
-                    cond = cond OP JS_TRUE;                                   \
-                } else {                                                      \
-                    cond = l OP r;                                            \
-                }                                                             \
-            } else {                                                          \
-                cond = lval.payloadAsRawUint32() OP rval.payloadAsRawUint32();\
-            }                                                                 \
-        } else {                                                              \
-            if (lval.isNullOrUndefined()) {                                   \
-                cond = rval.isNullOrUndefined() OP true;                      \
-            } else if (rval.isNullOrUndefined()) {                            \
-                cond = true OP false;                                         \
-            } else {                                                          \
-                if (lval.isObject())                                          \
-                    DEFAULT_VALUE(cx, -2, JSTYPE_VOID, lval);                 \
-                if (rval.isObject())                                          \
-                    DEFAULT_VALUE(cx, -1, JSTYPE_VOID, rval);                 \
-                if (lval.isString() && rval.isString()) {                     \
-                    JSString *l = lval.toString(), *r = rval.toString();      \
-                    JSBool equal;                                             \
-                    if (!EqualStrings(cx, l, r, &equal))                      \
-                        goto error;                                           \
-                    cond = equal OP JS_TRUE;                                  \
-                } else {                                                      \
-                    double l, r;                                              \
-                    if (!ValueToNumber(cx, lval, &l) ||                       \
-                        !ValueToNumber(cx, rval, &r)) {                       \
-                        goto error;                                           \
-                    }                                                         \
-                    cond = JSDOUBLE_COMPARE(l, OP, r, IFNAN);                 \
-                }                                                             \
-            }                                                                 \
-        }                                                                     \
+        JSBool cond;                                                          \
+        if (!LooselyEqual(cx, lval, rval, &cond))                             \
+            goto error;                                                       \
+        cond = cond OP JS_TRUE;                                               \
         TRY_BRANCH_AFTER_COND(cond, 2);                                       \
         regs.sp--;                                                            \
         regs.sp[-1].setBoolean(cond);                                         \
     JS_END_MACRO
 
 BEGIN_CASE(JSOP_EQ)
-    EQUALITY_OP(==, false);
+    EQUALITY_OP(==);
 END_CASE(JSOP_EQ)
 
 BEGIN_CASE(JSOP_NE)
-    EQUALITY_OP(!=, true);
+    EQUALITY_OP(!=);
 END_CASE(JSOP_NE)
 
 #undef EQUALITY_OP
-#undef XML_EQUALITY_OP
-#undef EXTENDED_EQUALITY_OP
 
 #define STRICT_EQUALITY_OP(OP, COND)                                          \
     JS_BEGIN_MACRO                                                            \
         const Value &rref = regs.sp[-1];                                      \
         const Value &lref = regs.sp[-2];                                      \
         JSBool equal;                                                         \
         if (!StrictlyEqual(cx, lref, rref, &equal))                           \
             goto error;                                                       \
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -1072,16 +1072,19 @@ extern JS_REQUIRES_STACK bool
 RunScript(JSContext *cx, JSScript *script, JSStackFrame *fp);
 
 extern bool
 CheckRedeclaration(JSContext *cx, JSObject *obj, jsid id, uintN attrs);
 
 extern bool
 StrictlyEqual(JSContext *cx, const Value &lval, const Value &rval, JSBool *equal);
 
+extern bool
+LooselyEqual(JSContext *cx, const Value &lval, const Value &rval, JSBool *equal);
+
 /* === except that NaN is the same as NaN and -0 is not the same as +0. */
 extern bool
 SameValue(JSContext *cx, const Value &v1, const Value &v2, JSBool *same);
 
 extern JSType
 TypeOfValue(JSContext *cx, const Value &v);
 
 inline bool