summaryrefslogtreecommitdiff
path: root/src/mongo/scripting/mozjs
diff options
context:
space:
mode:
authorJason Carey <jcarey@argv.me>2015-09-16 18:18:46 -0400
committerJason Carey <jcarey@argv.me>2015-09-21 18:25:39 -0400
commitee4f910322988cb9ba4784472a38a16ce2c0cdc9 (patch)
treed06b26e9ef598b71937f1c6aab5f10488663556d /src/mongo/scripting/mozjs
parenta74ecb2a746e4d8a8ab78610c07d509788c4d8ad (diff)
downloadmongo-ee4f910322988cb9ba4784472a38a16ce2c0cdc9.tar.gz
SERVER-19607 no recursion in JS -> BSON conversion
Replace functional recursion in javascript object to bson conversion with an explicit stack to minimize the memory cost of processing very deep / cyclical objects. This prevents stack overflows on debug and non-optimized builds on some platforms.
Diffstat (limited to 'src/mongo/scripting/mozjs')
-rw-r--r--src/mongo/scripting/mozjs/implscope.cpp10
-rw-r--r--src/mongo/scripting/mozjs/jsthread.cpp7
-rw-r--r--src/mongo/scripting/mozjs/lifetimestack.h134
-rw-r--r--src/mongo/scripting/mozjs/nativefunction.cpp6
-rw-r--r--src/mongo/scripting/mozjs/objectwrapper.cpp131
-rw-r--r--src/mongo/scripting/mozjs/objectwrapper.h71
-rw-r--r--src/mongo/scripting/mozjs/proxyscope.cpp49
-rw-r--r--src/mongo/scripting/mozjs/proxyscope.h6
-rw-r--r--src/mongo/scripting/mozjs/valuereader.cpp10
-rw-r--r--src/mongo/scripting/mozjs/valuereader.h3
-rw-r--r--src/mongo/scripting/mozjs/valuewriter.cpp204
-rw-r--r--src/mongo/scripting/mozjs/valuewriter.h19
-rw-r--r--src/mongo/scripting/mozjs/wraptype.h4
13 files changed, 488 insertions, 166 deletions
diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp
index 2f6cf57e2bd..384dd2ce83e 100644
--- a/src/mongo/scripting/mozjs/implscope.cpp
+++ b/src/mongo/scripting/mozjs/implscope.cpp
@@ -278,6 +278,9 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine)
_checkErrorState(JS_InitStandardClasses(_context, _global));
installBSONTypes();
+
+ JS_FireOnNewGlobalObject(_context, _global);
+
execSetup(JSFiles::assert);
execSetup(JSFiles::types);
@@ -425,10 +428,11 @@ BSONObj MozJSImplScope::callThreadArgs(const BSONObj& args) {
_checkErrorState(JS::Call(_context, thisv, function, argv, &out), false, true);
- BSONObjBuilder b;
- ValueWriter(_context, out).writeThis(&b, "ret");
+ JS::RootedObject rout(_context, JS_NewPlainObject(_context));
+ ObjectWrapper wout(_context, rout);
+ wout.setValue("ret", out);
- return b.obj();
+ return wout.toBSON();
}
bool hasFunctionIdentifier(StringData code) {
diff --git a/src/mongo/scripting/mozjs/jsthread.cpp b/src/mongo/scripting/mozjs/jsthread.cpp
index 3b457851efd..5c8eef51b65 100644
--- a/src/mongo/scripting/mozjs/jsthread.cpp
+++ b/src/mongo/scripting/mozjs/jsthread.cpp
@@ -87,16 +87,17 @@ public:
"first argument must be a function",
args.get(0).isObject() && JS_ObjectIsFunction(cx, args.get(0).toObjectOrNull()));
- BSONObjBuilder b;
+ JS::RootedObject robj(cx, JS_NewPlainObject(cx));
+ ObjectWrapper wobj(cx, robj);
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);
+ wobj.setValue(buf, args.get(i));
}
- _sharedData->_args = b.obj();
+ _sharedData->_args = wobj.toBSON();
_sharedData->_stack = currentJSStackToString(cx);
diff --git a/src/mongo/scripting/mozjs/lifetimestack.h b/src/mongo/scripting/mozjs/lifetimestack.h
new file mode 100644
index 00000000000..0954a525ccb
--- /dev/null
+++ b/src/mongo/scripting/mozjs/lifetimestack.h
@@ -0,0 +1,134 @@
+/* Copyright 2015 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 <cstddef>
+#include <type_traits>
+#include <utility>
+
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Implements a stack with:
+ * o specific LIFO lifetime guarantees
+ * o builtin storage (based on template parameter)
+ *
+ * This is useful for manipulating stacks of types which are non-movable and
+ * non-copyable (and thus cannot be put into standard containers). The lack of
+ * an allocator additionally supports types that must live in particular
+ * region of memory (like the stack vs. the heap).
+ *
+ * We need this to store GC Rooting types from spidermonkey safely.
+ */
+template <typename T, std::size_t N>
+class LifetimeStack {
+public:
+ // Boiler plate typedefs
+ using value_type = T;
+ using size_type = std::size_t;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using pointer = T*;
+ using const_pointer = const T*;
+
+ LifetimeStack() = default;
+
+ ~LifetimeStack() {
+ while (size()) {
+ pop();
+ }
+ }
+
+ /**
+ * Stacks are non-copyable and non-movable
+ */
+ LifetimeStack(const LifetimeStack&) = delete;
+ LifetimeStack& operator=(const LifetimeStack&) = delete;
+
+ LifetimeStack(LifetimeStack&& other) = delete;
+ LifetimeStack& operator=(LifetimeStack&& other) = delete;
+
+ template <typename... Args>
+ void emplace(Args&&... args) {
+ invariant(_size <= N);
+
+ new (_data() + _size) T(std::forward<Args>(args)...);
+
+ _size++;
+ }
+
+ void pop() {
+ invariant(_size > 0);
+
+ (&top())->~T();
+
+ _size--;
+ }
+
+ const_reference top() const {
+ invariant(_size > 0);
+ return _data()[_size - 1];
+ }
+
+ reference top() {
+ invariant(_size > 0);
+ return _data()[_size - 1];
+ }
+
+ size_type size() const {
+ return _size;
+ }
+
+ bool empty() const {
+ return _size == 0;
+ }
+
+ size_type capacity() const {
+ return N;
+ }
+
+private:
+ pointer _data() {
+ return reinterpret_cast<pointer>(&_storage);
+ }
+
+ const_pointer _data() const {
+ return reinterpret_cast<const_pointer>(&_storage);
+ }
+
+private:
+ typename std::aligned_storage<sizeof(T) * N, std::alignment_of<T>::value>::type _storage;
+
+ size_type _size = 0;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/nativefunction.cpp b/src/mongo/scripting/mozjs/nativefunction.cpp
index ef5423f0f21..4d019a5b347 100644
--- a/src/mongo/scripting/mozjs/nativefunction.cpp
+++ b/src/mongo/scripting/mozjs/nativefunction.cpp
@@ -78,6 +78,8 @@ void NativeFunctionInfo::call(JSContext* cx, JS::CallArgs args) {
}
BSONObjBuilder bob;
+ JS::RootedObject robj(cx, JS_NewPlainObject(cx));
+ ObjectWrapper wobj(cx, robj);
for (unsigned i = 0; i < args.length(); i++) {
// 11 is enough here. unsigned's are only 32 bits, and 1 << 32 is only
@@ -85,10 +87,10 @@ void NativeFunctionInfo::call(JSContext* cx, JS::CallArgs args) {
char buf[11];
std::sprintf(buf, "%i", i);
- ValueWriter(cx, args.get(i)).writeThis(&bob, buf);
+ wobj.setValue(buf, args.get(i));
}
- BSONObj out = holder->_func(bob.obj(), holder->_ctx);
+ BSONObj out = holder->_func(wobj.toBSON(), holder->_ctx);
ValueReader(cx, args.rval()).fromBSONElement(out.firstElement(), false);
}
diff --git a/src/mongo/scripting/mozjs/objectwrapper.cpp b/src/mongo/scripting/mozjs/objectwrapper.cpp
index 2dc17726b37..2213ec2a029 100644
--- a/src/mongo/scripting/mozjs/objectwrapper.cpp
+++ b/src/mongo/scripting/mozjs/objectwrapper.cpp
@@ -41,6 +41,10 @@
namespace mongo {
namespace mozjs {
+#ifndef _MSC_EXTENSIONS
+const int ObjectWrapper::kMaxWriteFieldDepth;
+#endif // _MSC_EXTENSIONS
+
void ObjectWrapper::Key::get(JSContext* cx, JS::HandleObject o, JS::MutableHandleValue value) {
switch (_type) {
case Type::Field:
@@ -173,11 +177,11 @@ std::string ObjectWrapper::Key::toString(JSContext* cx) {
cx, ErrorCodes::InternalError, "Failed to toString a ObjectWrapper::Key");
}
-ObjectWrapper::ObjectWrapper(JSContext* cx, JS::HandleObject obj, int depth)
- : _context(cx), _object(cx, obj), _depth(depth) {}
+ObjectWrapper::ObjectWrapper(JSContext* cx, JS::HandleObject obj)
+ : _context(cx), _object(cx, obj) {}
-ObjectWrapper::ObjectWrapper(JSContext* cx, JS::HandleValue value, int depth)
- : _context(cx), _object(cx, value.toObjectOrNull()), _depth(depth) {}
+ObjectWrapper::ObjectWrapper(JSContext* cx, JS::HandleValue value)
+ : _context(cx), _object(cx, value.toObjectOrNull()) {}
double ObjectWrapper::getNumber(Key key) {
JS::RootedValue x(_context);
@@ -225,7 +229,7 @@ BSONObj ObjectWrapper::getObject(Key key) {
JS::RootedValue x(_context);
getValue(key, &x);
- return ValueWriter(_context, x, _depth).toBSON();
+ return ValueWriter(_context, x).toBSON();
}
void ObjectWrapper::getValue(Key key, JS::MutableHandleValue value) {
@@ -255,14 +259,14 @@ void ObjectWrapper::setBoolean(Key key, bool val) {
void ObjectWrapper::setBSONElement(Key key, const BSONElement& elem, bool readOnly) {
JS::RootedValue value(_context);
- ValueReader(_context, &value, _depth).fromBSONElement(elem, readOnly);
+ ValueReader(_context, &value).fromBSONElement(elem, readOnly);
setValue(key, value);
}
void ObjectWrapper::setBSON(Key key, const BSONObj& obj, bool readOnly) {
JS::RootedValue value(_context);
- ValueReader(_context, &value, _depth).fromBSON(obj, readOnly);
+ ValueReader(_context, &value).fromBSON(obj, readOnly);
setValue(key, value);
}
@@ -339,54 +343,121 @@ void ObjectWrapper::callMethod(JS::HandleValue fun, JS::MutableHandleValue out)
callMethod(fun, args, out);
}
-void ObjectWrapper::writeThis(BSONObjBuilder* b) {
- auto scope = getScope(_context);
-
- BSONObj* originalBSON = nullptr;
- if (scope->getProto<BSONInfo>().instanceOf(_object)) {
+BSONObj ObjectWrapper::toBSON() {
+ if (getScope(_context)->getProto<BSONInfo>().instanceOf(_object)) {
+ BSONObj* originalBSON = nullptr;
bool altered;
std::tie(originalBSON, altered) = BSONInfo::originalBSON(_context, _object);
- if (originalBSON && !altered) {
- b->appendElements(*originalBSON);
- return;
- }
+ if (originalBSON && !altered)
+ return *originalBSON;
}
+ JS::RootedId id(_context);
+
+ // INCREDIBLY SUBTLE BEHAVIOR:
+ //
+ // (jcarey): Be very careful about how the Rooting API is used in
+ // relationship to WriteFieldRecursionFrames. Mozilla'a API more or less
+ // demands that the rooting types are on the stack and only manipulated as
+ // regular objects, which we aren't doing here. The reason they do this is
+ // because the rooting types must be global created and destroyed in an
+ // entirely linear order. This is impossible to screw up in regular use,
+ // but our unwinding of the recursion frames makes it easy to do here.
+ //
+ // The roots above need to be before the first frame is emplaced (so
+ // they'll be destroyed after it) and none of the roots in the below code
+ // (or in ValueWriter::writeThis) can live longer than until the call to
+ // emplace() inside ValueWriter. The runtime asserts enabled by MozJS's
+ // debug mode will catch runtime errors, but be aware of how difficult this
+ // is to get right and what to look for if one of them bites you.
+ WriteFieldRecursionFrames frames;
+ frames.emplace(_context, _object, nullptr, StringData{});
+
+ BSONObjBuilder b;
+
// We special case the _id field in top-level objects and move it to the front.
// This matches other drivers behavior and makes finding the _id field quicker in BSON.
- if (_depth == 0 && hasField("_id")) {
- _writeField(b, "_id", originalBSON);
+ if (hasField("_id")) {
+ _writeField(&b, "_id", &frames, frames.top().originalBSON);
}
- enumerate([&](JS::HandleId id) {
- JS::RootedValue x(_context);
+ while (frames.size()) {
+ auto& frame = frames.top();
+
+ // If the index is the same as length, we've seen all the keys at this
+ // level and should go up a level
+ if (frame.idx == frame.ids.length()) {
+ frames.pop();
+ continue;
+ }
- IdWrapper idw(_context, id);
+ if (frame.idx == 0 && frame.originalBSON && !frame.altered) {
+ // If this is our first look at the object and it has an unaltered
+ // bson behind it, move idx to the end so we'll roll up on the next
+ // pass through the loop.
+ frame.subbob_or(&b)->appendElements(*frame.originalBSON);
+ frame.idx = frame.ids.length();
+ continue;
+ }
- if (_depth == 0 && idw.isString() && idw.equals("_id"))
- return;
+ id.set(frame.ids[frame.idx++]);
- _writeField(b, id, originalBSON);
- });
+ if (frames.size() == 1) {
+ IdWrapper idw(_context, id);
- const int sizeWithEOO = b->len() + 1 /*EOO*/ - 4 /*BSONObj::Holder ref count*/;
+ if (idw.isString() && idw.equals("_id")) {
+ continue;
+ }
+ }
+
+ // writeField invokes ValueWriter with the frame stack, which will push
+ // onto frames for subobjects, which will effectively recurse the loop.
+ _writeField(frame.subbob_or(&b), JS::HandleId(id), &frames, frame.originalBSON);
+ }
+
+ const int sizeWithEOO = b.len() + 1 /*EOO*/ - 4 /*BSONObj::Holder ref count*/;
uassert(17260,
str::stream() << "Converting from JavaScript to BSON failed: "
<< "Object size " << sizeWithEOO << " exceeds limit of "
<< BSONObjMaxInternalSize << " bytes.",
sizeWithEOO <= BSONObjMaxInternalSize);
+
+ return b.obj();
+}
+
+ObjectWrapper::WriteFieldRecursionFrame::WriteFieldRecursionFrame(JSContext* cx,
+ JSObject* obj,
+ BSONObjBuilder* parent,
+ StringData sd)
+ : thisv(cx, obj), ids(cx, JS_Enumerate(cx, thisv)) {
+ if (parent) {
+ subbob.emplace(JS_IsArrayObject(cx, thisv) ? parent->subarrayStart(sd)
+ : parent->subobjStart(sd));
+ }
+
+ if (!ids) {
+ throwCurrentJSException(
+ cx, ErrorCodes::JSInterpreterFailure, "Failure to enumerate object");
+ }
+
+ if (getScope(cx)->getProto<BSONInfo>().instanceOf(thisv)) {
+ std::tie(originalBSON, altered) = BSONInfo::originalBSON(cx, thisv);
+ }
}
-void ObjectWrapper::_writeField(BSONObjBuilder* b, Key key, BSONObj* originalParent) {
+void ObjectWrapper::_writeField(BSONObjBuilder* b,
+ Key key,
+ WriteFieldRecursionFrames* frames,
+ BSONObj* originalParent) {
JS::RootedValue value(_context);
- key.get(_context, _object, &value);
+ key.get(_context, frames->top().thisv, &value);
- ValueWriter x(_context, value, _depth);
+ ValueWriter x(_context, value);
x.setOriginalBSON(originalParent);
- x.writeThis(b, key.toString(_context));
+ x.writeThis(b, key.toString(_context), frames);
}
std::string ObjectWrapper::getClassName() {
diff --git a/src/mongo/scripting/mozjs/objectwrapper.h b/src/mongo/scripting/mozjs/objectwrapper.h
index 57bdc728494..3134083f243 100644
--- a/src/mongo/scripting/mozjs/objectwrapper.h
+++ b/src/mongo/scripting/mozjs/objectwrapper.h
@@ -31,8 +31,10 @@
#include <jsapi.h>
#include <string>
+#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/platform/decimal128.h"
#include "mongo/scripting/mozjs/exception.h"
+#include "mongo/scripting/mozjs/lifetimestack.h"
namespace mongo {
@@ -43,6 +45,7 @@ class BSONElement;
namespace mozjs {
class MozJSImplScope;
+class ValueWriter;
/**
* Wraps JSObject's with helpers for accessing their properties
@@ -51,6 +54,8 @@ class MozJSImplScope;
* not movable or copyable
*/
class ObjectWrapper {
+ friend class ValueWriter;
+
public:
/**
* Helper subclass that provides some easy boilerplate for accessing
@@ -86,12 +91,8 @@ public:
Type _type;
};
- /**
- * The depth parameter here allows us to detect overly nested or circular
- * objects and bail without blowing the stack.
- */
- ObjectWrapper(JSContext* cx, JS::HandleObject obj, int depth = 0);
- ObjectWrapper(JSContext* cx, JS::HandleValue value, int depth = 0);
+ ObjectWrapper(JSContext* cx, JS::HandleObject obj);
+ ObjectWrapper(JSContext* cx, JS::HandleValue value);
double getNumber(Key key);
int getNumberInt(Key key);
@@ -152,9 +153,9 @@ public:
}
/**
- * concatenates all of the fields in the object into the associated builder
+ * Writes a bson object reflecting the contents of the object
*/
- void writeThis(BSONObjBuilder* b);
+ BSONObj toBSON();
JS::HandleObject thisv() {
return _object;
@@ -164,21 +165,61 @@ public:
private:
/**
+ * The maximum depth of recursion for writeField
+ */
+ static const int kMaxWriteFieldDepth = 150;
+
+ /**
+ * The state needed to write a single level of a nested javascript object as a
+ * bson object.
+ *
+ * We use this between ObjectWrapper and ValueWriter to avoid recursion in
+ * translating js to bson.
+ */
+ struct WriteFieldRecursionFrame {
+ WriteFieldRecursionFrame(JSContext* cx,
+ JSObject* obj,
+ BSONObjBuilder* parent,
+ StringData sd);
+
+ BSONObjBuilder* subbob_or(BSONObjBuilder* option) {
+ return subbob ? &subbob.get() : option;
+ }
+
+ JS::RootedObject thisv;
+
+ // ids for the keys of thisv
+ JS::AutoIdArray ids;
+
+ // Current index of the current key we're working on
+ std::size_t idx = 0;
+
+ boost::optional<BSONObjBuilder> subbob;
+ BSONObj* originalBSON = nullptr;
+ bool altered;
+ };
+
+ /**
+ * Synthetic stack of variables for writeThis
+ *
+ * We use a LifetimeStack here because we have SpiderMonkey Rooting types which
+ * are non-copyable and non-movable and have to be on the stack.
+ */
+ using WriteFieldRecursionFrames = LifetimeStack<WriteFieldRecursionFrame, kMaxWriteFieldDepth>;
+
+ /**
* writes the field "key" into the associated builder
*
* optional originalBSON is used to track updates to types (NumberInt
* overwritten by a float, but coercible to the original type, etc.)
*/
- void _writeField(BSONObjBuilder* b, Key key, BSONObj* originalBSON);
+ void _writeField(BSONObjBuilder* b,
+ Key key,
+ WriteFieldRecursionFrames* frames,
+ BSONObj* originalBSON);
JSContext* _context;
JS::RootedObject _object;
-
- /**
- * The depth of an object wrapper has to do with how many parents it has.
- * Used to avoid circular object graphs and associate stack smashing.
- */
- int _depth;
};
} // namespace mozjs
diff --git a/src/mongo/scripting/mozjs/proxyscope.cpp b/src/mongo/scripting/mozjs/proxyscope.cpp
index 833e083001a..ee1614d8509 100644
--- a/src/mongo/scripting/mozjs/proxyscope.cpp
+++ b/src/mongo/scripting/mozjs/proxyscope.cpp
@@ -47,7 +47,17 @@ MozJSProxyScope::MozJSProxyScope(MozJSScriptEngine* engine)
_state(State::Idle),
_status(Status::OK()),
_condvar(),
- _thread(&MozJSProxyScope::implThread, this) {
+ // Despite calling PR_CreateThread, we're actually using our own
+ // implementation of PosixNSPR.cpp in this directory. So these threads
+ // are actually hosted on top of stdx::threads and most of the flags
+ // don't matter.
+ _thread(PR_CreateThread(PR_USER_THREAD,
+ implThread,
+ this,
+ PR_PRIORITY_NORMAL,
+ PR_LOCAL_THREAD,
+ PR_JOINABLE_THREAD,
+ 0)) {
// Test the child on startup to make sure it's awake and that the
// implementation scope sucessfully constructed.
try {
@@ -249,7 +259,7 @@ void MozJSProxyScope::runOnImplThread(std::function<void()> f) {
// methods on it from there. If we're on the same thread, it's safe to
// simply call back in, so let's do that.
- if (_thread.get_id() == std::this_thread::get_id()) {
+ if (_thread == PR_GetCurrentThread()) {
return f();
}
@@ -281,7 +291,7 @@ void MozJSProxyScope::shutdownThread() {
_condvar.notify_one();
- _thread.join();
+ PR_JoinThread(_thread);
}
/**
@@ -296,7 +306,9 @@ void MozJSProxyScope::shutdownThread() {
* Shutdown: Shutdown -> _
* break out of the loop and return.
*/
-void MozJSProxyScope::implThread() {
+void MozJSProxyScope::implThread(void* arg) {
+ auto proxy = static_cast<MozJSProxyScope*>(arg);
+
if (hasGlobalServiceContext())
Client::initThread("js");
@@ -305,35 +317,38 @@ void MozJSProxyScope::implThread() {
// This will leave _status set for the first noop runOnImplThread(), which
// captures the startup exception that way
try {
- scope.reset(new MozJSImplScope(_engine));
- _implScope = scope.get();
+ scope.reset(new MozJSImplScope(proxy->_engine));
+ proxy->_implScope = scope.get();
} catch (...) {
- _status = exceptionToStatus();
+ proxy->_status = exceptionToStatus();
}
while (true) {
- stdx::unique_lock<stdx::mutex> lk(_mutex);
- _condvar.wait(
- lk, [this] { return _state == State::ProxyRequest || _state == State::Shutdown; });
-
- if (_state == State::Shutdown)
+ stdx::unique_lock<stdx::mutex> lk(proxy->_mutex);
+ proxy->_condvar.wait(lk,
+ [proxy] {
+ return proxy->_state == State::ProxyRequest ||
+ proxy->_state == State::Shutdown;
+ });
+
+ if (proxy->_state == State::Shutdown)
break;
try {
- _function();
+ proxy->_function();
} catch (...) {
- _status = exceptionToStatus();
+ proxy->_status = exceptionToStatus();
}
int exitCode;
- if (_implScope && _implScope->getQuickExit(&exitCode)) {
+ if (proxy->_implScope && proxy->_implScope->getQuickExit(&exitCode)) {
scope.reset();
quickExit(exitCode);
}
- _state = State::ImplResponse;
+ proxy->_state = State::ImplResponse;
- _condvar.notify_one();
+ proxy->_condvar.notify_one();
}
}
diff --git a/src/mongo/scripting/mozjs/proxyscope.h b/src/mongo/scripting/mozjs/proxyscope.h
index 3d472a5a05e..de932fe35a8 100644
--- a/src/mongo/scripting/mozjs/proxyscope.h
+++ b/src/mongo/scripting/mozjs/proxyscope.h
@@ -28,6 +28,8 @@
#pragma once
+#include "vm/PosixNSPR.h"
+
#include "mongo/client/dbclientcursor.h"
#include "mongo/scripting/mozjs/engine.h"
#include "mongo/stdx/condition_variable.h"
@@ -174,7 +176,7 @@ public:
private:
void runOnImplThread(std::function<void()> f);
void shutdownThread();
- void implThread();
+ static void implThread(void* proxy);
MozJSScriptEngine* const _engine;
MozJSImplScope* _implScope;
@@ -189,7 +191,7 @@ private:
Status _status;
stdx::condition_variable _condvar;
- stdx::thread _thread;
+ PRThread* _thread;
};
} // namespace mozjs
diff --git a/src/mongo/scripting/mozjs/valuereader.cpp b/src/mongo/scripting/mozjs/valuereader.cpp
index 89289c2f976..6a0b9dc6fe1 100644
--- a/src/mongo/scripting/mozjs/valuereader.cpp
+++ b/src/mongo/scripting/mozjs/valuereader.cpp
@@ -45,8 +45,8 @@
namespace mongo {
namespace mozjs {
-ValueReader::ValueReader(JSContext* cx, JS::MutableHandleValue value, int depth)
- : _context(cx), _value(value), _depth(depth) {}
+ValueReader::ValueReader(JSContext* cx, JS::MutableHandleValue value)
+ : _context(cx), _value(value) {}
void ValueReader::fromBSONElement(const BSONElement& elem, bool readOnly) {
auto scope = getScope(_context);
@@ -92,8 +92,8 @@ void ValueReader::fromBSONElement(const BSONElement& elem, bool readOnly) {
sprintf(str, "%i", i++);
JS::RootedValue member(_context);
- ValueReader(_context, &member, _depth + 1).fromBSONElement(subElem, readOnly);
- ObjectWrapper(_context, array, _depth + 1).setValue(str, member);
+ ValueReader(_context, &member).fromBSONElement(subElem, readOnly);
+ ObjectWrapper(_context, array).setValue(str, member);
}
_value.setObjectOrNull(array);
return;
@@ -225,7 +225,7 @@ void ValueReader::fromBSON(const BSONObj& obj, bool readOnly) {
ValueReader(_context, args[0]).fromBSONElement(ref, readOnly);
// id can be a subobject
- ValueReader(_context, args[1], _depth + 1).fromBSONElement(id, readOnly);
+ ValueReader(_context, args[1]).fromBSONElement(id, readOnly);
JS::RootedObject obj(_context);
diff --git a/src/mongo/scripting/mozjs/valuereader.h b/src/mongo/scripting/mozjs/valuereader.h
index e96ede814f1..223600aa5e4 100644
--- a/src/mongo/scripting/mozjs/valuereader.h
+++ b/src/mongo/scripting/mozjs/valuereader.h
@@ -45,7 +45,7 @@ public:
* Depth is used when readers are invoked from ObjectWrappers to avoid
* reading out overly nested objects
*/
- ValueReader(JSContext* cx, JS::MutableHandleValue value, int depth = 0);
+ ValueReader(JSContext* cx, JS::MutableHandleValue value);
void fromBSONElement(const BSONElement& elem, bool readOnly);
void fromBSON(const BSONObj& obj, bool readOnly);
@@ -55,7 +55,6 @@ public:
private:
JSContext* _context;
JS::MutableHandleValue _value;
- int _depth;
};
} // namespace mozjs
diff --git a/src/mongo/scripting/mozjs/valuewriter.cpp b/src/mongo/scripting/mozjs/valuewriter.cpp
index 7fdb1596fa3..6f57d48c460 100644
--- a/src/mongo/scripting/mozjs/valuewriter.cpp
+++ b/src/mongo/scripting/mozjs/valuewriter.cpp
@@ -43,8 +43,8 @@
namespace mongo {
namespace mozjs {
-ValueWriter::ValueWriter(JSContext* cx, JS::HandleValue value, int depth)
- : _context(cx), _value(value), _depth(depth), _originalParent(nullptr) {}
+ValueWriter::ValueWriter(JSContext* cx, JS::HandleValue value)
+ : _context(cx), _value(value), _originalParent(nullptr) {}
void ValueWriter::setOriginalBSON(BSONObj* obj) {
_originalParent = obj;
@@ -117,20 +117,7 @@ BSONObj ValueWriter::toBSON() {
JS::RootedObject obj(_context, _value.toObjectOrNull());
- if (getScope(_context)->getProto<BSONInfo>().instanceOf(obj)) {
- BSONObj* originalBSON;
- bool altered;
-
- std::tie(originalBSON, altered) = BSONInfo::originalBSON(_context, obj);
-
- if (originalBSON && !altered)
- return *originalBSON;
- }
-
- BSONObjBuilder bob;
- ObjectWrapper(_context, obj, _depth).writeThis(&bob);
-
- return bob.obj();
+ return ObjectWrapper(_context, obj).toBSON();
}
std::string ValueWriter::toString() {
@@ -189,11 +176,13 @@ Decimal128 ValueWriter::toDecimal128() {
uasserted(ErrorCodes::BadValue, str::stream() << "Unable to write Decimal128 value.");
}
-void ValueWriter::writeThis(BSONObjBuilder* b, StringData sd) {
+void ValueWriter::writeThis(BSONObjBuilder* b,
+ StringData sd,
+ ObjectWrapper::WriteFieldRecursionFrames* frames) {
uassert(17279,
- str::stream() << "Exceeded depth limit of " << 150
+ str::stream() << "Exceeded depth limit of " << ObjectWrapper::kMaxWriteFieldDepth
<< " when converting js object to BSON. Do you have a cycle?",
- _depth < 149);
+ frames->size() < ObjectWrapper::kMaxWriteFieldDepth);
// Null char should be at the end, not in the string
uassert(16985,
@@ -221,8 +210,7 @@ void ValueWriter::writeThis(BSONObjBuilder* b, StringData sd) {
b->append(sd, val);
} else if (_value.isObject()) {
- JS::RootedObject childObj(_context, _value.toObjectOrNull());
- _writeObject(b, sd, childObj);
+ _writeObject(b, sd, frames);
} else if (_value.isBoolean()) {
b->appendBool(sd, _value.toBoolean());
} else if (_value.isUndefined()) {
@@ -235,72 +223,126 @@ void ValueWriter::writeThis(BSONObjBuilder* b, StringData sd) {
}
}
-void ValueWriter::_writeObject(BSONObjBuilder* b, StringData sd, JS::HandleObject obj) {
+void ValueWriter::_writeObject(BSONObjBuilder* b,
+ StringData sd,
+ ObjectWrapper::WriteFieldRecursionFrames* frames) {
auto scope = getScope(_context);
- ObjectWrapper o(_context, obj, _depth);
-
- if (JS_ObjectIsFunction(_context, _value.toObjectOrNull())) {
- uassert(16716,
- "cannot convert native function to BSON",
- !scope->getProto<NativeFunctionInfo>().instanceOf(obj));
- b->appendCode(sd, ValueWriter(_context, _value).toString());
- } else if (JS_ObjectIsRegExp(_context, obj)) {
- JS::RootedValue v(_context);
- v.setObjectOrNull(obj);
-
- std::string regex = ValueWriter(_context, v).toString();
- regex = regex.substr(1);
- std::string r = regex.substr(0, regex.rfind('/'));
- std::string o = regex.substr(regex.rfind('/') + 1);
-
- b->appendRegex(sd, r, o);
- } else if (JS_ObjectIsDate(_context, obj)) {
- JS::RootedValue dateval(_context);
- o.callMethod("getTime", &dateval);
-
- auto d = Date_t::fromMillisSinceEpoch(ValueWriter(_context, dateval).toNumber());
- b->appendDate(sd, d);
- } else if (scope->getProto<OIDInfo>().instanceOf(obj)) {
- b->append(sd, OID(o.getString("str")));
- } else if (scope->getProto<NumberLongInfo>().instanceOf(obj)) {
- long long out = NumberLongInfo::ToNumberLong(_context, obj);
- b->append(sd, out);
- } else if (scope->getProto<NumberIntInfo>().instanceOf(obj)) {
- b->append(sd, NumberIntInfo::ToNumberInt(_context, obj));
- } else if (scope->getProto<NumberDecimalInfo>().instanceOf(obj)) {
- b->append(sd, NumberDecimalInfo::ToNumberDecimal(_context, obj));
- } else if (scope->getProto<DBPointerInfo>().instanceOf(obj)) {
- JS::RootedValue id(_context);
- o.getValue("id", &id);
-
- b->appendDBRef(sd, o.getString("ns"), OID(ObjectWrapper(_context, id).getString("str")));
- } else if (scope->getProto<BinDataInfo>().instanceOf(obj)) {
- auto str = static_cast<std::string*>(JS_GetPrivate(obj));
-
- auto binData = base64::decode(*str);
-
- b->appendBinData(sd,
- binData.size(),
- static_cast<mongo::BinDataType>(static_cast<int>(o.getNumber("type"))),
- binData.c_str());
- } else if (scope->getProto<TimestampInfo>().instanceOf(obj)) {
- Timestamp ot(o.getNumber("t"), o.getNumber("i"));
- b->append(sd, ot);
- } else if (scope->getProto<MinKeyInfo>().instanceOf(obj)) {
- b->appendMinKey(sd);
- } else if (scope->getProto<MaxKeyInfo>().instanceOf(obj)) {
- b->appendMaxKey(sd);
- } else {
- // nested object or array
+ // We open a block here because it's important that the two rooting types
+ // we need (obj and o) go out of scope before we actually open a
+ // new WriteFieldFrame (in the emplace at the bottom of the function). If
+ // we don't do this, we'll destroy the local roots in this function body
+ // before the frame we added, which will break the gc rooting list.
+ {
+ JS::RootedObject obj(_context, _value.toObjectOrNull());
+ ObjectWrapper o(_context, obj);
+
+ if (JS_ObjectIsFunction(_context, _value.toObjectOrNull())) {
+ uassert(16716,
+ "cannot convert native function to BSON",
+ !scope->getProto<NativeFunctionInfo>().instanceOf(obj));
+ b->appendCode(sd, ValueWriter(_context, _value).toString());
+ return;
+ }
+
+ if (JS_ObjectIsRegExp(_context, obj)) {
+ JS::RootedValue v(_context);
+ v.setObjectOrNull(obj);
+
+ std::string regex = ValueWriter(_context, v).toString();
+ regex = regex.substr(1);
+ std::string r = regex.substr(0, regex.rfind('/'));
+ std::string o = regex.substr(regex.rfind('/') + 1);
+
+ b->appendRegex(sd, r, o);
+
+ return;
+ }
+
+ if (JS_ObjectIsDate(_context, obj)) {
+ JS::RootedValue dateval(_context);
+ o.callMethod("getTime", &dateval);
+
+ auto d = Date_t::fromMillisSinceEpoch(ValueWriter(_context, dateval).toNumber());
+ b->appendDate(sd, d);
+
+ return;
+ }
+
+ if (scope->getProto<OIDInfo>().instanceOf(obj)) {
+ b->append(sd, OID(o.getString("str")));
+
+ return;
+ }
+
+ if (scope->getProto<NumberLongInfo>().instanceOf(obj)) {
+ long long out = NumberLongInfo::ToNumberLong(_context, obj);
+ b->append(sd, out);
+
+ return;
+ }
- BSONObjBuilder subbob(JS_IsArrayObject(_context, obj) ? b->subarrayStart(sd)
- : b->subobjStart(sd));
+ if (scope->getProto<NumberIntInfo>().instanceOf(obj)) {
+ b->append(sd, NumberIntInfo::ToNumberInt(_context, obj));
- ObjectWrapper child(_context, obj, _depth + 1);
+ return;
+ }
+
+ if (scope->getProto<NumberDecimalInfo>().instanceOf(obj)) {
+ b->append(sd, NumberDecimalInfo::ToNumberDecimal(_context, obj));
+
+ return;
+ }
+
+ if (scope->getProto<DBPointerInfo>().instanceOf(obj)) {
+ JS::RootedValue id(_context);
+ o.getValue("id", &id);
+
+ b->appendDBRef(
+ sd, o.getString("ns"), OID(ObjectWrapper(_context, id).getString("str")));
- child.writeThis(b);
+ return;
+ }
+
+ if (scope->getProto<BinDataInfo>().instanceOf(obj)) {
+ auto str = static_cast<std::string*>(JS_GetPrivate(obj));
+
+ auto binData = base64::decode(*str);
+
+ b->appendBinData(sd,
+ binData.size(),
+ static_cast<mongo::BinDataType>(static_cast<int>(o.getNumber("type"))),
+ binData.c_str());
+
+ return;
+ }
+
+ if (scope->getProto<TimestampInfo>().instanceOf(obj)) {
+ Timestamp ot(o.getNumber("t"), o.getNumber("i"));
+ b->append(sd, ot);
+
+ return;
+ }
+
+ if (scope->getProto<MinKeyInfo>().instanceOf(obj)) {
+ b->appendMinKey(sd);
+
+ return;
+ }
+
+ if (scope->getProto<MaxKeyInfo>().instanceOf(obj)) {
+ b->appendMaxKey(sd);
+
+ return;
+ }
}
+
+ // nested object or array
+
+ // This emplace is effectively a recursive function call, as this code path
+ // unwinds back to ObjectWrapper::toBSON. In that function we'll actually
+ // write the child we've just pushed onto the frames stack.
+ frames->emplace(_context, _value.toObjectOrNull(), b, sd);
}
} // namespace mozjs
diff --git a/src/mongo/scripting/mozjs/valuewriter.h b/src/mongo/scripting/mozjs/valuewriter.h
index 1945303867b..65f6f418d09 100644
--- a/src/mongo/scripting/mozjs/valuewriter.h
+++ b/src/mongo/scripting/mozjs/valuewriter.h
@@ -32,6 +32,7 @@
#include <string>
#include "mongo/bson/bsonobj.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
namespace mongo {
namespace mozjs {
@@ -39,14 +40,12 @@ namespace mozjs {
/**
* Writes C++ values out of JS Values
*
- * depth is used to trap circular objects in js and prevent stack smashing
- *
* originalBSON is a hack to keep integer types in their original type when
* they're read out, manipulated in js and saved back.
*/
class ValueWriter {
public:
- ValueWriter(JSContext* cx, JS::HandleValue value, int depth = 0);
+ ValueWriter(JSContext* cx, JS::HandleValue value);
BSONObj toBSON();
@@ -69,8 +68,15 @@ public:
/**
* Writes the value into a bsonobjbuilder under the name in sd.
+ *
+ * We take WriteFieldRecursionFrames so we can push a new object on if the underlying
+ * value is an object. This allows us to recurse without C++ stack frames.
+ *
+ * Look in toBSON on ObjectWrapper for the top of that loop.
*/
- void writeThis(BSONObjBuilder* b, StringData sd);
+ void writeThis(BSONObjBuilder* b,
+ StringData sd,
+ ObjectWrapper::WriteFieldRecursionFrames* frames);
void setOriginalBSON(BSONObj* obj);
@@ -78,11 +84,12 @@ private:
/**
* Writes the object into a bsonobjbuilder under the name in sd.
*/
- void _writeObject(BSONObjBuilder* b, StringData sd, JS::HandleObject obj);
+ void _writeObject(BSONObjBuilder* b,
+ StringData sd,
+ ObjectWrapper::WriteFieldRecursionFrames* frames);
JSContext* _context;
JS::HandleValue _value;
- int _depth;
BSONObj* _originalParent;
};
diff --git a/src/mongo/scripting/mozjs/wraptype.h b/src/mongo/scripting/mozjs/wraptype.h
index d569bcb9d86..3729e964c0e 100644
--- a/src/mongo/scripting/mozjs/wraptype.h
+++ b/src/mongo/scripting/mozjs/wraptype.h
@@ -240,8 +240,12 @@ public:
// before the other types are installed. Might as well just do it
// in the constructor.
if (T::classFlags & JSCLASS_GLOBAL_FLAGS) {
+ _jsclass.trace = JS_GlobalObjectTraceHook;
+
JS::RootedObject proto(_context);
+ JSAutoRequest ar(_context);
+
_proto.init(_context,
_assertPtr(JS_NewGlobalObject(
_context, &_jsclass, nullptr, JS::DontFireOnNewGlobalHook)));