diff options
author | Jason Carey <jcarey@argv.me> | 2017-12-14 14:34:55 -0500 |
---|---|---|
committer | Jason Carey <jcarey@argv.me> | 2018-01-19 15:49:17 -0500 |
commit | 1c629fb3e0cfdf218a6cdb20882806e3b7dd9e9c (patch) | |
tree | 1c0d97ced5b4384754c82b8dc59a360431686a01 | |
parent | d055ce30c42cec1171928f49c1739c0b14e0b455 (diff) | |
download | mongo-1c629fb3e0cfdf218a6cdb20882806e3b7dd9e9c.tar.gz |
SERVER-32239 Lossless status throwing in JS
Enable throwing of status without conversion to error reports in
spidermonkey. This will enable throwing of sidecars and other rich data
through js land.
-rw-r--r-- | src/mongo/dbtests/jstests.cpp | 20 | ||||
-rw-r--r-- | src/mongo/scripting/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/exception.cpp | 64 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/exception.h | 12 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.cpp | 18 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.h | 13 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/internedstring.defs | 21 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/objectwrapper.cpp | 24 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/objectwrapper.h | 14 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/status.cpp | 116 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/status.h | 68 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/wraptype.h | 15 |
12 files changed, 335 insertions, 51 deletions
diff --git a/src/mongo/dbtests/jstests.cpp b/src/mongo/dbtests/jstests.cpp index 6b1d2d27dd1..192076f08e1 100644 --- a/src/mongo/dbtests/jstests.cpp +++ b/src/mongo/dbtests/jstests.cpp @@ -2368,6 +2368,25 @@ public: } }; +class ErrorWithSidecarFromInvoke { +public: + void run() { + auto sidecarThrowingFunc = [](const BSONObj& args, void* data) -> BSONObj { + uassertStatusOK(Status(ErrorExtraInfoExample(123), "foo")); + return {}; + }; + + unique_ptr<Scope> s(getGlobalScriptEngine()->newScope()); + + s->injectNative("foo", sidecarThrowingFunc); + + ASSERT_THROWS_WITH_CHECK( + s->invoke("try { foo(); } catch (e) { throw e; } throw new Error(\"bar\");", 0, 0), + ExceptionFor<ErrorCodes::ForTestingErrorExtraInfo>, + [](const auto& ex) { ASSERT_EQ(ex->data, 123); }); + } +}; + class RequiresOwnedObjects { public: void run() { @@ -2467,6 +2486,7 @@ public: add<RecursiveInvoke>(); add<ErrorCodeFromInvoke>(); + add<ErrorWithSidecarFromInvoke>(); add<RequiresOwnedObjects>(); add<RoundTripTests::DBRefTest>(); diff --git a/src/mongo/scripting/SConscript b/src/mongo/scripting/SConscript index 1a91ce45af7..33ab0e8b161 100644 --- a/src/mongo/scripting/SConscript +++ b/src/mongo/scripting/SConscript @@ -140,6 +140,7 @@ if usemozjs: 'mozjs/proxyscope.cpp', 'mozjs/regexp.cpp', 'mozjs/session.cpp', + 'mozjs/status.cpp', 'mozjs/timestamp.cpp', 'mozjs/uri.cpp', 'mozjs/valuereader.cpp', diff --git a/src/mongo/scripting/mozjs/exception.cpp b/src/mongo/scripting/mozjs/exception.cpp index 410fce55b3d..d32ea8eb55a 100644 --- a/src/mongo/scripting/mozjs/exception.cpp +++ b/src/mongo/scripting/mozjs/exception.cpp @@ -64,18 +64,10 @@ MONGO_STATIC_ASSERT_MSG( void mongoToJSException(JSContext* cx) { auto status = exceptionToStatus(); - auto callback = - status.code() == ErrorCodes::JSUncatchableError ? uncatchableErrorCallback : errorCallback; + JS::RootedValue val(cx); + statusToJSException(cx, status, &val); - JS_ReportErrorNumber( - cx, callback, nullptr, JSErr_Limit + status.code(), status.reason().c_str()); -} - -void setJSException(JSContext* cx, ErrorCodes::Error code, StringData sd) { - auto callback = - code == ErrorCodes::JSUncatchableError ? uncatchableErrorCallback : errorCallback; - - JS_ReportErrorNumber(cx, callback, nullptr, JSErr_Limit + code, sd.rawData()); + JS_SetPendingException(cx, val); } std::string currentJSStackToString(JSContext* cx) { @@ -92,16 +84,7 @@ Status currentJSExceptionToStatus(JSContext* cx, ErrorCodes::Error altCode, Stri if (!JS_GetPendingException(cx, &vp)) return Status(altCode, altReason.rawData()); - if (!vp.isObject()) { - return Status(altCode, ValueWriter(cx, vp).toString()); - } - - JS::RootedObject obj(cx, vp.toObjectOrNull()); - JSErrorReport* report = JS_ErrorFromException(cx, obj); - if (!report) - return Status(altCode, altReason.rawData()); - - return JSErrorReportToStatus(cx, report, altCode, altReason); + return jsExceptionToStatus(cx, vp, altCode, altReason); } Status JSErrorReportToStatus(JSContext* cx, @@ -119,11 +102,7 @@ Status JSErrorReportToStatus(JSContext* cx, error = ErrorCodes::JSInterpreterFailure; } else { error = ErrorCodes::Error(report->errorNumber - JSErr_Limit); - if (ErrorCodes::shouldHaveExtraInfo(error)) { - // For now we can't propagate extra info through js exceptions. - // TODO SERVER-32239 delete this block. - error = ErrorCodes::UnknownError; - } + invariant(!ErrorCodes::shouldHaveExtraInfo(error)); } } @@ -135,5 +114,38 @@ void throwCurrentJSException(JSContext* cx, ErrorCodes::Error altCode, StringDat MONGO_UNREACHABLE; } +/** + * Turns a status into a js exception + */ +void statusToJSException(JSContext* cx, Status status, JS::MutableHandleValue out) { + MongoStatusInfo::fromStatus(cx, std::move(status), out); +} + +/** + * Turns a js exception into a status + */ +Status jsExceptionToStatus(JSContext* cx, + JS::HandleValue excn, + ErrorCodes::Error altCode, + StringData altReason) { + auto scope = getScope(cx); + + if (!excn.isObject()) { + return Status(altCode, ValueWriter(cx, excn).toString()); + } + + if (scope->getProto<MongoStatusInfo>().instanceOf(excn)) { + return MongoStatusInfo::toStatus(cx, excn); + } + + JS::RootedObject obj(cx, excn.toObjectOrNull()); + + JSErrorReport* report = JS_ErrorFromException(cx, obj); + if (!report) + return Status(altCode, altReason.rawData()); + + return JSErrorReportToStatus(cx, report, altCode, altReason); +} + } // namespace mozjs } // namespace mongo diff --git a/src/mongo/scripting/mozjs/exception.h b/src/mongo/scripting/mozjs/exception.h index 820e389f0e0..d6a628b6dac 100644 --- a/src/mongo/scripting/mozjs/exception.h +++ b/src/mongo/scripting/mozjs/exception.h @@ -43,9 +43,17 @@ namespace mozjs { void mongoToJSException(JSContext* cx); /** - * Sets an exception for javascript + * Turns a status into a js exception */ -void setJSException(JSContext* cx, ErrorCodes::Error code, StringData sd); +void statusToJSException(JSContext* cx, Status status, JS::MutableHandleValue out); + +/** + * Turns a status into a js exception + */ +Status jsExceptionToStatus(JSContext* cx, + JS::HandleValue excn, + ErrorCodes::Error altCode, + StringData altReason); /** * Converts the current pending js expection into a status diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp index b21fd817fad..2c120610008 100644 --- a/src/mongo/scripting/mozjs/implscope.cpp +++ b/src/mongo/scripting/mozjs/implscope.cpp @@ -126,6 +126,7 @@ void MozJSImplScope::_reportError(JSContext* cx, const char* message, JSErrorRep try { str::stream ss; + ss << message; // TODO: something far more elaborate that mimics the stack printing from v8 @@ -133,7 +134,9 @@ void MozJSImplScope::_reportError(JSContext* cx, const char* message, JSErrorRep if (JS_GetPendingException(cx, &excn) && excn.isObject()) { JS::RootedValue stack(cx); - ObjectWrapper(cx, excn).getValue("stack", &stack); + JS::RootedObject obj(cx, excn.toObjectOrNull()); + ObjectWrapper o(cx, obj); + o.getValue("stack", &stack); auto str = ValueWriter(cx, stack).toString(); @@ -143,6 +146,12 @@ void MozJSImplScope::_reportError(JSContext* cx, const char* message, JSErrorRep } else { ss << " :\n" << str; } + + scope->_status = + jsExceptionToStatus(cx, excn, ErrorCodes::JSInterpreterFailure, message) + .withReason(ss); + + return; } exceptionMsg = ss; @@ -151,7 +160,6 @@ void MozJSImplScope::_reportError(JSContext* cx, const char* message, JSErrorRep log() << exceptionMsg << ":" << dbe.toString() << ":" << message; } - // TODO SERVER-32239 is this right? scope->_status = JSErrorReportToStatus(cx, report, ErrorCodes::JSInterpreterFailure, message) .withReason(exceptionMsg); @@ -437,6 +445,7 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine) _oidProto(_context), _regExpProto(_context), _sessionProto(_context), + _statusProto(_context), _timestampProto(_context), _uriProto(_context) { kCurrentScope = this; @@ -850,23 +859,24 @@ void MozJSImplScope::reset() { } void MozJSImplScope::installBSONTypes() { + _objectProto.install(_global); + _errorProto.install(_global); _binDataProto.install(_global); _bsonProto.install(_global); _codeProto.install(_global); _dbPointerProto.install(_global); _dbRefProto.install(_global); - _errorProto.install(_global); _maxKeyProto.install(_global); _minKeyProto.install(_global); _nativeFunctionProto.install(_global); _numberIntProto.install(_global); _numberLongProto.install(_global); _numberDecimalProto.install(_global); - _objectProto.install(_global); _oidProto.install(_global); _regExpProto.install(_global); _timestampProto.install(_global); _uriProto.install(_global); + _statusProto.install(_global); // This builtin map is a javascript 6 thing. We want our version. so // take theirs out diff --git a/src/mongo/scripting/mozjs/implscope.h b/src/mongo/scripting/mozjs/implscope.h index 8ae4c6ab3d5..cba83cba184 100644 --- a/src/mongo/scripting/mozjs/implscope.h +++ b/src/mongo/scripting/mozjs/implscope.h @@ -60,6 +60,7 @@ #include "mongo/scripting/mozjs/oid.h" #include "mongo/scripting/mozjs/regexp.h" #include "mongo/scripting/mozjs/session.h" +#include "mongo/scripting/mozjs/status.h" #include "mongo/scripting/mozjs/timestamp.h" #include "mongo/scripting/mozjs/uri.h" #include "mongo/stdx/unordered_set.h" @@ -294,6 +295,12 @@ public: } template <typename T> + typename std::enable_if<std::is_same<T, MongoStatusInfo>::value, WrapType<T>&>::type + getProto() { + return _statusProto; + } + + template <typename T> typename std::enable_if<std::is_same<T, TimestampInfo>::value, WrapType<T>&>::type getProto() { return _timestampProto; } @@ -303,6 +310,11 @@ public: return _uriProto; } + template <typename T> + typename std::enable_if<std::is_same<T, GlobalInfo>::value, WrapType<T>&>::type getProto() { + return _globalProto; + } + static const char* const kExecResult; static const char* const kInvokeResult; @@ -444,6 +456,7 @@ private: WrapType<OIDInfo> _oidProto; WrapType<RegExpInfo> _regExpProto; WrapType<SessionInfo> _sessionProto; + WrapType<MongoStatusInfo> _statusProto; WrapType<TimestampInfo> _timestampProto; WrapType<URIInfo> _uriProto; }; diff --git a/src/mongo/scripting/mozjs/internedstring.defs b/src/mongo/scripting/mozjs/internedstring.defs index 2dbcb1bb44a..3735c576b97 100644 --- a/src/mongo/scripting/mozjs/internedstring.defs +++ b/src/mongo/scripting/mozjs/internedstring.defs @@ -10,14 +10,17 @@ MONGO_MOZJS_INTERNED_STRING(bottom, "bottom") MONGO_MOZJS_INTERNED_STRING(_bson, "_bson") MONGO_MOZJS_INTERNED_STRING(code, "code") MONGO_MOZJS_INTERNED_STRING(_collection, "_collection") +MONGO_MOZJS_INTERNED_STRING(columnNumber, "columnNumber") MONGO_MOZJS_INTERNED_STRING(constructor, "constructor") MONGO_MOZJS_INTERNED_STRING(_cursor, "_cursor") +MONGO_MOZJS_INTERNED_STRING(database, "database") MONGO_MOZJS_INTERNED_STRING(_db, "_db") MONGO_MOZJS_INTERNED_STRING(defaultDB, "defaultDB") MONGO_MOZJS_INTERNED_STRING(dollar_db, "$db") MONGO_MOZJS_INTERNED_STRING(dollar_id, "$id") MONGO_MOZJS_INTERNED_STRING(dollar_ref, "$ref") MONGO_MOZJS_INTERNED_STRING(_fields, "_fields") +MONGO_MOZJS_INTERNED_STRING(fileName, "fileName") MONGO_MOZJS_INTERNED_STRING(flags, "flags") MONGO_MOZJS_INTERNED_STRING(floatApprox, "floatApprox") MONGO_MOZJS_INTERNED_STRING(_fullName, "_fullName") @@ -26,9 +29,11 @@ MONGO_MOZJS_INTERNED_STRING(host, "host") MONGO_MOZJS_INTERNED_STRING(_id, "_id") MONGO_MOZJS_INTERNED_STRING(id, "id") MONGO_MOZJS_INTERNED_STRING(i, "i") +MONGO_MOZJS_INTERNED_STRING(isValid, "isValid") MONGO_MOZJS_INTERNED_STRING(_JSThreadConfig, "_JSThreadConfig") MONGO_MOZJS_INTERNED_STRING(len, "len") MONGO_MOZJS_INTERNED_STRING(_limit, "_limit") +MONGO_MOZJS_INTERNED_STRING(lineNumber, "lineNumber") MONGO_MOZJS_INTERNED_STRING(MaxKey, "MaxKey") MONGO_MOZJS_INTERNED_STRING(MinKey, "MinKey") MONGO_MOZJS_INTERNED_STRING(_mongo, "_mongo") @@ -38,30 +43,26 @@ MONGO_MOZJS_INTERNED_STRING(_ns, "_ns") MONGO_MOZJS_INTERNED_STRING(ns, "ns") MONGO_MOZJS_INTERNED_STRING(_numReturned, "_numReturned") MONGO_MOZJS_INTERNED_STRING(_options, "_options") +MONGO_MOZJS_INTERNED_STRING(options, "options") +MONGO_MOZJS_INTERNED_STRING(password, "password") MONGO_MOZJS_INTERNED_STRING(prototype, "prototype") MONGO_MOZJS_INTERNED_STRING(_query, "_query") MONGO_MOZJS_INTERNED_STRING(readOnly, "readOnly") +MONGO_MOZJS_INTERNED_STRING(reason, "reason") MONGO_MOZJS_INTERNED_STRING(_ro, "_ro") MONGO_MOZJS_INTERNED_STRING(scope, "scope") +MONGO_MOZJS_INTERNED_STRING(servers, "servers") +MONGO_MOZJS_INTERNED_STRING(setName, "setName") MONGO_MOZJS_INTERNED_STRING(_shortName, "_shortName") MONGO_MOZJS_INTERNED_STRING(singleton, "singleton") MONGO_MOZJS_INTERNED_STRING(_skip, "_skip") MONGO_MOZJS_INTERNED_STRING(slaveOk, "slaveOk") MONGO_MOZJS_INTERNED_STRING(source, "source") MONGO_MOZJS_INTERNED_STRING(_special, "_special") +MONGO_MOZJS_INTERNED_STRING(stack, "stack") MONGO_MOZJS_INTERNED_STRING(str, "str") MONGO_MOZJS_INTERNED_STRING(top, "top") MONGO_MOZJS_INTERNED_STRING(t, "t") MONGO_MOZJS_INTERNED_STRING(type, "type") MONGO_MOZJS_INTERNED_STRING(uri, "uri") MONGO_MOZJS_INTERNED_STRING(user, "user") -MONGO_MOZJS_INTERNED_STRING(password, "password") -MONGO_MOZJS_INTERNED_STRING(options, "options") -MONGO_MOZJS_INTERNED_STRING(database, "database") -MONGO_MOZJS_INTERNED_STRING(isValid, "isValid") -MONGO_MOZJS_INTERNED_STRING(setName, "setName") -MONGO_MOZJS_INTERNED_STRING(servers, "servers") -MONGO_MOZJS_INTERNED_STRING(stack, "stack") -MONGO_MOZJS_INTERNED_STRING(fileName, "fileName") -MONGO_MOZJS_INTERNED_STRING(lineNumber, "lineNumber") -MONGO_MOZJS_INTERNED_STRING(columnNumber, "columnNumber") diff --git a/src/mongo/scripting/mozjs/objectwrapper.cpp b/src/mongo/scripting/mozjs/objectwrapper.cpp index 98f8094dfcc..0480cc65561 100644 --- a/src/mongo/scripting/mozjs/objectwrapper.cpp +++ b/src/mongo/scripting/mozjs/objectwrapper.cpp @@ -107,27 +107,29 @@ void ObjectWrapper::Key::set(JSContext* cx, JS::HandleObject o, JS::HandleValue void ObjectWrapper::Key::define(JSContext* cx, JS::HandleObject o, JS::HandleValue value, - unsigned attrs) { + unsigned attrs, + JSNative getter, + JSNative setter) { switch (_type) { case Type::Field: - if (JS_DefineProperty(cx, o, _field, value, attrs)) + if (JS_DefineProperty(cx, o, _field, value, attrs, getter, setter)) return; break; case Type::Index: - if (JS_DefineElement(cx, o, _idx, value, attrs)) + if (JS_DefineElement(cx, o, _idx, value, attrs, getter, setter)) return; break; case Type::Id: { JS::RootedId id(cx, _id); - if (JS_DefinePropertyById(cx, o, id, value, attrs)) + if (JS_DefinePropertyById(cx, o, id, value, attrs, getter, setter)) return; break; } case Type::InternedString: { InternedStringId id(cx, _internedString); - if (JS_DefinePropertyById(cx, o, id, value, attrs)) + if (JS_DefinePropertyById(cx, o, id, value, attrs, getter, setter)) return; break; } @@ -379,8 +381,16 @@ void ObjectWrapper::setObject(Key key, JS::HandleObject object) { setValue(key, value); } -void ObjectWrapper::defineProperty(Key key, JS::HandleValue val, unsigned attrs) { - key.define(_context, _object, val, attrs); +void ObjectWrapper::setPrototype(JS::HandleObject object) { + if (JS_SetPrototype(_context, _object, object)) + return; + + throwCurrentJSException(_context, ErrorCodes::InternalError, "Failed to set prototype"); +} + +void ObjectWrapper::defineProperty( + Key key, JS::HandleValue val, unsigned attrs, JSNative getter, JSNative setter) { + key.define(_context, _object, val, attrs, getter, setter); } void ObjectWrapper::deleteProperty(Key key) { diff --git a/src/mongo/scripting/mozjs/objectwrapper.h b/src/mongo/scripting/mozjs/objectwrapper.h index cf6c61d39e1..a4ab120b1ad 100644 --- a/src/mongo/scripting/mozjs/objectwrapper.h +++ b/src/mongo/scripting/mozjs/objectwrapper.h @@ -84,7 +84,12 @@ public: void set(JSContext* cx, JS::HandleObject o, JS::HandleValue value); bool has(JSContext* cx, JS::HandleObject o); bool hasOwn(JSContext* cx, JS::HandleObject o); - void define(JSContext* cx, JS::HandleObject o, JS::HandleValue value, unsigned attrs); + void define(JSContext* cx, + JS::HandleObject o, + JS::HandleValue value, + unsigned attrs, + JSNative getter, + JSNative setter); void del(JSContext* cx, JS::HandleObject o); std::string toString(JSContext* cx); StringData toStringData(JSContext* cx, JSStringWrapper* jsstr); @@ -118,11 +123,16 @@ public: void setBSONArray(Key key, const BSONObj& obj, bool readOnly); void setValue(Key key, JS::HandleValue value); void setObject(Key key, JS::HandleObject value); + void setPrototype(JS::HandleObject value); /** * See JS_DefineProperty for what sort of attributes might be useful */ - void defineProperty(Key key, JS::HandleValue value, unsigned attrs); + void defineProperty(Key key, + JS::HandleValue value, + unsigned attrs, + JSNative getter = nullptr, + JSNative setter = nullptr); void deleteProperty(Key key); diff --git a/src/mongo/scripting/mozjs/status.cpp b/src/mongo/scripting/mozjs/status.cpp new file mode 100644 index 00000000000..cb4ecb1bbd3 --- /dev/null +++ b/src/mongo/scripting/mozjs/status.cpp @@ -0,0 +1,116 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/status.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/internedstring.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/wrapconstrainedmethod.h" + +namespace mongo { +namespace mozjs { + +const char* const MongoStatusInfo::className = "MongoStatus"; +const char* const MongoStatusInfo::inheritFrom = "Error"; + +Status MongoStatusInfo::toStatus(JSContext* cx, JS::HandleObject object) { + return *static_cast<Status*>(JS_GetPrivate(object)); +} + +Status MongoStatusInfo::toStatus(JSContext* cx, JS::HandleValue value) { + return *static_cast<Status*>(JS_GetPrivate(value.toObjectOrNull())); +} + +void MongoStatusInfo::fromStatus(JSContext* cx, Status status, JS::MutableHandleValue value) { + auto scope = getScope(cx); + + JS::RootedValue undef(cx); + undef.setUndefined(); + + JS::AutoValueArray<1> args(cx); + ValueReader(cx, args[0]).fromStringData(status.reason()); + JS::RootedObject error(cx); + scope->getProto<ErrorInfo>().newInstance(args, &error); + + JS::RootedObject thisv(cx); + scope->getProto<MongoStatusInfo>().newObjectWithProto(&thisv, error); + ObjectWrapper thisvObj(cx, thisv); + thisvObj.defineProperty( + InternedString::code, + undef, + JSPROP_ENUMERATE | JSPROP_SHARED, + smUtils::wrapConstrainedMethod<Functions::code, false, MongoStatusInfo>); + + thisvObj.defineProperty( + InternedString::reason, + undef, + JSPROP_ENUMERATE | JSPROP_SHARED, + smUtils::wrapConstrainedMethod<Functions::reason, false, MongoStatusInfo>); + + JS_SetPrivate(thisv, scope->trackedNew<Status>(std::move(status))); + + value.setObjectOrNull(thisv); +} + +void MongoStatusInfo::construct(JSContext* cx, JS::CallArgs args) { + auto code = args.get(0).toInt32(); + auto reason = JSStringWrapper(cx, args.get(1).toString()).toString(); + + JS::RootedValue out(cx); + fromStatus(cx, Status(ErrorCodes::Error(code), std::move(reason)), &out); + + args.rval().set(out); +} + +void MongoStatusInfo::finalize(JSFreeOp* fop, JSObject* obj) { + auto status = static_cast<Status*>(JS_GetPrivate(obj)); + + if (status) + getScope(fop)->trackedDelete(status); +} + +void MongoStatusInfo::Functions::code::call(JSContext* cx, JS::CallArgs args) { + args.rval().setInt32(toStatus(cx, args.thisv()).code()); +} + +void MongoStatusInfo::Functions::reason::call(JSContext* cx, JS::CallArgs args) { + ValueReader(cx, args.rval()).fromStringData(toStatus(cx, args.thisv()).reason()); +} + +void MongoStatusInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) { + auto scope = getScope(cx); + + JS_SetPrivate(proto, scope->trackedNew<Status>(Status::OK())); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/status.h b/src/mongo/scripting/mozjs/status.h new file mode 100644 index 00000000000..d930bdb8df9 --- /dev/null +++ b/src/mongo/scripting/mozjs/status.h @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "MongoStatus" Javascript object. + * + * This type wraps the "Status" type in the server, allowing for lossless throwing of mongodb native + * exceptions through javascript. It can be created (albeit without sidecar) from javascript. + * These are also created automatically when exceptions are thrown from native c++ functions. + * + * They are somewhat special, in that the prototype for each MongoStatus object is actually an Error + * object specific to that status object. This allows Error-like behavior such as useful stack + * traces, and instanceOf Error. + */ +struct MongoStatusInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + static void finalize(JSFreeOp* fop, JSObject* obj); + + struct Functions { + MONGO_DECLARE_JS_FUNCTION(code); + MONGO_DECLARE_JS_FUNCTION(reason); + }; + + static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto); + + static const char* const className; + static const char* const inheritFrom; + static const unsigned classFlags = JSCLASS_HAS_PRIVATE; + + static Status toStatus(JSContext* cx, JS::HandleObject object); + static Status toStatus(JSContext* cx, JS::HandleValue value); + static void fromStatus(JSContext* cx, Status status, JS::MutableHandleValue value); +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/wraptype.h b/src/mongo/scripting/mozjs/wraptype.h index 76af28efd1c..4f85802d4ec 100644 --- a/src/mongo/scripting/mozjs/wraptype.h +++ b/src/mongo/scripting/mozjs/wraptype.h @@ -298,6 +298,17 @@ public: out.setObjectOrNull(obj); } + void newObjectWithProto(JS::MutableHandleObject out, JS::HandleObject proto) { + out.set(_assertPtr(JS_NewObjectWithGivenProto(_context, &_jsclass, proto))); + } + + void newObjectWithProto(JS::MutableHandleValue out, JS::HandleObject proto) { + JS::RootedObject obj(_context); + newObjectWithProto(&obj, proto); + + out.setObjectOrNull(obj); + } + /** * newInstance calls the constructor, a la new Type() in js */ @@ -353,6 +364,10 @@ public: return _proto; } + JS::HandleObject getCtor() const { + return _constructor; + } + private: /** * Use this if you want your types installed visibly in the global scope |