diff options
-rw-r--r-- | jstests/core/numberlong.js | 30 | ||||
-rw-r--r-- | jstests/core/numberlong2.js | 8 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/numberlong.cpp | 167 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/numberlong.h | 13 |
4 files changed, 158 insertions, 60 deletions
diff --git a/jstests/core/numberlong.js b/jstests/core/numberlong.js index 4f1d27d6cb0..acbde248889 100644 --- a/jstests/core/numberlong.js +++ b/jstests/core/numberlong.js @@ -25,6 +25,21 @@ a.a = n; p = tojson( a ); assert.eq.automsg( "'{ \"a\" : NumberLong(-4) }'", "p" ); +// double +n = new NumberLong(4294967296); // 2^32 +assert.eq.automsg( "4294967296", "n" ); +assert.eq.automsg( "4294967296", "n.toNumber()" ); +assert.eq.automsg( "4294967295", "n - 1" ); +assert.eq.automsg( "'NumberLong(\"4294967296\")'", "n.toString()" ); +assert.eq.automsg( "'NumberLong(\"4294967296\")'", "tojson( n )" ); +assert.eq.automsg( "4294967296", "n.floatApprox" ); +assert.eq.automsg( "", "n.top" ); +assert.eq.automsg( "", "n.bottom" ); +a = {} +a.a = n; +p = tojson( a ); +assert.eq.automsg( "'{ \"a\" : NumberLong(\"4294967296\") }'", "p" ); + // too big to fit in double n = new NumberLong( "11111111111111111" ); assert.eq.automsg( "11111111111111112", "n.toNumber()" ); @@ -44,11 +59,24 @@ assert.eq.automsg( "-11111111111111112", "n.toNumber()" ); assert.eq.automsg( "-11111111111111108", "n + 4" ); assert.eq.automsg( "'NumberLong(\"-11111111111111111\")'", "n.toString()" ); assert.eq.automsg( "'NumberLong(\"-11111111111111111\")'", "tojson( n )" ); -a = {}; +assert.eq.automsg( "-11111111111111112", "n.floatApprox" ); +assert.eq.automsg( "4292380288", "n.top" ); +assert.eq.automsg( "3643379257", "n.bottom" ); +a = {} a.a = n; p = tojson( a ); assert.eq.automsg( "'{ \"a\" : NumberLong(\"-11111111111111111\") }'", "p" ); +n = new NumberLong( "9223372036854775807" ); +assert.eq.automsg( "9223372036854775807", "n.floatApprox" ); +assert.eq.automsg( "2147483647", "n.top" ); +assert.eq.automsg( "4294967295", "n.bottom" ); + +n = new NumberLong( 9223372036854775807, 2147483647, 4294967295 ); +assert.eq.automsg( "9223372036854775807", "n.floatApprox" ); +assert.eq.automsg( "2147483647", "n.top" ); +assert.eq.automsg( "4294967295", "n.bottom" ); + // parsing assert.throws.automsg( function() { new NumberLong( "" ); } ); assert.throws.automsg( function() { new NumberLong( "y" ); } ); diff --git a/jstests/core/numberlong2.js b/jstests/core/numberlong2.js index 5d7529a9e21..c730345f307 100644 --- a/jstests/core/numberlong2.js +++ b/jstests/core/numberlong2.js @@ -12,11 +12,11 @@ function chk(longNum) { assert.eq(longNum, t.find({}, { _id: 0, x: 1 }).hint({ x: 1 }).next().x); } -chk( NumberLong("1123539983311657217") ); +chk(NumberLong("1123539983311657217")); chk(NumberLong("-1123539983311657217")); - chk(NumberLong("4503599627370495")); - chk(NumberLong("4503599627370496")); - chk(NumberLong("4503599627370497")); +chk(NumberLong("4503599627370495")); +chk(NumberLong("4503599627370496")); +chk(NumberLong("4503599627370497")); t.remove({}); diff --git a/src/mongo/scripting/mozjs/numberlong.cpp b/src/mongo/scripting/mozjs/numberlong.cpp index c1bab0979e0..7abef7ed34c 100644 --- a/src/mongo/scripting/mozjs/numberlong.cpp +++ b/src/mongo/scripting/mozjs/numberlong.cpp @@ -48,36 +48,29 @@ const JSFunctionSpec NumberLongInfo::methods[5] = { MONGO_ATTACH_JS_CONSTRAINED_METHOD(toString, NumberLongInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD(valueOf, NumberLongInfo), MONGO_ATTACH_JS_CONSTRAINED_METHOD(compare, NumberLongInfo), - JS_FS_END, -}; + JS_FS_END}; const char* const NumberLongInfo::className = "NumberLong"; -long long NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleValue thisv) { - JS::RootedObject obj(cx, thisv.toObjectOrNull()); - return ToNumberLong(cx, obj); -} - -long long NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleObject thisv) { - ObjectWrapper o(cx, thisv); +void NumberLongInfo::finalize(JSFreeOp* fop, JSObject* obj) { + auto numLong = static_cast<int*>(JS_GetPrivate(obj)); - if (!o.hasOwnField(InternedString::top)) { - if (!o.hasOwnField(InternedString::floatApprox)) - uasserted(ErrorCodes::InternalError, "No top and no floatApprox fields"); - - return o.getNumberLongLong(InternedString::floatApprox); - } + if (numLong) + delete numLong; +} - if (!o.hasOwnField(InternedString::bottom)) - uasserted(ErrorCodes::InternalError, "top but no bottom field"); +int64_t NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleValue thisv) { + auto numLong = static_cast<int64_t*>(JS_GetPrivate(thisv.toObjectOrNull())); + return numLong ? *numLong : 0; +} - return ((unsigned long long)((long long)o.getNumberLongLong(InternedString::top) << 32) + - (unsigned)(o.getNumberLongLong(InternedString::bottom))); +int64_t NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleObject thisv) { + auto numLong = static_cast<int64_t*>(JS_GetPrivate(thisv)); + return numLong ? *numLong : 0; } void NumberLongInfo::Functions::valueOf::call(JSContext* cx, JS::CallArgs args) { - long long out = NumberLongInfo::ToNumberLong(cx, args.thisv()); - + int64_t out = NumberLongInfo::ToNumberLong(cx, args.thisv()); args.rval().setDouble(out); } @@ -88,15 +81,16 @@ void NumberLongInfo::Functions::toNumber::call(JSContext* cx, JS::CallArgs args) void NumberLongInfo::Functions::toString::call(JSContext* cx, JS::CallArgs args) { str::stream ss; - long long val = NumberLongInfo::ToNumberLong(cx, args.thisv()); + int64_t val = NumberLongInfo::ToNumberLong(cx, args.thisv()); - const long long limit = 2LL << 30; + const int64_t limit = 2LL << 30; if (val <= -limit || limit <= val) ss << "NumberLong(\"" << val << "\")"; else ss << "NumberLong(" << val << ")"; + ValueReader(cx, args.rval()).fromStringData(ss.operator std::string()); } @@ -106,8 +100,8 @@ void NumberLongInfo::Functions::compare::call(JSContext* cx, JS::CallArgs args) "NumberLong.compare() argument must be an object", args.get(0).isObject()); - long long thisVal = NumberLongInfo::ToNumberLong(cx, args.thisv()); - long long otherVal = NumberLongInfo::ToNumberLong(cx, args.get(0)); + int64_t thisVal = NumberLongInfo::ToNumberLong(cx, args.thisv()); + int64_t otherVal = NumberLongInfo::ToNumberLong(cx, args.get(0)); int comparison = 0; if (thisVal < otherVal) { @@ -119,6 +113,35 @@ void NumberLongInfo::Functions::compare::call(JSContext* cx, JS::CallArgs args) args.rval().setDouble(comparison); } +void NumberLongInfo::Functions::floatApprox::call(JSContext* cx, JS::CallArgs args) { + int64_t numLong = NumberLongInfo::ToNumberLong(cx, args.thisv()); + args.rval().setDouble(numLong); +} + +void NumberLongInfo::Functions::top::call(JSContext* cx, JS::CallArgs args) { + int64_t numLong = NumberLongInfo::ToNumberLong(cx, args.thisv()); + + // values above 2^53 are not accurately represented in JS + if (numLong == INT64_MIN || std::abs(numLong) >= 9007199254740992LL) { + auto val64 = static_cast<unsigned long long>(numLong); + args.rval().setDouble(val64 >> 32); + } else { + args.rval().setNull(); + } +} + +void NumberLongInfo::Functions::bottom::call(JSContext* cx, JS::CallArgs args) { + int64_t numLong = NumberLongInfo::ToNumberLong(cx, args.thisv()); + + // values above 2^53 are not accurately represented in JS + if (numLong == INT64_MIN || std::abs(numLong) >= 9007199254740992LL) { + auto val64 = static_cast<unsigned long long>(numLong); + args.rval().setDouble(val64 & 0x00000000FFFFFFFF); + } else { + args.rval().setNull(); + } +} + void NumberLongInfo::construct(JSContext* cx, JS::CallArgs args) { uassert(ErrorCodes::BadValue, "NumberLong needs 0, 1 or 3 arguments", @@ -129,60 +152,98 @@ void NumberLongInfo::construct(JSContext* cx, JS::CallArgs args) { JS::RootedObject thisv(cx); scope->getProto<NumberLongInfo>().newObject(&thisv); - ObjectWrapper o(cx, thisv); - JS::RootedValue floatApprox(cx); - JS::RootedValue top(cx); - JS::RootedValue bottom(cx); + int64_t numLong; + + ObjectWrapper o(cx, thisv); if (args.length() == 0) { - o.setNumber(InternedString::floatApprox, 0); + numLong = 0; } else if (args.length() == 1) { auto arg = args.get(0); - if (arg.isNumber()) { - o.setValue(InternedString::floatApprox, arg); + + if (arg.isInt32()) { + numLong = arg.toInt32(); + } else if (arg.isDouble()) { + numLong = arg.toDouble(); } else { std::string str = ValueWriter(cx, arg).toString(); - long long val; + int64_t val; // For string values we call strtoll because we expect non-number string - // values to fail rather than return 0 (which is the behavior of ToInt64. + // values to fail rather than return 0 (which is the behavior of ToInt64). if (arg.isString()) val = parseLL(str.c_str()); - // Otherwise we call the toNumber on the js value to get the long long value. + // Otherwise we call the toNumber on the js value to get the int64_t value. else val = ValueWriter(cx, arg).toInt64(); - // values above 2^53 are not accurately represented in JS - if (val == INT64_MIN || std::abs(val) >= 9007199254740992LL) { - auto val64 = static_cast<unsigned long long>(val); - o.setNumber(InternedString::floatApprox, val); - o.setNumber(InternedString::top, val64 >> 32); - o.setNumber(InternedString::bottom, val64 & 0x00000000ffffffff); - } else { - o.setNumber(InternedString::floatApprox, val); - } + numLong = val; } } else { if (!args.get(0).isNumber()) uasserted(ErrorCodes::BadValue, "floatApprox must be a number"); - if (!args.get(1).isNumber() || - args.get(1).toNumber() != - static_cast<double>(static_cast<uint32_t>(args.get(1).toNumber()))) + double top; + if (args.get(1).isNumber()) + top = static_cast<double>(static_cast<uint32_t>(args.get(1).toNumber())); + + if (!args.get(1).isNumber() || args.get(1).toNumber() != top) uasserted(ErrorCodes::BadValue, "top must be a 32 bit unsigned number"); - if (!args.get(2).isNumber() || - args.get(2).toNumber() != - static_cast<double>(static_cast<uint32_t>(args.get(2).toNumber()))) + double bot; + if (args.get(2).isNumber()) + bot = static_cast<double>(static_cast<uint32_t>(args.get(2).toNumber())); + + if (!args.get(2).isNumber() || args.get(2).toNumber() != bot) uasserted(ErrorCodes::BadValue, "bottom must be a 32 bit unsigned number"); - o.setValue(InternedString::floatApprox, args.get(0)); - o.setValue(InternedString::top, args.get(1)); - o.setValue(InternedString::bottom, args.get(2)); + numLong = (static_cast<uint64_t>(top) << 32) + static_cast<uint64_t>(bot); } + JS_SetPrivate(thisv, new int64_t(numLong)); + args.rval().setObjectOrNull(thisv); } +void NumberLongInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) { + JS::RootedValue undef(cx); + undef.setUndefined(); + + // floatapprox + if (!JS_DefinePropertyById( + cx, + proto, + getScope(cx)->getInternedStringId(InternedString::floatApprox), + undef, + JSPROP_READONLY | JSPROP_ENUMERATE, + smUtils::wrapConstrainedMethod<Functions::floatApprox, true, NumberLongInfo>, + nullptr)) { + uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_DefinePropertyById"); + } + + // top + if (!JS_DefinePropertyById(cx, + proto, + getScope(cx)->getInternedStringId(InternedString::top), + undef, + JSPROP_READONLY | JSPROP_ENUMERATE, + smUtils::wrapConstrainedMethod<Functions::top, true, NumberLongInfo>, + nullptr)) { + uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_DefinePropertyById"); + } + + // bottom + if (!JS_DefinePropertyById( + cx, + proto, + getScope(cx)->getInternedStringId(InternedString::bottom), + undef, + JSPROP_READONLY | JSPROP_ENUMERATE, + smUtils::wrapConstrainedMethod<Functions::bottom, true, NumberLongInfo>, + nullptr)) { + uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_DefinePropertyById"); + } +} + } // namespace mozjs } // namespace mongo diff --git a/src/mongo/scripting/mozjs/numberlong.h b/src/mongo/scripting/mozjs/numberlong.h index 145afd387b5..22bf2b94790 100644 --- a/src/mongo/scripting/mozjs/numberlong.h +++ b/src/mongo/scripting/mozjs/numberlong.h @@ -28,6 +28,8 @@ #pragma once +#include <jsapi.h> + #include "mongo/scripting/mozjs/wraptype.h" namespace mongo { @@ -49,20 +51,27 @@ namespace mozjs { */ struct NumberLongInfo : public BaseInfo { static void construct(JSContext* cx, JS::CallArgs args); + static void finalize(JSFreeOp* fop, JSObject* obj); struct Functions { MONGO_DECLARE_JS_FUNCTION(toNumber); MONGO_DECLARE_JS_FUNCTION(toString); MONGO_DECLARE_JS_FUNCTION(valueOf); MONGO_DECLARE_JS_FUNCTION(compare); + MONGO_DECLARE_JS_FUNCTION(floatApprox); + MONGO_DECLARE_JS_FUNCTION(top); + MONGO_DECLARE_JS_FUNCTION(bottom); }; static const JSFunctionSpec methods[5]; static const char* const className; + static const unsigned classFlags = JSCLASS_HAS_PRIVATE; + + static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto); - static long long ToNumberLong(JSContext* cx, JS::HandleObject object); - static long long ToNumberLong(JSContext* cx, JS::HandleValue value); + static int64_t ToNumberLong(JSContext* cx, JS::HandleObject object); + static int64_t ToNumberLong(JSContext* cx, JS::HandleValue value); }; } // namespace mozjs |