explanation of https://twitter.com/getify/status/780249160662605824 in terms of SpiderMonkey (Firefox) internals
edit: The tweet linked above has since been deleted, but the original code read something like:
' \t\n\r\u000c\u000b\uFEFF\u0020' == 0 // true
-
The script is parsed and compiled to bytecode.
-
For simplicity's sake, we assume the code runs through the interpreter rather than one of the JIT compiler backends. The interpreter sees the
JSOP_EQ
bytecode, representing the non-strict equality operator (==
).CASE(JSOP_EQ) if (!LooseEqualityOp<true>(cx, REGS)) goto error; END_CASE(JSOP_EQ)
-
The interpreter calls
LooseEqualityOp
template <bool Eq> static MOZ_ALWAYS_INLINE bool LooseEqualityOp(JSContext* cx, InterpreterRegs& regs) { HandleValue rval = regs.stackHandleAt(-1); HandleValue lval = regs.stackHandleAt(-2); bool cond; if (!LooselyEqual(cx, lval, rval, &cond)) return false; cond = (cond == Eq); regs.sp--; regs.sp[-1].setBoolean(cond); return true; }
which in turn calls
js::LooselyEqual
// ES6 draft rev32 7.2.12 Abstract Equality Comparison bool js::LooselyEqual(JSContext* cx, HandleValue lval, HandleValue rval, bool* result) {
-
js::LooselyEqual
performs some tests on the left- and right-hand values of the==
operator until it comes to// Step 7. if (lval.isString() && rval.isNumber()) { double num; if (!StringToNumber(cx, lval.toString(), &num)) return false;
The left-hand value is a string and the right-hand value is a number, so
StringToNumber
is called to convert the lval to a number. -
StringToNumber
delegates to the function CharsToNumber, which skips the spaces in the string.\t
,\n
, ...,\uFEFF
,\u0020
are all space characters, so the result is an empty array of characters (""
). -
CharsToNumber
in turn callsjs_strtod
(double d
is the result number)const CharT* ep; double d; if (!js_strtod(cx, bp, end, &ep, &d)) { *result = GenericNaN(); return false; }
-
In this case,
js_strtod
performs some checks and ultimately callsjs_strtod_harder
(better, faster, stronger...)*d = js_strtod_harder(cx->dtoaState(), chars.begin(), &ep, &err);
and
js_strtod_harder
is a thin wrapper around_strtod
double js_strtod_harder(DtoaState* state, const char* s00, char** se, int* err) { double retval; if (err) *err = 0; retval = _strtod(state, s00, se); return retval; }
-
_strtod
("string to double") is an insanely convoluted function, complicated by lots of platform-specific#ifdef
s. It's part ofdtoa.c
, originally written by David M. Gay and pulled in by a bunch of projects including Firefox. In this case, it ends up seeing that the input char array is emptycase 0: goto ret0;
and ultimately returns zero.
-
The zero result propagates all the way back to
js::LooselyEqual
; the left-hand operand of==
has been converted to the number0
. Finally, this number 0 is compared with the number 0 that is the right-hand operand*result = (num == rval.toNumber()); return true;
result
istrue
, which is the value of==
given back to the script.return true
here indicates that no error occurred (SpiderMonkey uses return codes instead of C++ exceptions).