namespace mozjs { const JSFunctionSpec GlobalInfo::freeFunctions[4] = { MONGO_ATTACH_JS_FUNCTION(gc), MONGO_ATTACH_JS_FUNCTION(print), MONGO_ATTACH_JS_FUNCTION(version), JS_FS_END, }; const char* const GlobalInfo::className = "Global"; namespace { logger::MessageLogDomain* jsPrintLogDomain; } // namespace void GlobalInfo::Functions::print::call(JSContext* cx, JS::CallArgs args) { logger::LogstreamBuilder builder(jsPrintLogDomain, getThreadName(), logger::LogSeverity::Log()); std::ostream& ss = builder.stream(); bool first = true; for (size_t i = 0; i < args.length(); i++) { if (first) first = false; else ss << " "; if (args.get(i).isNullOrUndefined()) { // failed to get object to convert ss << "[unknown type]"; continue; } JSStringWrapper jsstr(cx, JS::ToString(cx, args.get(i))); ss << jsstr.toStringData(); } ss << std::endl; args.rval().setUndefined(); } void GlobalInfo::Functions::version::call(JSContext* cx, JS::CallArgs args) { ValueReader(cx, args.rval()).fromStringData(JS_VersionToString(JS_GetVersion(cx))); } void GlobalInfo::Functions::gc::call(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); scope->gc(); args.rval().setUndefined(); } MONGO_INITIALIZER(JavascriptPrintDomain)(InitializerContext*) { jsPrintLogDomain = logger::globalLogManager()->getNamedDomain("javascriptOutput"); return Status::OK(); } } // namespace mozjs
namespace mozjs { const JSFunctionSpec ObjectInfo::methods[3] = { MONGO_ATTACH_JS_FUNCTION(bsonsize), MONGO_ATTACH_JS_FUNCTION(invalidForStorage), JS_FS_END, }; const char* const ObjectInfo::className = "Object"; void ObjectInfo::Functions::bsonsize::call(JSContext* cx, JS::CallArgs args) { if (args.length() != 1) uasserted(ErrorCodes::BadValue, "bsonsize needs 1 argument"); if (args.get(0).isNull()) { args.rval().setInt32(0); return; } if (!args.get(0).isObject()) uasserted(ErrorCodes::BadValue, "argument to bsonsize has to be an object"); args.rval().setInt32(ValueWriter(cx, args.get(0)).toBSON().objsize()); } void ObjectInfo::Functions::invalidForStorage::call(JSContext* cx, JS::CallArgs args) { if (args.length() != 1) uasserted(ErrorCodes::BadValue, "invalidForStorage needs 1 argument"); if (args.get(0).isNull()) { args.rval().setNull(); return; } if (!args.get(0).isObject()) uasserted(ErrorCodes::BadValue, "argument to invalidForStorage has to be an object"); Status validForStorage = ValueWriter(cx, args.get(0)).toBSON().storageValid(true); if (validForStorage.isOK()) { args.rval().setNull(); return; } std::string errmsg = str::stream() << validForStorage.codeString() << ": " << validForStorage.reason(); ValueReader(cx, args.rval()).fromStringData(errmsg); } } // namespace mozjs
namespace mozjs { // These are all executed on some object that owns a js thread, rather than a // jsthread itself, so CONSTRAINED_METHOD doesn't do the job here. const JSFunctionSpec JSThreadInfo::threadMethods[6] = { MONGO_ATTACH_JS_FUNCTION(init), MONGO_ATTACH_JS_FUNCTION(start), MONGO_ATTACH_JS_FUNCTION(join), MONGO_ATTACH_JS_FUNCTION(hasFailed), MONGO_ATTACH_JS_FUNCTION(returnData), JS_FS_END, }; const JSFunctionSpec JSThreadInfo::freeFunctions[3] = { MONGO_ATTACH_JS_FUNCTION(_threadInject), MONGO_ATTACH_JS_FUNCTION(_scopedThreadInject), JS_FS_END, }; const char* const JSThreadInfo::className = "JSThread"; /** * Holder for JSThreads as exposed by fork() in the shell. * * The idea here is that we create a jsthread by taking a js function and its * parameters and encoding them into a single bson object. Then we spawn a * thread, have that thread do the work and join() it before checking it's * result (serialized through bson). We can check errors at any time by * checking a mutex guarded hasError(). */ class JSThreadConfig { public: JSThreadConfig(JSContext* cx, JS::CallArgs args) : _started(false), _done(false), _sharedData(new SharedData()) { auto scope = getScope(cx); uassert(ErrorCodes::JSInterpreterFailure, "need at least one argument", args.length() > 0); uassert(ErrorCodes::JSInterpreterFailure, "first argument must be a function", args.get(0).isObject() && JS_ObjectIsFunction(cx, args.get(0).toObjectOrNull())); BSONObjBuilder b; for (unsigned i = 0; i < args.length(); ++i) { // 10 decimal digits for a 32 bit unsigned, then 1 for the null char buf[11]; std::sprintf(buf, "%i", i); ValueWriter(cx, args.get(i)).writeThis(&b, buf); } _sharedData->_args = b.obj(); _sharedData->_stack = currentJSStackToString(cx); if (!scope->getParentStack().empty()) { _sharedData->_stack = _sharedData->_stack + scope->getParentStack(); } } void start() { uassert(ErrorCodes::JSInterpreterFailure, "Thread already started", !_started); _thread = stdx::thread(JSThread(*this)); _started = true; } void join() { uassert(ErrorCodes::JSInterpreterFailure, "Thread not running", _started && !_done); _thread.join(); _done = true; } /** * Returns true if the JSThread terminated as a result of an error * during its execution, and false otherwise. This operation does * not block, nor does it require join() to have been called. */ bool hasFailed() const { uassert(ErrorCodes::JSInterpreterFailure, "Thread not started", _started); return _sharedData->getErrored(); } BSONObj returnData() { if (!_done) join(); return _sharedData->_returnData; } private: /** * SharedData between the calling thread and the callee * * JSThreadConfig doesn't always outlive its JSThread (for example, if the parent thread * garbage collects the JSThreadConfig before the JSThread has finished running), so any * data shared between them has to go in a shared_ptr. */ class SharedData { public: SharedData() : _errored(false) {} void setErrored(bool value) { stdx::lock_guard<stdx::mutex> lck(_erroredMutex); _errored = value; } bool getErrored() { stdx::lock_guard<stdx::mutex> lck(_erroredMutex); return _errored; } /** * These three members aren't protected in any way, so you have to be * mindful about how they're used. I.e. _args/_stack need to be set * before start() and _returnData can't be touched until after join(). */ BSONObj _args; BSONObj _returnData; std::string _stack; private: stdx::mutex _erroredMutex; bool _errored; }; /** * The callable object used by stdx::thread */ class JSThread { public: JSThread(JSThreadConfig& config) : _sharedData(config._sharedData) {} void operator()() { try { MozJSImplScope scope(static_cast<MozJSScriptEngine*>(globalScriptEngine)); scope.setParentStack(_sharedData->_stack); _sharedData->_returnData = scope.callThreadArgs(_sharedData->_args); } catch (...) { auto status = exceptionToStatus(); log() << "js thread raised js exception: " << status.reason() << _sharedData->_stack; _sharedData->setErrored(true); _sharedData->_returnData = BSON("ret" << BSONUndefined); } } private: std::shared_ptr<SharedData> _sharedData; }; bool _started; bool _done; stdx::thread _thread; std::shared_ptr<SharedData> _sharedData; }; namespace { JSThreadConfig* getConfig(JSContext* cx, JS::CallArgs args) { JS::RootedValue value(cx); ObjectWrapper(cx, args.thisv()).getValue("_JSThreadConfig", &value); if (!value.isObject()) uasserted(ErrorCodes::BadValue, "_JSThreadConfig not an object"); if (!getScope(cx)->getProto<JSThreadInfo>().instanceOf(value)) uasserted(ErrorCodes::BadValue, "_JSThreadConfig is not a JSThread"); return static_cast<JSThreadConfig*>(JS_GetPrivate(value.toObjectOrNull())); } } // namespace void JSThreadInfo::finalize(JSFreeOp* fop, JSObject* obj) { auto config = static_cast<JSThreadConfig*>(JS_GetPrivate(obj)); if (!config) return; delete config; } void JSThreadInfo::Functions::init::call(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); JS::RootedObject obj(cx); scope->getProto<JSThreadInfo>().newObject(&obj); JSThreadConfig* config = new JSThreadConfig(cx, args); JS_SetPrivate(obj, config); ObjectWrapper(cx, args.thisv()).setObject("_JSThreadConfig", obj); args.rval().setUndefined(); } void JSThreadInfo::Functions::start::call(JSContext* cx, JS::CallArgs args) { getConfig(cx, args)->start(); args.rval().setUndefined(); } void JSThreadInfo::Functions::join::call(JSContext* cx, JS::CallArgs args) { getConfig(cx, args)->join(); args.rval().setUndefined(); } void JSThreadInfo::Functions::hasFailed::call(JSContext* cx, JS::CallArgs args) { args.rval().setBoolean(getConfig(cx, args)->hasFailed()); } void JSThreadInfo::Functions::returnData::call(JSContext* cx, JS::CallArgs args) { ValueReader(cx, args.rval()) .fromBSONElement(getConfig(cx, args)->returnData().firstElement(), true); } void JSThreadInfo::Functions::_threadInject::call(JSContext* cx, JS::CallArgs args) { uassert(ErrorCodes::JSInterpreterFailure, "threadInject takes exactly 1 argument", args.length() == 1); uassert(ErrorCodes::JSInterpreterFailure, "threadInject needs to be passed a prototype", args.get(0).isObject()); JS::RootedObject o(cx, args.get(0).toObjectOrNull()); if (!JS_DefineFunctions(cx, o, JSThreadInfo::threadMethods)) throwCurrentJSException(cx, ErrorCodes::JSInterpreterFailure, "Failed to define functions"); args.rval().setUndefined(); } void JSThreadInfo::Functions::_scopedThreadInject::call(JSContext* cx, JS::CallArgs args) { _threadInject::call(cx, args); } } // namespace mozjs
namespace mozjs { const char* const BSONInfo::className = "BSON"; const JSFunctionSpec BSONInfo::freeFunctions[2] = { MONGO_ATTACH_JS_FUNCTION(bsonWoCompare), JS_FS_END, }; namespace { /** * Holder for bson objects which tracks state for the js wrapper * * Basically, we have read only and read/write variants, and a need to manage * the appearance of mutable state on the read/write versions. */ struct BSONHolder { BSONHolder(const BSONObj& obj, bool ro) : _obj(obj.getOwned()), _resolved(false), _readOnly(ro), _altered(false) {} BSONObj _obj; bool _resolved; bool _readOnly; bool _altered; std::set<std::string> _removed; }; BSONHolder* getHolder(JSObject* obj) { return static_cast<BSONHolder*>(JS_GetPrivate(obj)); } } // namespace void BSONInfo::make(JSContext* cx, JS::MutableHandleObject obj, BSONObj bson, bool ro) { auto scope = getScope(cx); scope->getBsonProto().newInstance(obj); JS_SetPrivate(obj, new BSONHolder(bson, ro)); } void BSONInfo::finalize(JSFreeOp* fop, JSObject* obj) { auto holder = getHolder(obj); if (!holder) return; delete holder; } void BSONInfo::enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties) { auto holder = getHolder(obj); if (!holder) return; BSONObjIterator i(holder->_obj); ObjectWrapper o(cx, obj); JS::RootedValue val(cx); JS::RootedId id(cx); while (i.more()) { BSONElement e = i.next(); // TODO: when we get heterogenous set lookup, switch to StringData // rather than involving the temporary string if (holder->_removed.count(e.fieldName())) continue; ValueReader(cx, &val).fromStringData(e.fieldNameStringData()); if (!JS_ValueToId(cx, val, &id)) uasserted(ErrorCodes::JSInterpreterFailure, "Failed to invoke JS_ValueToId"); properties.append(id); } } void BSONInfo::setProperty( JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict, JS::MutableHandleValue vp) { auto holder = getHolder(obj); if (holder) { if (holder->_readOnly) { uasserted(ErrorCodes::BadValue, "Read only object"); } auto iter = holder->_removed.find(IdWrapper(cx, id).toString()); if (iter != holder->_removed.end()) { holder->_removed.erase(iter); } holder->_altered = true; } ObjectWrapper(cx, obj).defineProperty(id, vp, JSPROP_ENUMERATE); } void BSONInfo::delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded) { auto holder = getHolder(obj); if (holder) { if (holder->_readOnly) { uasserted(ErrorCodes::BadValue, "Read only object"); } holder->_altered = true; holder->_removed.insert(IdWrapper(cx, id).toString()); } *succeeded = true; } void BSONInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) { auto holder = getHolder(obj); *resolvedp = false; if (!holder) { return; } IdWrapper idw(cx, id); if (!holder->_readOnly && holder->_removed.count(idw.toString())) { return; } ObjectWrapper o(cx, obj); std::string sname = IdWrapper(cx, id).toString(); if (holder->_obj.hasField(sname)) { auto elem = holder->_obj[sname]; JS::RootedValue vp(cx); ValueReader(cx, &vp).fromBSONElement(elem, holder->_readOnly); o.defineProperty(id, vp, JSPROP_ENUMERATE); if (!holder->_readOnly && (elem.type() == mongo::Object || elem.type() == mongo::Array)) { // if accessing a subobject, we have no way to know if // modifications are being made on writable objects holder->_altered = true; } *resolvedp = true; } } void BSONInfo::construct(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); scope->getBsonProto().newObject(args.rval()); } std::tuple<BSONObj*, bool> BSONInfo::originalBSON(JSContext* cx, JS::HandleObject obj) { std::tuple<BSONObj*, bool> out(nullptr, false); if (auto holder = getHolder(obj)) out = std::make_tuple(&holder->_obj, holder->_altered); return out; } void BSONInfo::Functions::bsonWoCompare(JSContext* cx, JS::CallArgs args) { if (args.length() != 2) uasserted(ErrorCodes::BadValue, "bsonWoCompare needs 2 argument"); if (!args.get(0).isObject()) uasserted(ErrorCodes::BadValue, "first argument to bsonWoCompare must be an object"); if (!args.get(1).isObject()) uasserted(ErrorCodes::BadValue, "second argument to bsonWoCompare must be an object"); BSONObj firstObject = ValueWriter(cx, args.get(0)).toBSON(); BSONObj secondObject = ValueWriter(cx, args.get(1)).toBSON(); args.rval().setInt32(firstObject.woCompare(secondObject)); } void BSONInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) { JS::RootedValue value(cx); value.setBoolean(true); ObjectWrapper(cx, proto).defineProperty("_bson", value, 0); } } // namespace mozjs