summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Carey <jcarey@argv.me>2017-12-14 14:34:55 -0500
committerJason Carey <jcarey@argv.me>2018-01-19 15:49:17 -0500
commit1c629fb3e0cfdf218a6cdb20882806e3b7dd9e9c (patch)
tree1c0d97ced5b4384754c82b8dc59a360431686a01
parentd055ce30c42cec1171928f49c1739c0b14e0b455 (diff)
downloadmongo-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.cpp20
-rw-r--r--src/mongo/scripting/SConscript1
-rw-r--r--src/mongo/scripting/mozjs/exception.cpp64
-rw-r--r--src/mongo/scripting/mozjs/exception.h12
-rw-r--r--src/mongo/scripting/mozjs/implscope.cpp18
-rw-r--r--src/mongo/scripting/mozjs/implscope.h13
-rw-r--r--src/mongo/scripting/mozjs/internedstring.defs21
-rw-r--r--src/mongo/scripting/mozjs/objectwrapper.cpp24
-rw-r--r--src/mongo/scripting/mozjs/objectwrapper.h14
-rw-r--r--src/mongo/scripting/mozjs/status.cpp116
-rw-r--r--src/mongo/scripting/mozjs/status.h68
-rw-r--r--src/mongo/scripting/mozjs/wraptype.h15
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