summaryrefslogtreecommitdiff
path: root/src/mongo/scripting
diff options
context:
space:
mode:
authorJason Carey <jcarey@argv.me>2015-07-09 14:05:20 -0400
committerJason Carey <jcarey@argv.me>2015-07-14 16:15:54 -0400
commite749ffad9b4fe3110d97f366ebe39e7c9a4edd9d (patch)
tree927e727645da9bfc80095bb124860e31e58e9d77 /src/mongo/scripting
parent1af5f44f9ba2b7cff8e0457798b7a25b64e9fe69 (diff)
downloadmongo-e749ffad9b4fe3110d97f366ebe39e7c9a4edd9d.tar.gz
SERVER-18531 Integrate SpiderMonkey
Provides SpiderMonkey 38.0.1esr as a JS engine for mongo and mongod.
Diffstat (limited to 'src/mongo/scripting')
-rw-r--r--src/mongo/scripting/SConscript78
-rw-r--r--src/mongo/scripting/deadline_monitor.h (renamed from src/mongo/scripting/v8_deadline_monitor.h)0
-rw-r--r--src/mongo/scripting/deadline_monitor_test.cpp (renamed from src/mongo/scripting/v8_deadline_monitor_test.cpp)2
-rw-r--r--src/mongo/scripting/engine_v8-3.25.h2
-rw-r--r--src/mongo/scripting/engine_v8.h2
-rw-r--r--src/mongo/scripting/mozjs/PosixNSPR.cpp259
-rw-r--r--src/mongo/scripting/mozjs/base.cpp68
-rw-r--r--src/mongo/scripting/mozjs/base.h92
-rw-r--r--src/mongo/scripting/mozjs/bindata.cpp216
-rw-r--r--src/mongo/scripting/mozjs/bindata.h63
-rw-r--r--src/mongo/scripting/mozjs/bson.cpp235
-rw-r--r--src/mongo/scripting/mozjs/bson.h77
-rw-r--r--src/mongo/scripting/mozjs/countdownlatch.cpp198
-rw-r--r--src/mongo/scripting/mozjs/countdownlatch.h61
-rw-r--r--src/mongo/scripting/mozjs/cursor.cpp122
-rw-r--r--src/mongo/scripting/mozjs/cursor.h76
-rw-r--r--src/mongo/scripting/mozjs/db.cpp139
-rw-r--r--src/mongo/scripting/mozjs/db.h56
-rw-r--r--src/mongo/scripting/mozjs/dbcollection.cpp85
-rw-r--r--src/mongo/scripting/mozjs/dbcollection.h56
-rw-r--r--src/mongo/scripting/mozjs/dbpointer.cpp66
-rw-r--r--src/mongo/scripting/mozjs/dbpointer.h52
-rw-r--r--src/mongo/scripting/mozjs/dbquery.cpp143
-rw-r--r--src/mongo/scripting/mozjs/dbquery.h53
-rw-r--r--src/mongo/scripting/mozjs/dbref.cpp68
-rw-r--r--src/mongo/scripting/mozjs/dbref.h53
-rw-r--r--src/mongo/scripting/mozjs/engine.cpp133
-rw-r--r--src/mongo/scripting/mozjs/engine.h93
-rw-r--r--src/mongo/scripting/mozjs/exception.cpp89
-rw-r--r--src/mongo/scripting/mozjs/exception.h67
-rw-r--r--src/mongo/scripting/mozjs/global.cpp103
-rw-r--r--src/mongo/scripting/mozjs/global.h56
-rw-r--r--src/mongo/scripting/mozjs/idwrapper.cpp74
-rw-r--r--src/mongo/scripting/mozjs/idwrapper.h71
-rw-r--r--src/mongo/scripting/mozjs/implscope.cpp728
-rw-r--r--src/mongo/scripting/mozjs/implscope.h323
-rw-r--r--src/mongo/scripting/mozjs/jscustomallocator.cpp234
-rw-r--r--src/mongo/scripting/mozjs/jsstringwrapper.cpp84
-rw-r--r--src/mongo/scripting/mozjs/jsstringwrapper.h61
-rw-r--r--src/mongo/scripting/mozjs/jsthread.cpp274
-rw-r--r--src/mongo/scripting/mozjs/jsthread.h74
-rw-r--r--src/mongo/scripting/mozjs/maxkey.cpp94
-rw-r--r--src/mongo/scripting/mozjs/maxkey.h59
-rw-r--r--src/mongo/scripting/mozjs/minkey.cpp94
-rw-r--r--src/mongo/scripting/mozjs/minkey.h59
-rw-r--r--src/mongo/scripting/mozjs/mongo.cpp565
-rw-r--r--src/mongo/scripting/mozjs/mongo.h88
-rw-r--r--src/mongo/scripting/mozjs/nativefunction.cpp124
-rw-r--r--src/mongo/scripting/mozjs/nativefunction.h73
-rw-r--r--src/mongo/scripting/mozjs/numberint.cpp112
-rw-r--r--src/mongo/scripting/mozjs/numberint.h61
-rw-r--r--src/mongo/scripting/mozjs/numberlong.cpp164
-rw-r--r--src/mongo/scripting/mozjs/numberlong.h68
-rw-r--r--src/mongo/scripting/mozjs/object.cpp87
-rw-r--r--src/mongo/scripting/mozjs/object.h56
-rw-r--r--src/mongo/scripting/mozjs/objectwrapper.cpp385
-rw-r--r--src/mongo/scripting/mozjs/objectwrapper.h181
-rw-r--r--src/mongo/scripting/mozjs/oid.cpp87
-rw-r--r--src/mongo/scripting/mozjs/oid.h54
-rw-r--r--src/mongo/scripting/mozjs/proxyscope.cpp318
-rw-r--r--src/mongo/scripting/mozjs/proxyscope.h195
-rw-r--r--src/mongo/scripting/mozjs/regexp.cpp39
-rw-r--r--src/mongo/scripting/mozjs/regexp.h49
-rw-r--r--src/mongo/scripting/mozjs/timestamp.cpp77
-rw-r--r--src/mongo/scripting/mozjs/timestamp.h53
-rw-r--r--src/mongo/scripting/mozjs/valuereader.cpp272
-rw-r--r--src/mongo/scripting/mozjs/valuereader.h61
-rw-r--r--src/mongo/scripting/mozjs/valuewriter.cpp252
-rw-r--r--src/mongo/scripting/mozjs/valuewriter.h84
-rw-r--r--src/mongo/scripting/mozjs/wraptype.h474
70 files changed, 9066 insertions, 5 deletions
diff --git a/src/mongo/scripting/SConscript b/src/mongo/scripting/SConscript
index f05572b98c8..f3c3d9a05fd 100644
--- a/src/mongo/scripting/SConscript
+++ b/src/mongo/scripting/SConscript
@@ -4,6 +4,7 @@ Import([
'env',
'serverJs',
'usev8',
+ 'usemozjs',
'v8suffix',
])
@@ -68,6 +69,79 @@ if usev8:
'$BUILD_DIR/mongo/db/service_context',
],
)
+elif usemozjs:
+ scriptingEnv = env.Clone()
+ scriptingEnv.InjectThirdPartyIncludePaths(libraries=['mozjs'])
+
+ # TODO: get rid of all of this /FI and -include stuff and migrate to a shim
+ # header we include in all of our files.
+ if env.TargetOSIs('windows'):
+ scriptingEnv.Append(CCFLAGS=[
+ '/FI', 'js-config.h',
+ '/FI', 'js/RequiredDefines.h',
+ ])
+ else:
+ scriptingEnv.Append(
+ CCFLAGS=[
+ '-include', 'js-config.h',
+ '-include', 'js/RequiredDefines.h',
+ '-Wno-invalid-offsetof',
+ ],
+ CXXFLAGS=[
+ '-Wno-non-virtual-dtor',
+ ],
+ )
+
+ scriptingEnv.Prepend(CPPDEFINES=[
+ 'JS_USE_CUSTOM_ALLOCATOR',
+ 'STATIC_JS_API=1',
+ ])
+
+ scriptingEnv.Library(
+ target='scripting',
+ source=[
+ 'mozjs/base.cpp',
+ 'mozjs/bindata.cpp',
+ 'mozjs/bson.cpp',
+ 'mozjs/countdownlatch.cpp',
+ 'mozjs/cursor.cpp',
+ 'mozjs/dbcollection.cpp',
+ 'mozjs/db.cpp',
+ 'mozjs/dbpointer.cpp',
+ 'mozjs/dbquery.cpp',
+ 'mozjs/dbref.cpp',
+ 'mozjs/engine.cpp',
+ 'mozjs/exception.cpp',
+ 'mozjs/global.cpp',
+ 'mozjs/idwrapper.cpp',
+ 'mozjs/implscope.cpp',
+ 'mozjs/jscustomallocator.cpp',
+ 'mozjs/jsstringwrapper.cpp',
+ 'mozjs/jsthread.cpp',
+ 'mozjs/maxkey.cpp',
+ 'mozjs/minkey.cpp',
+ 'mozjs/mongo.cpp',
+ 'mozjs/nativefunction.cpp',
+ 'mozjs/numberint.cpp',
+ 'mozjs/numberlong.cpp',
+ 'mozjs/object.cpp',
+ 'mozjs/objectwrapper.cpp',
+ 'mozjs/oid.cpp',
+ 'mozjs/PosixNSPR.cpp',
+ 'mozjs/proxyscope.cpp',
+ 'mozjs/regexp.cpp',
+ 'mozjs/timestamp.cpp',
+ 'mozjs/valuereader.cpp',
+ 'mozjs/valuewriter.cpp',
+ ],
+ LIBDEPS=[
+ 'bson_template_evaluator',
+ 'scripting_common',
+ '$BUILD_DIR/third_party/shim_mozjs',
+ '$BUILD_DIR/mongo/shell/mongojs',
+ '$BUILD_DIR/mongo/db/service_context',
+ ],
+ )
else:
env.Library(
target='scripting',
@@ -88,9 +162,9 @@ env.Library(
)
env.CppUnitTest(
- target='v8_deadline_monitor_test',
+ target='deadline_monitor_test',
source=[
- 'v8_deadline_monitor_test.cpp',
+ 'deadline_monitor_test.cpp',
],
LIBDEPS=[
],
diff --git a/src/mongo/scripting/v8_deadline_monitor.h b/src/mongo/scripting/deadline_monitor.h
index 89f7e9d1b84..89f7e9d1b84 100644
--- a/src/mongo/scripting/v8_deadline_monitor.h
+++ b/src/mongo/scripting/deadline_monitor.h
diff --git a/src/mongo/scripting/v8_deadline_monitor_test.cpp b/src/mongo/scripting/deadline_monitor_test.cpp
index 702160cd1ed..98f4008b234 100644
--- a/src/mongo/scripting/v8_deadline_monitor_test.cpp
+++ b/src/mongo/scripting/deadline_monitor_test.cpp
@@ -30,7 +30,7 @@
#include "mongo/platform/basic.h"
-#include "mongo/scripting/v8_deadline_monitor.h"
+#include "mongo/scripting/deadline_monitor.h"
#include "mongo/unittest/unittest.h"
diff --git a/src/mongo/scripting/engine_v8-3.25.h b/src/mongo/scripting/engine_v8-3.25.h
index f3e594c33ee..0865ab59ac3 100644
--- a/src/mongo/scripting/engine_v8-3.25.h
+++ b/src/mongo/scripting/engine_v8-3.25.h
@@ -40,7 +40,7 @@
#include "mongo/client/dbclientcursor.h"
#include "mongo/platform/unordered_map.h"
#include "mongo/scripting/engine.h"
-#include "mongo/scripting/v8_deadline_monitor.h"
+#include "mongo/scripting/deadline_monitor.h"
#include "mongo/scripting/v8-3.25_profiler.h"
/**
diff --git a/src/mongo/scripting/engine_v8.h b/src/mongo/scripting/engine_v8.h
index 8825580c987..d70439c45ff 100644
--- a/src/mongo/scripting/engine_v8.h
+++ b/src/mongo/scripting/engine_v8.h
@@ -38,7 +38,7 @@
#include "mongo/client/dbclientcursor.h"
#include "mongo/platform/unordered_map.h"
#include "mongo/scripting/engine.h"
-#include "mongo/scripting/v8_deadline_monitor.h"
+#include "mongo/scripting/deadline_monitor.h"
#include "mongo/scripting/v8_profiler.h"
/**
diff --git a/src/mongo/scripting/mozjs/PosixNSPR.cpp b/src/mongo/scripting/mozjs/PosixNSPR.cpp
new file mode 100644
index 00000000000..2e6bbe93fe0
--- /dev/null
+++ b/src/mongo/scripting/mozjs/PosixNSPR.cpp
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file was copied out of the firefox 38.0.1esr source tree from
+ * js/src/vm/PosixNSPR.cpp and modified to use the MongoDB threading
+ * primitives.
+ *
+ * The point of this file is to shim the posix emulation of nspr that Mozilla
+ * ships with firefox. We force configuration such that the SpiderMonkey build
+ * looks for these symbols and we provide them from within our object code
+ * rather than attempting to build it in there's so we can take advantage of
+ * the cross platform abstractions that we rely upon.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include <array>
+#include <js/Utility.h>
+#include <vm/PosixNSPR.h>
+
+#include "mongo/stdx/chrono.h"
+#include "mongo/stdx/condition_variable.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/stdx/thread.h"
+#include "mongo/util/concurrency/thread_name.h"
+#include "mongo/util/concurrency/threadlocal.h"
+
+class nspr::Thread {
+ mongo::stdx::thread thread_;
+ void (*start)(void* arg);
+ void* arg;
+ bool joinable;
+
+public:
+ Thread(void (*start)(void* arg), void* arg, bool joinable)
+ : start(start), arg(arg), joinable(joinable) {}
+
+ static void* ThreadRoutine(void* arg);
+
+ mongo::stdx::thread& thread() {
+ return thread_;
+ }
+};
+
+namespace {
+MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL nspr::Thread* kCurrentThread;
+} // namespace
+
+void* nspr::Thread::ThreadRoutine(void* arg) {
+ Thread* self = static_cast<Thread*>(arg);
+ kCurrentThread = self;
+ self->start(self->arg);
+ if (!self->joinable)
+ js_delete(self);
+ return nullptr;
+}
+
+PRThread* PR_CreateThread(PRThreadType type,
+ void (*start)(void* arg),
+ void* arg,
+ PRThreadPriority priority,
+ PRThreadScope scope,
+ PRThreadState state,
+ uint32_t stackSize) {
+ MOZ_ASSERT(type == PR_USER_THREAD);
+ MOZ_ASSERT(priority == PR_PRIORITY_NORMAL);
+
+ try {
+ std::unique_ptr<nspr::Thread, void (*)(nspr::Thread*)> t(
+ js_new<nspr::Thread>(start, arg, state != PR_UNJOINABLE_THREAD),
+ js_delete<nspr::Thread>);
+
+ t->thread() = mongo::stdx::thread(&nspr::Thread::ThreadRoutine, t.get());
+
+ if (state == PR_UNJOINABLE_THREAD) {
+ t->thread().detach();
+ }
+
+ return t.release();
+ } catch (...) {
+ return nullptr;
+ }
+}
+
+PRStatus PR_JoinThread(PRThread* thread) {
+ try {
+ thread->thread().join();
+
+ js_delete(thread);
+
+ return PR_SUCCESS;
+ } catch (...) {
+ return PR_FAILURE;
+ }
+}
+
+PRThread* PR_GetCurrentThread() {
+ return kCurrentThread;
+}
+
+PRStatus PR_SetCurrentThreadName(const char* name) {
+ mongo::setThreadName(name);
+
+ return PR_SUCCESS;
+}
+
+static const size_t MaxTLSKeyCount = 32;
+static size_t gTLSKeyCount;
+namespace {
+MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL std::array<void*, MaxTLSKeyCount> gTLSArray;
+
+} // namespace
+
+PRStatus PR_NewThreadPrivateIndex(unsigned* newIndex, PRThreadPrivateDTOR destructor) {
+ /*
+ * We only call PR_NewThreadPrivateIndex from the main thread, so there's no
+ * need to lock the table of TLS keys.
+ */
+ MOZ_ASSERT(gTLSKeyCount + 1 < MaxTLSKeyCount);
+
+ *newIndex = gTLSKeyCount;
+ gTLSKeyCount++;
+
+ return PR_SUCCESS;
+}
+
+PRStatus PR_SetThreadPrivate(unsigned index, void* priv) {
+ if (index >= gTLSKeyCount)
+ return PR_FAILURE;
+
+ gTLSArray[index] = priv;
+
+ return PR_SUCCESS;
+}
+
+void* PR_GetThreadPrivate(unsigned index) {
+ if (index >= gTLSKeyCount)
+ return nullptr;
+
+ return gTLSArray[index];
+}
+
+PRStatus PR_CallOnce(PRCallOnceType* once, PRCallOnceFN func) {
+ MOZ_CRASH("PR_CallOnce unimplemented");
+}
+
+PRStatus PR_CallOnceWithArg(PRCallOnceType* once, PRCallOnceWithArgFN func, void* arg) {
+ MOZ_CRASH("PR_CallOnceWithArg unimplemented");
+}
+
+class nspr::Lock {
+ mongo::stdx::mutex mutex_;
+
+public:
+ Lock() {}
+ mongo::stdx::mutex& mutex() {
+ return mutex_;
+ }
+};
+
+PRLock* PR_NewLock() {
+ return js_new<nspr::Lock>();
+}
+
+void PR_DestroyLock(PRLock* lock) {
+ js_delete(lock);
+}
+
+void PR_Lock(PRLock* lock) {
+ lock->mutex().lock();
+}
+
+PRStatus PR_Unlock(PRLock* lock) {
+ lock->mutex().unlock();
+
+ return PR_SUCCESS;
+}
+
+class nspr::CondVar {
+ mongo::stdx::condition_variable cond_;
+ nspr::Lock* lock_;
+
+public:
+ CondVar(nspr::Lock* lock) : lock_(lock) {}
+ mongo::stdx::condition_variable& cond() {
+ return cond_;
+ }
+ nspr::Lock* lock() {
+ return lock_;
+ }
+};
+
+PRCondVar* PR_NewCondVar(PRLock* lock) {
+ return js_new<nspr::CondVar>(lock);
+}
+
+void PR_DestroyCondVar(PRCondVar* cvar) {
+ js_delete(cvar);
+}
+
+PRStatus PR_NotifyCondVar(PRCondVar* cvar) {
+ cvar->cond().notify_one();
+
+ return PR_SUCCESS;
+}
+
+PRStatus PR_NotifyAllCondVar(PRCondVar* cvar) {
+ cvar->cond().notify_all();
+
+ return PR_SUCCESS;
+}
+
+uint32_t PR_MillisecondsToInterval(uint32_t milli) {
+ return milli;
+}
+
+uint32_t PR_MicrosecondsToInterval(uint32_t micro) {
+ return (micro + 999) / 1000;
+}
+
+static const uint64_t TicksPerSecond = 1000;
+static const uint64_t NanoSecondsInSeconds = 1000000000;
+static const uint64_t MicroSecondsInSeconds = 1000000;
+
+uint32_t PR_TicksPerSecond() {
+ return TicksPerSecond;
+}
+
+PRStatus PR_WaitCondVar(PRCondVar* cvar, uint32_t timeout) {
+ if (timeout == PR_INTERVAL_NO_TIMEOUT) {
+ try {
+ mongo::stdx::unique_lock<mongo::stdx::mutex> lk(cvar->lock()->mutex(),
+ mongo::stdx::adopt_lock_t());
+
+ cvar->cond().wait(lk);
+ lk.release();
+
+ return PR_SUCCESS;
+ } catch (...) {
+ return PR_FAILURE;
+ }
+ } else {
+ try {
+ mongo::stdx::unique_lock<mongo::stdx::mutex> lk(cvar->lock()->mutex(),
+ mongo::stdx::adopt_lock_t());
+
+ cvar->cond().wait_for(lk, mongo::stdx::chrono::microseconds(timeout));
+ lk.release();
+
+ return PR_SUCCESS;
+ } catch (...) {
+ return PR_FAILURE;
+ }
+ }
+}
diff --git a/src/mongo/scripting/mozjs/base.cpp b/src/mongo/scripting/mozjs/base.cpp
new file mode 100644
index 00000000000..b85229ae354
--- /dev/null
+++ b/src/mongo/scripting/mozjs/base.cpp
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/base.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec* BaseInfo::freeFunctions = nullptr;
+const JSFunctionSpec* BaseInfo::methods = nullptr;
+
+const char* const BaseInfo::inheritFrom = nullptr;
+
+void BaseInfo::addProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue v) {}
+void BaseInfo::call(JSContext* cx, JS::CallArgs args) {}
+void BaseInfo::construct(JSContext* cx, JS::CallArgs args) {}
+void BaseInfo::convert(JSContext* cx,
+ JS::HandleObject obj,
+ JSType type,
+ JS::MutableHandleValue vp) {}
+void BaseInfo::delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded) {}
+void BaseInfo::enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties) {}
+void BaseInfo::finalize(JSFreeOp* fop, JSObject* obj) {}
+void BaseInfo::getProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue vp) {}
+void BaseInfo::hasInstance(JSContext* cx,
+ JS::HandleObject obj,
+ JS::MutableHandleValue vp,
+ bool* bp) {}
+void BaseInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {}
+void BaseInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) {}
+void BaseInfo::setProperty(
+ JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict, JS::MutableHandleValue vp) {}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/base.h b/src/mongo/scripting/mozjs/base.h
new file mode 100644
index 00000000000..fe90ea65b97
--- /dev/null
+++ b/src/mongo/scripting/mozjs/base.h
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * InstallType represents how we want this type overlayed in the JS world.
+ *
+ * Global is for regular types that get installed into the global scope
+ * Private gives us the type, but doesn't make the prototype publicly available
+ * OverNative is used to attach functionality to prototypes that are already there.
+ */
+enum class InstallType : char {
+ Global = 0,
+ Private,
+ OverNative,
+};
+
+/**
+ * The Base object for all info types
+ *
+ * It's difficult to access the array types correctly in a non constexpr world,
+ * so we just stash some nullptrs that are universally available.
+ */
+struct BaseInfo {
+ static const char* const inheritFrom;
+ static const InstallType installType = InstallType::Global;
+ static const JSFunctionSpec* freeFunctions;
+ static const JSFunctionSpec* methods;
+ static const unsigned classFlags = 0;
+ static void addProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue v);
+ static void call(JSContext* cx, JS::CallArgs args);
+ static void construct(JSContext* cx, JS::CallArgs args);
+ static void convert(JSContext* cx,
+ JS::HandleObject obj,
+ JSType type,
+ JS::MutableHandleValue vp);
+ static void delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded);
+ static void enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties);
+ static void finalize(JSFreeOp* fop, JSObject* obj);
+ static void getProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue vp);
+ static void hasInstance(JSContext* cx,
+ JS::HandleObject obj,
+ JS::MutableHandleValue vp,
+ bool* bp);
+ static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto);
+ static void resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp);
+ static void setProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ bool strict,
+ JS::MutableHandleValue vp);
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/bindata.cpp b/src/mongo/scripting/mozjs/bindata.cpp
new file mode 100644
index 00000000000..10a3045f103
--- /dev/null
+++ b/src/mongo/scripting/mozjs/bindata.cpp
@@ -0,0 +1,216 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/bindata.h"
+
+#include <iomanip>
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/util/base64.h"
+#include "mongo/util/hex.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec BinDataInfo::methods[4] = {
+ MONGO_ATTACH_JS_FUNCTION(base64),
+ MONGO_ATTACH_JS_FUNCTION(hex),
+ MONGO_ATTACH_JS_FUNCTION(toString),
+ JS_FS_END,
+};
+
+const JSFunctionSpec BinDataInfo::freeFunctions[4] = {
+ MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(HexData, JSFUN_CONSTRUCTOR),
+ MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(MD5, JSFUN_CONSTRUCTOR),
+ MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(UUID, JSFUN_CONSTRUCTOR),
+ JS_FS_END,
+};
+
+const char* const BinDataInfo::className = "BinData";
+
+namespace {
+
+void hexToBinData(JSContext* cx, int type, StringData hexstr, JS::MutableHandleValue out) {
+ auto scope = getScope(cx);
+
+ // SERVER-9686: This function does not correctly check to make sure hexstr is actually made
+ // up of valid hex digits, and fails in the hex utility functions
+
+ int len = hexstr.size() / 2;
+ std::unique_ptr<char[]> data(new char[len]);
+ const char* src = hexstr.rawData();
+ for (int i = 0; i < len; i++) {
+ data[i] = fromHex(src + i * 2);
+ }
+
+ std::string encoded = base64::encode(data.get(), len);
+ JS::AutoValueArray<2> args(cx);
+
+ args[0].setInt32(type);
+ ValueReader(cx, args[1]).fromStringData(encoded);
+ return scope->getBinDataProto().newInstance(args, out);
+}
+
+std::string* getEncoded(JS::HandleValue thisv) {
+ return static_cast<std::string*>(JS_GetPrivate(thisv.toObjectOrNull()));
+}
+
+std::string* getEncoded(JSObject* thisv) {
+ return static_cast<std::string*>(JS_GetPrivate(thisv));
+}
+
+} // namespace
+
+void BinDataInfo::finalize(JSFreeOp* fop, JSObject* obj) {
+ auto str = getEncoded(obj);
+
+ if (str) {
+ delete str;
+ }
+}
+
+void BinDataInfo::Functions::UUID(JSContext* cx, JS::CallArgs args) {
+ if (args.length() != 1)
+ uasserted(ErrorCodes::BadValue, "UUID needs 1 argument");
+
+ auto str = ValueWriter(cx, args.get(0)).toString();
+
+ if (str.length() != 32)
+ uasserted(ErrorCodes::BadValue, "UUID string must have 32 characters");
+
+ hexToBinData(cx, bdtUUID, str, args.rval());
+}
+
+void BinDataInfo::Functions::MD5(JSContext* cx, JS::CallArgs args) {
+ if (args.length() != 1)
+ uasserted(ErrorCodes::BadValue, "MD5 needs 1 argument");
+
+ auto str = ValueWriter(cx, args.get(0)).toString();
+
+ if (str.length() != 32)
+ uasserted(ErrorCodes::BadValue, "MD5 string must have 32 characters");
+
+ hexToBinData(cx, MD5Type, str, args.rval());
+}
+
+void BinDataInfo::Functions::HexData(JSContext* cx, JS::CallArgs args) {
+ if (args.length() != 2)
+ uasserted(ErrorCodes::BadValue, "HexData needs 2 arguments");
+
+ JS::RootedValue type(cx, args.get(0));
+
+ if (!type.isNumber() || type.toInt32() < 0 || type.toInt32() > 255)
+ uasserted(ErrorCodes::BadValue,
+ "HexData subtype must be a Number between 0 and 255 inclusive");
+
+ auto str = ValueWriter(cx, args.get(1)).toString();
+
+ hexToBinData(cx, type.toInt32(), str, args.rval());
+}
+
+void BinDataInfo::Functions::toString(JSContext* cx, JS::CallArgs args) {
+ ObjectWrapper o(cx, args.thisv());
+
+ auto str = getEncoded(args.thisv());
+
+ str::stream ss;
+
+ ss << "BinData(" << o.getNumber("type") << ",\"" << *str << "\")";
+
+ ValueReader(cx, args.rval()).fromStringData(ss.operator std::string());
+}
+
+void BinDataInfo::Functions::base64(JSContext* cx, JS::CallArgs args) {
+ auto str = getEncoded(args.thisv());
+
+ ValueReader(cx, args.rval()).fromStringData(*str);
+}
+
+void BinDataInfo::Functions::hex(JSContext* cx, JS::CallArgs args) {
+ auto str = getEncoded(args.thisv());
+
+ std::string data = base64::decode(*str);
+ std::stringstream ss;
+ ss.setf(std::ios_base::hex, std::ios_base::basefield);
+ ss.fill('0');
+ ss.setf(std::ios_base::right, std::ios_base::adjustfield);
+ for (auto it = data.begin(); it != data.end(); ++it) {
+ unsigned v = (unsigned char)*it;
+ ss << std::setw(2) << v;
+ }
+
+ ValueReader(cx, args.rval()).fromStringData(ss.str());
+}
+
+void BinDataInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 2) {
+ uasserted(ErrorCodes::BadValue, "BinData takes 2 arguments -- BinData(subtype,data)");
+ }
+
+ auto type = args.get(0);
+
+ if (!type.isNumber() || type.toInt32() < 0 || type.toInt32() > 255) {
+ uasserted(ErrorCodes::BadValue,
+ "BinData subtype must be a Number between 0 and 255 inclusive");
+ }
+
+ auto utf = args.get(1);
+
+ if (!utf.isString()) {
+ uasserted(ErrorCodes::BadValue, "BinData data must be a String");
+ }
+
+ auto str = ValueWriter(cx, utf).toString();
+
+ auto tmpBase64 = base64::decode(str);
+
+ JS::RootedObject thisv(cx);
+ scope->getBinDataProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ JS::RootedValue len(cx);
+ len.setInt32(tmpBase64.length());
+
+ o.defineProperty("len", len, JSPROP_READONLY);
+ o.defineProperty("type", type, JSPROP_READONLY);
+
+ JS_SetPrivate(thisv, new std::string(std::move(str)));
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/bindata.h b/src/mongo/scripting/mozjs/bindata.h
new file mode 100644
index 00000000000..85b504230ff
--- /dev/null
+++ b/src/mongo/scripting/mozjs/bindata.h
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Wrapper for the BinData bson type
+ *
+ * It offers some simple methods and a handful of specialized constructors
+ */
+struct BinDataInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+ static void finalize(JSFreeOp* fop, JSObject* obj);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(base64);
+ MONGO_DEFINE_JS_FUNCTION(hex);
+ MONGO_DEFINE_JS_FUNCTION(toString);
+
+ MONGO_DEFINE_JS_FUNCTION(HexData);
+ MONGO_DEFINE_JS_FUNCTION(MD5);
+ MONGO_DEFINE_JS_FUNCTION(UUID);
+ };
+
+ static const JSFunctionSpec methods[4];
+ static const JSFunctionSpec freeFunctions[4];
+
+ static const char* const className;
+ static const unsigned classFlags = JSCLASS_HAS_PRIVATE;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/bson.cpp b/src/mongo/scripting/mozjs/bson.cpp
new file mode 100644
index 00000000000..308e29d90f3
--- /dev/null
+++ b/src/mongo/scripting/mozjs/bson.cpp
@@ -0,0 +1,235 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/bson.h"
+
+#include <set>
+
+#include "mongo/scripting/mozjs/idwrapper.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+
+namespace mongo {
+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
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/bson.h b/src/mongo/scripting/mozjs/bson.h
new file mode 100644
index 00000000000..bcf57b5eda4
--- /dev/null
+++ b/src/mongo/scripting/mozjs/bson.h
@@ -0,0 +1,77 @@
+/**
+ * Copyright (C) 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 <tuple>
+
+#include "mongo/db/jsobj.h"
+#include "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Provides a wrapper for BSONObj's in JS. The main idea here is that BSONObj's
+ * can be read only, or read write when we shim them in, and in all cases we
+ * lazily load their member elements into JS. So a bunch of these lifecycle
+ * methods are set up to wrap field access (enumerate, resolve and
+ * del/setProperty).
+ *
+ * Note that installType is private. So you can only get BSON types in JS via
+ * ::make() from C++.
+ */
+struct BSONInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+ static void delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded);
+ static void enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties);
+ static void finalize(JSFreeOp* fop, JSObject* obj);
+ static void resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp);
+ static void setProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ bool strict,
+ JS::MutableHandleValue vp);
+
+ static const char* const className;
+ static const unsigned classFlags = JSCLASS_HAS_PRIVATE;
+ static const InstallType installType = InstallType::Private;
+ static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(bsonWoCompare);
+ };
+
+ static const JSFunctionSpec freeFunctions[2];
+
+ static std::tuple<BSONObj*, bool> originalBSON(JSContext* cx, JS::HandleObject obj);
+ static void make(JSContext* cx, JS::MutableHandleObject obj, BSONObj bson, bool ro);
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/countdownlatch.cpp b/src/mongo/scripting/mozjs/countdownlatch.cpp
new file mode 100644
index 00000000000..bf7ae4ff551
--- /dev/null
+++ b/src/mongo/scripting/mozjs/countdownlatch.cpp
@@ -0,0 +1,198 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/countdownlatch.h"
+
+#include <unordered_map>
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/stdx/condition_variable.h"
+#include "mongo/stdx/mutex.h"
+
+namespace mongo {
+namespace mozjs {
+
+const char* const CountDownLatchInfo::className = "CountDownLatch";
+
+const JSFunctionSpec CountDownLatchInfo::methods[5] = {
+ MONGO_ATTACH_JS_FUNCTION(_new),
+ MONGO_ATTACH_JS_FUNCTION(_await),
+ MONGO_ATTACH_JS_FUNCTION(_countDown),
+ MONGO_ATTACH_JS_FUNCTION(_getCount),
+ JS_FS_END,
+};
+
+/**
+ * The global CountDownLatch holder.
+ *
+ * Provides an interface for communicating between JSThread's
+ */
+class CountDownLatchHolder {
+public:
+ CountDownLatchHolder() : _counter(0) {}
+
+ int32_t make(int32_t count) {
+ uassert(ErrorCodes::JSInterpreterFailure, "argument must be >= 0", count >= 0);
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+
+ int32_t desc = ++_counter;
+ _latches.insert(std::make_pair(desc, std::make_shared<Latch>(count)));
+
+ return desc;
+ }
+
+ void await(int32_t desc) {
+ std::shared_ptr<Latch> latch = get(desc);
+ stdx::unique_lock<stdx::mutex> lock(latch->mutex);
+
+ while (latch->count != 0) {
+ latch->cv.wait(lock);
+ }
+ }
+
+ void countDown(int32_t desc) {
+ std::shared_ptr<Latch> latch = get(desc);
+ stdx::unique_lock<stdx::mutex> lock(latch->mutex);
+
+ if (latch->count > 0)
+ latch->count--;
+
+ if (latch->count == 0)
+ latch->cv.notify_all();
+ }
+
+ int32_t getCount(int32_t desc) {
+ std::shared_ptr<Latch> latch = get(desc);
+ stdx::unique_lock<stdx::mutex> lock(latch->mutex);
+
+ return latch->count;
+ }
+
+private:
+ /**
+ * Latches for communication between threads
+ */
+ struct Latch {
+ Latch(int32_t count) : count(count) {}
+
+ stdx::mutex mutex;
+ stdx::condition_variable cv;
+ int32_t count;
+ };
+
+ std::shared_ptr<Latch> get(int32_t desc) {
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+
+ auto iter = _latches.find(desc);
+ uassert(ErrorCodes::JSInterpreterFailure,
+ "not a valid CountDownLatch descriptor",
+ iter != _latches.end());
+
+ return iter->second;
+ }
+
+ using Map = std::unordered_map<int32_t, std::shared_ptr<Latch>>;
+
+ stdx::mutex _mutex;
+ Map _latches;
+ int32_t _counter;
+};
+
+namespace {
+CountDownLatchHolder globalCountDownLatchHolder;
+} // namespace
+
+void CountDownLatchInfo::Functions::_new(JSContext* cx, JS::CallArgs args) {
+ uassert(ErrorCodes::JSInterpreterFailure, "need exactly one argument", args.length() == 1);
+ uassert(
+ ErrorCodes::JSInterpreterFailure, "argument must be an integer", args.get(0).isNumber());
+
+ args.rval().setInt32(globalCountDownLatchHolder.make(args.get(0).toNumber()));
+}
+
+void CountDownLatchInfo::Functions::_await(JSContext* cx, JS::CallArgs args) {
+ uassert(ErrorCodes::JSInterpreterFailure, "need exactly one argument", args.length() == 1);
+ uassert(
+ ErrorCodes::JSInterpreterFailure, "argument must be an integer", args.get(0).isNumber());
+
+ globalCountDownLatchHolder.await(args.get(0).toNumber());
+
+ args.rval().setUndefined();
+}
+
+void CountDownLatchInfo::Functions::_countDown(JSContext* cx, JS::CallArgs args) {
+ uassert(ErrorCodes::JSInterpreterFailure, "need exactly one argument", args.length() == 1);
+ uassert(
+ ErrorCodes::JSInterpreterFailure, "argument must be an integer", args.get(0).isNumber());
+
+ globalCountDownLatchHolder.countDown(args.get(0).toNumber());
+
+ args.rval().setUndefined();
+}
+
+void CountDownLatchInfo::Functions::_getCount(JSContext* cx, JS::CallArgs args) {
+ uassert(ErrorCodes::JSInterpreterFailure, "need exactly one argument", args.length() == 1);
+ uassert(
+ ErrorCodes::JSInterpreterFailure, "argument must be an integer", args.get(0).isNumber());
+
+ args.rval().setInt32(globalCountDownLatchHolder.getCount(args.get(0).toNumber()));
+}
+
+/**
+ * We have to do this odd dance here because we need the methods from
+ * CountDownLatch to be installed in a plain object as enumerable properties.
+ * This is due to the way CountDownLatch is invoked, specifically after being
+ * transmitted across our js fork(). So we can't inherit and can't rely on the
+ * type. Practically, we also end up wrapping up all of these functions in pure
+ * js variants that call down, which makes them bson <-> js safe.
+ */
+void CountDownLatchInfo::postInstall(JSContext* cx,
+ JS::HandleObject global,
+ JS::HandleObject proto) {
+ auto objPtr = JS_NewPlainObject(cx);
+ uassert(ErrorCodes::JSInterpreterFailure, "Failed to JS_NewPlainObject", objPtr);
+
+ JS::RootedObject obj(cx, objPtr);
+ ObjectWrapper objWrapper(cx, obj);
+ ObjectWrapper protoWrapper(cx, proto);
+
+ JS::RootedValue val(cx);
+ for (auto iter = methods; iter->name; ++iter) {
+ protoWrapper.getValue(iter->name, &val);
+ objWrapper.setValue(iter->name, val);
+ }
+
+ val.setObjectOrNull(obj);
+ ObjectWrapper(cx, global).setValue("CountDownLatch", val);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/countdownlatch.h b/src/mongo/scripting/mozjs/countdownlatch.h
new file mode 100644
index 00000000000..07001ebf473
--- /dev/null
+++ b/src/mongo/scripting/mozjs/countdownlatch.h
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "CountDownLatch" javascript object.
+ *
+ * Installs a global "CountDownLatch" object with associated methods.
+ *
+ * Note that there is only one instance of this class and it is used to
+ * communicate between different C++ threads.
+ */
+struct CountDownLatchInfo : public BaseInfo {
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(_new);
+ MONGO_DEFINE_JS_FUNCTION(_await);
+ MONGO_DEFINE_JS_FUNCTION(_countDown);
+ MONGO_DEFINE_JS_FUNCTION(_getCount);
+ };
+
+ static const JSFunctionSpec methods[5];
+
+ static const char* const className;
+ static const InstallType installType = InstallType::Private;
+
+ static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto);
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/cursor.cpp b/src/mongo/scripting/mozjs/cursor.cpp
new file mode 100644
index 00000000000..8f6bb7b540c
--- /dev/null
+++ b/src/mongo/scripting/mozjs/cursor.cpp
@@ -0,0 +1,122 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/cursor.h"
+
+#include "mongo/scripting/mozjs/bson.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec CursorInfo::methods[5] = {
+ MONGO_ATTACH_JS_FUNCTION(hasNext),
+ MONGO_ATTACH_JS_FUNCTION(next),
+ MONGO_ATTACH_JS_FUNCTION(objsLeftInBatch),
+ MONGO_ATTACH_JS_FUNCTION(readOnly),
+ JS_FS_END,
+};
+
+const char* const CursorInfo::className = "Cursor";
+
+namespace {
+
+DBClientCursor* getCursor(JSObject* thisv) {
+ return static_cast<CursorInfo::CursorHolder*>(JS_GetPrivate(thisv))->cursor.get();
+}
+
+DBClientCursor* getCursor(JS::CallArgs& args) {
+ return getCursor(args.thisv().toObjectOrNull());
+}
+
+} // namespace
+
+void CursorInfo::finalize(JSFreeOp* fop, JSObject* obj) {
+ auto cursor = static_cast<CursorInfo::CursorHolder*>(JS_GetPrivate(obj));
+
+ if (cursor) {
+ delete cursor;
+ }
+}
+
+void CursorInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ scope->getCursorProto().newObject(args.rval());
+}
+
+void CursorInfo::Functions::next(JSContext* cx, JS::CallArgs args) {
+ auto cursor = getCursor(args);
+
+ if (!cursor) {
+ args.rval().setUndefined();
+ return;
+ }
+
+ ObjectWrapper o(cx, args.thisv());
+
+ BSONObj bson = cursor->next();
+ bool ro = o.hasField("_ro") ? o.getBoolean("_ro") : false;
+
+ ValueReader(cx, args.rval()).fromBSON(bson, ro);
+}
+
+void CursorInfo::Functions::hasNext(JSContext* cx, JS::CallArgs args) {
+ auto cursor = getCursor(args);
+
+ if (!cursor) {
+ args.rval().setBoolean(false);
+ return;
+ }
+
+ args.rval().setBoolean(cursor->more());
+}
+
+void CursorInfo::Functions::objsLeftInBatch(JSContext* cx, JS::CallArgs args) {
+ auto cursor = getCursor(args);
+
+ if (!cursor) {
+ args.rval().setInt32(0);
+ return;
+ }
+
+ args.rval().setInt32(cursor->objsLeftInBatch());
+}
+
+void CursorInfo::Functions::readOnly(JSContext* cx, JS::CallArgs args) {
+ ObjectWrapper(cx, args.thisv()).setBoolean("_ro", true);
+
+ args.rval().set(args.thisv());
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/cursor.h b/src/mongo/scripting/mozjs/cursor.h
new file mode 100644
index 00000000000..39c8ba56295
--- /dev/null
+++ b/src/mongo/scripting/mozjs/cursor.h
@@ -0,0 +1,76 @@
+/**
+ * Copyright (C) 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 "mongo/client/dbclientcursor.h"
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Wraps a DBClientCursor in javascript
+ *
+ * Note that the install is private, so this class should only be constructible
+ * from C++. Current callers are all via the Mongo object.
+ */
+struct CursorInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+ static void finalize(JSFreeOp* fop, JSObject* obj);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(hasNext);
+ MONGO_DEFINE_JS_FUNCTION(next);
+ MONGO_DEFINE_JS_FUNCTION(objsLeftInBatch);
+ MONGO_DEFINE_JS_FUNCTION(readOnly);
+ };
+
+ static const JSFunctionSpec methods[5];
+
+ static const char* const className;
+ static const unsigned classFlags = JSCLASS_HAS_PRIVATE;
+ static const InstallType installType = InstallType::Private;
+
+ /**
+ * We need this because the DBClientBase can go out of scope before all of
+ * its children (as in global shutdown). So we have to manage object
+ * lifetimes in C++ land.
+ */
+ struct CursorHolder {
+ CursorHolder(std::unique_ptr<DBClientCursor> cursor, std::shared_ptr<DBClientBase> client)
+ : client(std::move(client)), cursor(std::move(cursor)) {}
+
+ std::shared_ptr<DBClientBase> client;
+ std::unique_ptr<DBClientCursor> cursor;
+ };
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/db.cpp b/src/mongo/scripting/mozjs/db.cpp
new file mode 100644
index 00000000000..da0197cc293
--- /dev/null
+++ b/src/mongo/scripting/mozjs/db.cpp
@@ -0,0 +1,139 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/db.h"
+
+#include "mongo/db/namespace_string.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/scripting/mozjs/idwrapper.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/s/d_state.h"
+
+namespace mongo {
+namespace mozjs {
+
+const char* const DBInfo::className = "DB";
+
+void DBInfo::getProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue vp) {
+ JS::RootedObject parent(cx);
+ if (!JS_GetPrototype(cx, obj, &parent))
+ uasserted(ErrorCodes::JSInterpreterFailure, "Couldn't get prototype");
+
+ auto scope = getScope(cx);
+
+ ObjectWrapper parentWrapper(cx, parent);
+
+ std::string sname = IdWrapper(cx, id).toString();
+
+ // 2nd look into real values, may be cached collection object
+ if (!vp.isUndefined()) {
+ if (vp.isObject()) {
+ ObjectWrapper o(cx, vp);
+
+ if (o.hasField("_fullName")) {
+ auto opContext = scope->getOpContext();
+
+ // need to check every time that the collection did not get sharded
+ if (opContext &&
+ haveLocalShardingInfo(opContext->getClient(), o.getString("_fullName")))
+ uasserted(ErrorCodes::BadValue, "can't use sharded collection from db.eval");
+ }
+ }
+
+ return;
+ } else if (parentWrapper.hasField(id)) {
+ parentWrapper.getValue(id, vp);
+ return;
+ } else if (sname.length() == 0 || sname[0] == '_') {
+ // if starts with '_' we dont return collection, one must use getCollection()
+ return;
+ }
+
+ // no hit, create new collection
+ JS::RootedValue getCollection(cx);
+ parentWrapper.getValue("getCollection", &getCollection);
+
+ if (!(getCollection.isObject() && JS_ObjectIsFunction(cx, getCollection.toObjectOrNull()))) {
+ uasserted(ErrorCodes::BadValue, "getCollection is not a function");
+ }
+
+ JS::AutoValueArray<1> args(cx);
+
+ ValueReader(cx, args[0]).fromStringData(sname);
+
+ JS::RootedValue coll(cx);
+ ObjectWrapper(cx, obj).callMethod(getCollection, args, &coll);
+
+ uassert(16861,
+ "getCollection returned something other than a collection",
+ scope->getDbCollectionProto().instanceOf(coll));
+
+ // cache collection for reuse, don't enumerate
+ ObjectWrapper(cx, obj).defineProperty(sname.c_str(), coll, 0);
+
+ vp.set(coll);
+}
+
+void DBInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 2)
+ uasserted(ErrorCodes::BadValue, "db constructor requires 2 arguments");
+
+ for (unsigned i = 0; i < args.length(); ++i) {
+ uassert(ErrorCodes::BadValue,
+ "db initializer called with undefined argument",
+ !args.get(i).isUndefined());
+ }
+
+ JS::RootedObject thisv(cx);
+ scope->getDbProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ o.setValue("_mongo", args.get(0));
+ o.setValue("_name", args.get(1));
+
+ std::string dbName = ValueWriter(cx, args.get(1)).toString();
+
+ if (!NamespaceString::validDBName(dbName))
+ uasserted(ErrorCodes::BadValue,
+ str::stream() << "[" << dbName << "] is not a valid database name");
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/db.h b/src/mongo/scripting/mozjs/db.h
new file mode 100644
index 00000000000..c752953236f
--- /dev/null
+++ b/src/mongo/scripting/mozjs/db.h
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "DB" Javascript object.
+ *
+ * This maps to the 'db' global variable you can call db.COLLECTION_NAME.X() on
+ * in the shell.
+ *
+ * Its major magic is in its getProperty() callback, which threads through to
+ * a getCollection method installed in js
+ */
+struct DBInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+ static void getProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue vp);
+
+ static const char* const className;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/dbcollection.cpp b/src/mongo/scripting/mozjs/dbcollection.cpp
new file mode 100644
index 00000000000..fd8bc086676
--- /dev/null
+++ b/src/mongo/scripting/mozjs/dbcollection.cpp
@@ -0,0 +1,85 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/dbcollection.h"
+
+#include "mongo/db/namespace_string.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/scripting/mozjs/bson.h"
+#include "mongo/scripting/mozjs/db.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/s/d_state.h"
+
+namespace mongo {
+namespace mozjs {
+
+const char* const DBCollectionInfo::className = "DBCollection";
+
+void DBCollectionInfo::getProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue vp) {
+ DBInfo::getProperty(cx, obj, id, vp);
+}
+
+void DBCollectionInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 4)
+ uasserted(ErrorCodes::BadValue, "collection constructor requires 4 arguments");
+
+ for (unsigned i = 0; i < args.length(); ++i) {
+ uassert(ErrorCodes::BadValue,
+ "collection constructor called with undefined argument",
+ !args.get(i).isUndefined());
+ }
+
+ JS::RootedObject thisv(cx);
+ scope->getDbCollectionProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ o.setValue("_mongo", args.get(0));
+ o.setValue("_db", args.get(1));
+ o.setValue("_shortName", args.get(2));
+ o.setValue("_fullName", args.get(3));
+
+ std::string fullName = ValueWriter(cx, args.get(3)).toString();
+
+ auto context = scope->getOpContext();
+ if (context && haveLocalShardingInfo(context->getClient(), fullName))
+ uasserted(ErrorCodes::BadValue, "can't use sharded collection from db.eval");
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/dbcollection.h b/src/mongo/scripting/mozjs/dbcollection.h
new file mode 100644
index 00000000000..34be422dfd4
--- /dev/null
+++ b/src/mongo/scripting/mozjs/dbcollection.h
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "DBCollection" Javascript object.
+ *
+ * This maps to the object you get after calling db.COLLECTION_NAME on the
+ * global 'db' object in the shell.
+ *
+ * Its major magic is in its getProperty() callback, which threads through to
+ * a getCollection method installed in js
+ */
+struct DBCollectionInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+ static void getProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue vp);
+
+ static const char* const className;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/dbpointer.cpp b/src/mongo/scripting/mozjs/dbpointer.cpp
new file mode 100644
index 00000000000..fbfb8282649
--- /dev/null
+++ b/src/mongo/scripting/mozjs/dbpointer.cpp
@@ -0,0 +1,66 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/dbpointer.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace mozjs {
+
+const char* const DBPointerInfo::className = "DBPointer";
+
+void DBPointerInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 2)
+ uasserted(ErrorCodes::BadValue, "DBPointer needs 2 arguments");
+
+ if (!args.get(0).isString())
+ uasserted(ErrorCodes::BadValue, "DBPointer 1st parameter must be a string");
+
+ if (!scope->getOidProto().instanceOf(args.get(1)))
+ uasserted(ErrorCodes::BadValue, "DBPointer 2nd parameter must be an ObjectId");
+
+ JS::RootedObject thisv(cx);
+ scope->getDbPointerProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ o.setValue("ns", args.get(0));
+ o.setValue("id", args.get(1));
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/dbpointer.h b/src/mongo/scripting/mozjs/dbpointer.h
new file mode 100644
index 00000000000..d521eda4681
--- /dev/null
+++ b/src/mongo/scripting/mozjs/dbpointer.h
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "DBPointer" Javascript Object
+ *
+ * These look like:
+ * {
+ * id : OID(),
+ * ns : String(),
+ * }
+ */
+struct DBPointerInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+
+ static const char* const className;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/dbquery.cpp b/src/mongo/scripting/mozjs/dbquery.cpp
new file mode 100644
index 00000000000..e9bddf4b30e
--- /dev/null
+++ b/src/mongo/scripting/mozjs/dbquery.cpp
@@ -0,0 +1,143 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/dbquery.h"
+
+#include "mongo/scripting/mozjs/idwrapper.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+
+namespace mongo {
+namespace mozjs {
+
+const char* const DBQueryInfo::className = "DBQuery";
+
+void DBQueryInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() < 4)
+ uasserted(ErrorCodes::BadValue, "dbQuery constructor requires at least 4 arguments");
+
+ JS::RootedObject thisv(cx);
+ scope->getDbQueryProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ o.setValue("_mongo", args.get(0));
+ o.setValue("_db", args.get(1));
+ o.setValue("_collection", args.get(2));
+ o.setValue("_ns", args.get(3));
+
+ JS::RootedObject emptyObj(cx);
+ JS::RootedValue emptyObjVal(cx);
+ emptyObjVal.setObjectOrNull(emptyObj);
+
+ JS::RootedValue nullVal(cx);
+ nullVal.setNull();
+
+ if (args.length() > 4 && args.get(4).isObject()) {
+ o.setValue("_query", args.get(4));
+ } else {
+ o.setValue("_query", emptyObjVal);
+ }
+
+ if (args.length() > 5 && args.get(5).isObject()) {
+ o.setValue("_fields", args.get(5));
+ } else {
+ o.setValue("_fields", nullVal);
+ }
+
+ if (args.length() > 6 && args.get(6).isNumber()) {
+ o.setValue("_limit", args.get(6));
+ } else {
+ o.setNumber("_limit", 0);
+ }
+
+ if (args.length() > 7 && args.get(7).isNumber()) {
+ o.setValue("_skip", args.get(7));
+ } else {
+ o.setNumber("_skip", 0);
+ }
+
+ if (args.length() > 8 && args.get(8).isNumber()) {
+ o.setValue("_batchSize", args.get(8));
+ } else {
+ o.setNumber("_batchSize", 0);
+ }
+
+ if (args.length() > 9 && args.get(9).isNumber()) {
+ o.setValue("_options", args.get(9));
+ } else {
+ o.setNumber("_options", 0);
+ }
+
+ o.setValue("_cursor", nullVal);
+ o.setNumber("_numReturned", 0);
+ o.setBoolean("_special", false);
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+void DBQueryInfo::getProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue vp) {
+ if (!vp.isUndefined()) {
+ return;
+ }
+
+ IdWrapper wid(cx, id);
+
+ // We only use this for index access
+ if (!wid.isInt()) {
+ return;
+ }
+
+ JS::RootedObject parent(cx);
+ if (!JS_GetPrototype(cx, obj, &parent))
+ uasserted(ErrorCodes::InternalError, "Couldn't get prototype");
+
+ ObjectWrapper parentWrapper(cx, parent);
+
+ JS::RootedValue arrayAccess(cx);
+ parentWrapper.getValue("arrayAccess", &arrayAccess);
+
+ if (arrayAccess.isObject() && JS_ObjectIsFunction(cx, arrayAccess.toObjectOrNull())) {
+ JS::AutoValueArray<1> args(cx);
+
+ args[0].setInt32(wid.toInt32());
+
+ ObjectWrapper(cx, obj).callMethod(arrayAccess, args, vp);
+ } else {
+ uasserted(ErrorCodes::BadValue, "arrayAccess is not a function");
+ }
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/dbquery.h b/src/mongo/scripting/mozjs/dbquery.h
new file mode 100644
index 00000000000..dc844c3084c
--- /dev/null
+++ b/src/mongo/scripting/mozjs/dbquery.h
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "DBQuery" Javascript object.
+ *
+ * This represents the result of a find() and uses its getProperty() callback
+ * to shim operator[]. I.e. db.test.find()[4]
+ */
+struct DBQueryInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+ static void getProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue vp);
+
+ static const char* const className;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/dbref.cpp b/src/mongo/scripting/mozjs/dbref.cpp
new file mode 100644
index 00000000000..16ad0058084
--- /dev/null
+++ b/src/mongo/scripting/mozjs/dbref.cpp
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/dbref.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+
+namespace mongo {
+namespace mozjs {
+
+const char* const DBRefInfo::className = "DBRef";
+
+void DBRefInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (!(args.length() == 2 || args.length() == 3))
+ uasserted(ErrorCodes::BadValue, "DBRef needs 2 or 3 arguments");
+
+ if (!args.get(0).isString())
+ uasserted(ErrorCodes::BadValue, "DBRef 1st parameter must be a string");
+
+ JS::RootedObject thisv(cx);
+ scope->getDbRefProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ o.setValue("$ref", args.get(0));
+ o.setValue("$id", args.get(1));
+
+ if (args.length() == 3) {
+ if (!args.get(2).isString())
+ uasserted(ErrorCodes::BadValue, "DBRef 3rd parameter must be a string");
+
+ o.setValue("$db", args.get(2));
+ }
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/dbref.h b/src/mongo/scripting/mozjs/dbref.h
new file mode 100644
index 00000000000..e556a61f765
--- /dev/null
+++ b/src/mongo/scripting/mozjs/dbref.h
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "DBRef" Javascript object.
+ *
+ * These look like:
+ * {
+ * $ref : String(),
+ * $id : Any,
+ * $db : String(),
+ * }
+ */
+struct DBRefInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+
+ static const char* const className;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/engine.cpp b/src/mongo/scripting/mozjs/engine.cpp
new file mode 100644
index 00000000000..15875da16e1
--- /dev/null
+++ b/src/mongo/scripting/mozjs/engine.cpp
@@ -0,0 +1,133 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/engine.h"
+
+#include "mongo/db/operation_context.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/proxyscope.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+void ScriptEngine::setup() {
+ if (!globalScriptEngine) {
+ globalScriptEngine = new mozjs::MozJSScriptEngine();
+
+ if (hasGlobalServiceContext()) {
+ getGlobalServiceContext()->registerKillOpListener(globalScriptEngine);
+ }
+ }
+}
+
+std::string ScriptEngine::getInterpreterVersionString() {
+ return "MozJS-38";
+}
+
+namespace mozjs {
+
+MozJSScriptEngine::MozJSScriptEngine() {
+ uassert(ErrorCodes::JSInterpreterFailure, "Failed to JS_Init()", JS_Init());
+}
+
+MozJSScriptEngine::~MozJSScriptEngine() {
+ JS_ShutDown();
+}
+
+mongo::Scope* MozJSScriptEngine::createScope() {
+ return new MozJSProxyScope(this);
+}
+
+void MozJSScriptEngine::interrupt(unsigned opId) {
+ stdx::lock_guard<stdx::mutex> intLock(_globalInterruptLock);
+ OpIdToScopeMap::iterator iScope = _opToScopeMap.find(opId);
+ if (iScope == _opToScopeMap.end()) {
+ // got interrupt request for a scope that no longer exists
+ LOG(1) << "received interrupt request for unknown op: " << opId << printKnownOps_inlock();
+ return;
+ }
+
+ LOG(1) << "interrupting op: " << opId << printKnownOps_inlock();
+ iScope->second->kill();
+}
+
+std::string MozJSScriptEngine::printKnownOps_inlock() {
+ str::stream out;
+
+ if (shouldLog(logger::LogSeverity::Debug(2))) {
+ out << " known ops: \n";
+
+ for (auto&& iSc : _opToScopeMap) {
+ out << " " << iSc.first << "\n";
+ }
+ }
+
+ return out;
+}
+
+void MozJSScriptEngine::interruptAll() {
+ stdx::lock_guard<stdx::mutex> interruptLock(_globalInterruptLock);
+
+ for (auto&& iScope : _opToScopeMap) {
+ iScope.second->kill();
+ }
+}
+
+void MozJSScriptEngine::registerOperation(OperationContext* txn, MozJSImplScope* scope) {
+ stdx::lock_guard<stdx::mutex> giLock(_globalInterruptLock);
+
+ auto opId = txn->getOpID();
+
+ _opToScopeMap[opId] = scope;
+
+ LOG(2) << "SMScope " << static_cast<const void*>(scope) << " registered for op " << opId;
+ Status status = txn->checkForInterruptNoAssert();
+ if (!status.isOK()) {
+ scope->kill();
+ }
+}
+
+void MozJSScriptEngine::unregisterOperation(unsigned int opId) {
+ stdx::lock_guard<stdx::mutex> giLock(_globalInterruptLock);
+
+ LOG(2) << "ImplScope " << static_cast<const void*>(this) << " unregistered for op " << opId;
+
+ if (opId != 0) {
+ // scope is currently associated with an operation id
+ auto it = _opToScopeMap.find(opId);
+ if (it != _opToScopeMap.end())
+ _opToScopeMap.erase(it);
+ }
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/engine.h b/src/mongo/scripting/mozjs/engine.h
new file mode 100644
index 00000000000..a337ef0bcdc
--- /dev/null
+++ b/src/mongo/scripting/mozjs/engine.h
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+#include <unordered_map>
+
+#include "mongo/scripting/deadline_monitor.h"
+#include "mongo/scripting/engine.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/util/concurrency/mutex.h"
+
+namespace mongo {
+namespace mozjs {
+
+class MozJSImplScope;
+
+/**
+ * Implements the global ScriptEngine interface for MozJS. The associated TU
+ * pulls this in for the polymorphic globalScriptEngine.
+ */
+class MozJSScriptEngine final : public mongo::ScriptEngine {
+public:
+ MozJSScriptEngine();
+ ~MozJSScriptEngine() override;
+
+ mongo::Scope* createScope() override;
+
+ void runTest() override {}
+
+ bool utf8Ok() const override {
+ return true;
+ }
+
+ void interrupt(unsigned opId) override;
+
+ void interruptAll() override;
+
+ void registerOperation(OperationContext* ctx, MozJSImplScope* scope);
+ void unregisterOperation(unsigned int opId);
+
+ using ScopeCallback = void (*)(Scope&);
+ ScopeCallback getScopeInitCallback() {
+ return _scopeInitCallback;
+ };
+
+ DeadlineMonitor<MozJSImplScope>& getDeadlineMonitor() {
+ return _deadlineMonitor;
+ }
+
+private:
+ std::string printKnownOps_inlock();
+
+ /**
+ * This mutex protects _opToScopeMap
+ */
+ stdx::mutex _globalInterruptLock;
+
+ using OpIdToScopeMap = std::unordered_map<unsigned, MozJSImplScope*>;
+ OpIdToScopeMap _opToScopeMap; // map of mongo op ids to scopes (protected by
+ // _globalInterruptLock).
+
+ DeadlineMonitor<MozJSImplScope> _deadlineMonitor;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/exception.cpp b/src/mongo/scripting/mozjs/exception.cpp
new file mode 100644
index 00000000000..7f7349acee1
--- /dev/null
+++ b/src/mongo/scripting/mozjs/exception.cpp
@@ -0,0 +1,89 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/exception.h"
+
+#include <jsfriendapi.h>
+
+#include "mongo/scripting/mozjs/jsstringwrapper.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+namespace mozjs {
+
+namespace {
+
+JSErrorFormatString kFormatString = {"{0}", 1, JSEXN_ERR};
+const JSErrorFormatString* errorCallback(void* data, const unsigned code) {
+ return &kFormatString;
+}
+
+} // namespace
+
+void mongoToJSException(JSContext* cx) {
+ auto status = exceptionToStatus();
+
+ JS_ReportErrorNumber(cx, errorCallback, nullptr, status.code(), status.reason().c_str());
+}
+
+void setJSException(JSContext* cx, ErrorCodes::Error code, StringData sd) {
+ JS_ReportErrorNumber(cx, errorCallback, nullptr, code, sd.rawData());
+}
+
+Status currentJSExceptionToStatus(JSContext* cx, ErrorCodes::Error altCode, StringData altReason) {
+ JS::RootedValue vp(cx);
+ if (!JS_GetPendingException(cx, &vp))
+ return Status(altCode, altReason.rawData());
+
+ JS::RootedObject obj(cx, vp.toObjectOrNull());
+ JSErrorReport* report = JS_ErrorFromException(cx, obj);
+ if (!report)
+ return Status(altCode, altReason.rawData());
+
+ JSStringWrapper jsstr(cx, js::ErrorReportToString(cx, report));
+ if (!jsstr)
+ return Status(altCode, altReason.rawData());
+
+ /**
+ * errorNumber is only set by library consumers of MozJS, and then only via
+ * JS_ReportErrorNumber, so all of the codes we see here are ours.
+ */
+ return Status(report->errorNumber ? static_cast<ErrorCodes::Error>(report->errorNumber)
+ : altCode,
+ jsstr.toStringData().rawData());
+}
+
+void throwCurrentJSException(JSContext* cx, ErrorCodes::Error altCode, StringData altReason) {
+ auto status = currentJSExceptionToStatus(cx, altCode, altReason);
+ uasserted(status.code(), status.reason());
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/exception.h b/src/mongo/scripting/mozjs/exception.h
new file mode 100644
index 00000000000..9ef48664db6
--- /dev/null
+++ b/src/mongo/scripting/mozjs/exception.h
@@ -0,0 +1,67 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+
+#include "mongo/base/error_codes.h"
+#include "mongo/base/string_data.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Turns a current C++ exception into a JS exception
+ */
+void mongoToJSException(JSContext* cx);
+
+/**
+ * Sets an exception for javascript
+ */
+void setJSException(JSContext* cx, ErrorCodes::Error code, StringData sd);
+
+/**
+ * Converts the current pending js expection into a status
+ *
+ * The altCode and altReason are used if no JS exception is pending
+ */
+Status currentJSExceptionToStatus(JSContext* cx, ErrorCodes::Error altCode, StringData altReason);
+
+/**
+ * Turns the current JS exception into a C++ exception
+ *
+ * The altCode and altReason are used if no JS exception is pending
+ */
+MONGO_COMPILER_NORETURN void throwCurrentJSException(JSContext* cx,
+ ErrorCodes::Error altCode,
+ StringData altReason);
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/global.cpp b/src/mongo/scripting/mozjs/global.cpp
new file mode 100644
index 00000000000..d8c6da94e35
--- /dev/null
+++ b/src/mongo/scripting/mozjs/global.cpp
@@ -0,0 +1,103 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/global.h"
+
+#include <js/Conversions.h>
+
+#include "mongo/base/init.h"
+#include "mongo/logger/logstream_builder.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/jsstringwrapper.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+
+namespace mongo {
+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(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(JSContext* cx, JS::CallArgs args) {
+ ValueReader(cx, args.rval()).fromStringData(JS_VersionToString(JS_GetVersion(cx)));
+}
+
+void GlobalInfo::Functions::gc(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 mongo
diff --git a/src/mongo/scripting/mozjs/global.h b/src/mongo/scripting/mozjs/global.h
new file mode 100644
index 00000000000..1da83350137
--- /dev/null
+++ b/src/mongo/scripting/mozjs/global.h
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The global object for all of our JS.
+ *
+ * This function is super special and it's properties are the globally visible
+ * symbol for JS execution.
+ */
+struct GlobalInfo : public BaseInfo {
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(gc);
+ MONGO_DEFINE_JS_FUNCTION(print);
+ MONGO_DEFINE_JS_FUNCTION(version);
+ };
+
+ static const JSFunctionSpec freeFunctions[4];
+
+ static const char* const className;
+ static const unsigned classFlags = JSCLASS_GLOBAL_FLAGS;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/idwrapper.cpp b/src/mongo/scripting/mozjs/idwrapper.cpp
new file mode 100644
index 00000000000..2c701037a22
--- /dev/null
+++ b/src/mongo/scripting/mozjs/idwrapper.cpp
@@ -0,0 +1,74 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/idwrapper.h"
+
+#include "mongo/base/error_codes.h"
+#include "mongo/scripting/mozjs/exception.h"
+#include "mongo/scripting/mozjs/jsstringwrapper.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+namespace mozjs {
+
+IdWrapper::IdWrapper(JSContext* cx, JS::HandleId value) : _context(cx), _value(cx, value) {}
+
+std::string IdWrapper::toString() const {
+ if (JSID_IS_STRING(_value)) {
+ return JSStringWrapper(_context, JSID_TO_STRING(_value)).toString();
+ } else if (JSID_IS_INT(_value)) {
+ return std::to_string(JSID_TO_INT(_value));
+ } else {
+ throwCurrentJSException(_context,
+ ErrorCodes::TypeMismatch,
+ "Cannot toString() non-string and non-integer jsid");
+ }
+}
+
+uint32_t IdWrapper::toInt32() const {
+ uassert(ErrorCodes::TypeMismatch, "Cannot toInt32() non-integer jsid", JSID_IS_INT(_value));
+
+ return JSID_TO_INT(_value);
+}
+
+bool IdWrapper::equals(StringData sd) const {
+ return sd.compare(toString()) == 0;
+}
+
+bool IdWrapper::isInt() const {
+ return JSID_IS_INT(_value);
+}
+
+bool IdWrapper::isString() const {
+ return JSID_IS_STRING(_value);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/idwrapper.h b/src/mongo/scripting/mozjs/idwrapper.h
new file mode 100644
index 00000000000..4865e5e0aec
--- /dev/null
+++ b/src/mongo/scripting/mozjs/idwrapper.h
@@ -0,0 +1,71 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+#include <string>
+
+#include "mongo/base/string_data.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Wraps jsid's to make them slightly easier to use
+ *
+ * As these own a JS::RootedId they're not movable or copyable
+ *
+ * IdWrapper should only be used on the stack, never in a heap allocation
+ */
+class IdWrapper {
+public:
+ IdWrapper(JSContext* cx, JS::HandleId id);
+
+ /**
+ * Converts to a string. This coerces for integers
+ */
+ std::string toString() const;
+
+ /**
+ * Converts to an int. This throws if the id is not an integer
+ */
+ uint32_t toInt32() const;
+
+ bool isString() const;
+ bool isInt() const;
+
+ bool equals(StringData sd) const;
+
+private:
+ JSContext* _context;
+ JS::RootedId _value;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp
new file mode 100644
index 00000000000..0d5d3675603
--- /dev/null
+++ b/src/mongo/scripting/mozjs/implscope.cpp
@@ -0,0 +1,728 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+
+#include <jscustomallocator.h>
+#include <jsfriendapi.h>
+
+#include "mongo/base/error_codes.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/util/concurrency/threadlocal.h"
+#include "mongo/util/log.h"
+
+using namespace mongoutils;
+
+namespace mongo {
+
+// Generated symbols for JS files
+namespace JSFiles {
+extern const JSFile types;
+extern const JSFile assert;
+} // namespace
+
+namespace mozjs {
+
+const char* const MozJSImplScope::kExecResult = "__lastres__";
+const char* const MozJSImplScope::kInvokeResult = "__returnValue";
+
+namespace {
+
+/**
+ * The maximum amount of memory to be given out per thread to mozilla. We
+ * manage this by trapping all calls to malloc, free, etc. and keeping track of
+ * counts in some thread locals
+ */
+const size_t kMallocMemoryLimit = 1024ul * 1024 * 1024 * 1.1;
+
+/**
+ * The number of bytes to allocate after which garbage collection is run
+ */
+const int kMaxBytesBeforeGC = 8 * 1024 * 1024;
+
+/**
+ * The size, in bytes, of each "stack chunk". 8192 is the recommended amount
+ * from mozilla
+ */
+const int kStackChunkSize = 8192;
+
+/**
+ * Runtime's can race on first creation (on some function statics), so we just
+ * serialize the initial Runtime creation.
+ */
+stdx::mutex gRuntimeCreationMutex;
+bool gFirstRuntimeCreated = false;
+
+} // namespace
+
+MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL MozJSImplScope* kCurrentScope;
+
+struct MozJSImplScope::MozJSEntry {
+ MozJSEntry(MozJSImplScope* scope) : ar(scope->_context), ac(scope->_context, scope->_global) {}
+
+ JSAutoRequest ar;
+ JSAutoCompartment ac;
+};
+
+void MozJSImplScope::_reportError(JSContext* cx, const char* message, JSErrorReport* report) {
+ auto scope = getScope(cx);
+
+ if (!JSREPORT_IS_WARNING(report->flags)) {
+ scope->_status =
+ Status(report->errorNumber ? static_cast<ErrorCodes::Error>(report->errorNumber)
+ : ErrorCodes::JSInterpreterFailure,
+ str::stream() << message << ":\n"
+ << JS::FormatStackDump(cx, nullptr, true, true, false) << "\n");
+ }
+}
+
+std::string MozJSImplScope::getError() {
+ return "";
+}
+
+void MozJSImplScope::registerOperation(OperationContext* txn) {
+ invariant(_opId == 0);
+ _opId = txn->getOpID();
+
+ _engine->registerOperation(txn, this);
+}
+
+void MozJSImplScope::unregisterOperation() {
+ if (_opId != 0) {
+ _engine->unregisterOperation(_opId);
+
+ _opId = 0;
+ }
+}
+
+void MozJSImplScope::kill() {
+ _pendingKill.store(true);
+ JS_RequestInterruptCallback(_runtime);
+}
+
+bool MozJSImplScope::isKillPending() const {
+ return _pendingKill.load();
+}
+
+OperationContext* MozJSImplScope::getOpContext() const {
+ return _opCtx;
+}
+
+bool MozJSImplScope::_interruptCallback(JSContext* cx) {
+ auto scope = getScope(cx);
+
+ if (scope->_pendingGC.load()) {
+ JS_GC(scope->_runtime);
+ }
+
+ bool kill = scope->isKillPending();
+
+ if (kill) {
+ scope->_engine->getDeadlineMonitor().stopDeadline(scope);
+ scope->unregisterOperation();
+ }
+
+ return !kill;
+}
+
+void MozJSImplScope::_gcCallback(JSRuntime* rt, JSGCStatus status, void* data) {
+ if (!shouldLog(logger::LogSeverity::Debug(1))) {
+ // don't collect stats unless verbose
+ return;
+ }
+
+ log() << "MozJS GC " << (status == JSGC_BEGIN ? "prologue" : "epilogue") << " heap stats - "
+ << " total: " << mongo::sm::get_total_bytes() << " limit: " << mongo::sm::get_max_bytes()
+ << std::endl;
+}
+
+MozJSImplScope::MozRuntime::MozRuntime() {
+ mongo::sm::reset(kMallocMemoryLimit);
+
+ {
+ stdx::unique_lock<stdx::mutex> lk(gRuntimeCreationMutex);
+
+ if (gFirstRuntimeCreated) {
+ // If we've already made a runtime, just proceed
+ lk.unlock();
+ } else {
+ // If this is the first one, hold the lock until after the first
+ // one's done
+ gFirstRuntimeCreated = true;
+ }
+
+ _runtime = JS_NewRuntime(kMaxBytesBeforeGC);
+ }
+
+ uassert(ErrorCodes::JSInterpreterFailure, "Failed to initialize JSRuntime", _runtime);
+
+ _context = JS_NewContext(_runtime, kStackChunkSize);
+ uassert(ErrorCodes::JSInterpreterFailure, "Failed to initialize JSContext", _context);
+}
+
+MozJSImplScope::MozRuntime::~MozRuntime() {
+ JS_DestroyContext(_context);
+ JS_DestroyRuntime(_runtime);
+}
+
+MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine)
+ : _engine(engine),
+ _mr(),
+ _runtime(_mr._runtime),
+ _context(_mr._context),
+ _globalProto(_context),
+ _global(_globalProto.getProto()),
+ _funcs(),
+ _pendingKill(false),
+ _opId(0),
+ _opCtx(nullptr),
+ _pendingGC(false),
+ _connectState(ConnectState::Not),
+ _status(Status::OK()),
+ _binDataProto(_context),
+ _bsonProto(_context),
+ _countDownLatchProto(_context),
+ _cursorProto(_context),
+ _dbCollectionProto(_context),
+ _dbPointerProto(_context),
+ _dbQueryProto(_context),
+ _dbProto(_context),
+ _dbRefProto(_context),
+ _jsThreadProto(_context),
+ _maxKeyProto(_context),
+ _minKeyProto(_context),
+ _mongoExternalProto(_context),
+ _mongoLocalProto(_context),
+ _nativeFunctionProto(_context),
+ _numberIntProto(_context),
+ _numberLongProto(_context),
+ _objectProto(_context),
+ _oidProto(_context),
+ _regExpProto(_context),
+ _timestampProto(_context) {
+ kCurrentScope = this;
+
+ // The default is quite low and doesn't seem to directly correlate with
+ // malloc'd bytes. Set it to MAX_INT here and catching things in the
+ // jscustomallocator.cpp
+ JS_SetGCParameter(_runtime, JSGC_MAX_BYTES, 0xffffffff);
+
+ JS_SetInterruptCallback(_runtime, _interruptCallback);
+ JS_SetGCCallback(_runtime, _gcCallback, this);
+ JS_SetContextPrivate(_context, this);
+ JSAutoRequest ar(_context);
+
+ JS_SetErrorReporter(_runtime, _reportError);
+
+ JSAutoCompartment ac(_context, _global);
+
+ _checkErrorState(JS_InitStandardClasses(_context, _global));
+
+ installBSONTypes();
+ execSetup(JSFiles::assert);
+ execSetup(JSFiles::types);
+
+ // install process-specific utilities in the global scope (dependancy: types.js, assert.js)
+ if (_engine->getScopeInitCallback())
+ _engine->getScopeInitCallback()(*this);
+
+ // install global utility functions
+ installGlobalUtils(*this);
+}
+
+MozJSImplScope::~MozJSImplScope() {
+ for (auto&& x : _funcs) {
+ x.reset();
+ }
+
+ unregisterOperation();
+}
+
+bool MozJSImplScope::hasOutOfMemoryException() {
+ return false;
+}
+
+void MozJSImplScope::init(const BSONObj* data) {
+ if (!data)
+ return;
+
+ BSONObjIterator i(*data);
+ while (i.more()) {
+ BSONElement e = i.next();
+ setElement(e.fieldName(), e);
+ }
+}
+
+void MozJSImplScope::setNumber(const char* field, double val) {
+ MozJSEntry entry(this);
+
+ ObjectWrapper(_context, _global).setNumber(field, val);
+}
+
+void MozJSImplScope::setString(const char* field, StringData val) {
+ MozJSEntry entry(this);
+
+ ObjectWrapper(_context, _global).setString(field, val);
+}
+
+void MozJSImplScope::setBoolean(const char* field, bool val) {
+ MozJSEntry entry(this);
+
+ ObjectWrapper(_context, _global).setBoolean(field, val);
+}
+
+void MozJSImplScope::setElement(const char* field, const BSONElement& e) {
+ MozJSEntry entry(this);
+
+ ObjectWrapper(_context, _global).setBSONElement(field, e, false);
+}
+
+void MozJSImplScope::setObject(const char* field, const BSONObj& obj, bool readOnly) {
+ MozJSEntry entry(this);
+
+ ObjectWrapper(_context, _global).setBSON(field, obj, readOnly);
+}
+
+int MozJSImplScope::type(const char* field) {
+ MozJSEntry entry(this);
+
+ return ObjectWrapper(_context, _global).type(field);
+}
+
+double MozJSImplScope::getNumber(const char* field) {
+ MozJSEntry entry(this);
+
+ return ObjectWrapper(_context, _global).getNumber(field);
+}
+
+int MozJSImplScope::getNumberInt(const char* field) {
+ MozJSEntry entry(this);
+
+ return ObjectWrapper(_context, _global).getNumberInt(field);
+}
+
+long long MozJSImplScope::getNumberLongLong(const char* field) {
+ MozJSEntry entry(this);
+
+ return ObjectWrapper(_context, _global).getNumberLongLong(field);
+}
+
+std::string MozJSImplScope::getString(const char* field) {
+ MozJSEntry entry(this);
+
+ return ObjectWrapper(_context, _global).getString(field);
+}
+
+bool MozJSImplScope::getBoolean(const char* field) {
+ MozJSEntry entry(this);
+
+ return ObjectWrapper(_context, _global).getBoolean(field);
+}
+
+BSONObj MozJSImplScope::getObject(const char* field) {
+ MozJSEntry entry(this);
+
+ return ObjectWrapper(_context, _global).getObject(field);
+}
+
+void MozJSImplScope::newFunction(StringData raw, JS::MutableHandleValue out) {
+ MozJSEntry entry(this);
+
+ std::string code = str::stream() << "____MongoToSM_newFunction_temp = " << raw;
+
+ JS::CompileOptions co(_context);
+ setCompileOptions(&co);
+ _checkErrorState(JS::Evaluate(_context, _global, co, code.c_str(), code.length(), out));
+}
+
+BSONObj MozJSImplScope::callThreadArgs(const BSONObj& args) {
+ MozJSEntry entry(this);
+
+ JS::RootedValue function(_context);
+ ValueReader(_context, &function).fromBSONElement(args.firstElement(), true);
+
+ int argc = args.nFields() - 1;
+
+ JS::AutoValueVector argv(_context);
+ BSONObjIterator it(args);
+ it.next();
+ JS::RootedValue value(_context);
+
+ for (int i = 0; i < argc; ++i) {
+ ValueReader(_context, &value).fromBSONElement(*it, true);
+ argv.append(value);
+ it.next();
+ }
+
+ JS::RootedValue out(_context);
+ JS::RootedObject thisv(_context);
+
+ bool success = JS::Call(_context, thisv, function, argv, &out);
+
+ if (!success) {
+ auto status = currentJSExceptionToStatus(
+ _context, ErrorCodes::JSInterpreterFailure, "Unknown callThread failure");
+
+ log() << "js thread raised js exception: " << status;
+
+ uasserted(status.code(), status.reason());
+ }
+
+ BSONObjBuilder b;
+ ValueWriter(_context, out).writeThis(&b, "ret");
+
+ return b.obj();
+}
+
+bool hasFunctionIdentifier(StringData code) {
+ if (code.size() < 9 || code.find("function") != 0)
+ return false;
+
+ return code[8] == ' ' || code[8] == '(';
+}
+
+// TODO: This function identification code is broken. Fix it up to be more robust
+//
+// See: SERVER-16703 for more info
+void MozJSImplScope::_MozJSCreateFunction(const char* raw,
+ ScriptingFunction functionNumber,
+ JS::MutableHandleValue fun) {
+ std::string code = jsSkipWhiteSpace(raw);
+ if (!hasFunctionIdentifier(code)) {
+ if (code.find('\n') == std::string::npos && !hasJSReturn(code) &&
+ (code.find(';') == std::string::npos || code.find(';') == code.size() - 1)) {
+ code = "return " + code;
+ }
+ code = "function(){ " + code + "}";
+ }
+
+ code = str::stream() << "_funcs" << functionNumber << " = " << code;
+
+ JS::CompileOptions co(_context);
+ setCompileOptions(&co);
+
+ _checkErrorState(JS::Evaluate(_context, _global, co, code.c_str(), code.length(), fun));
+ uassert(10232,
+ "not a function",
+ fun.isObject() && JS_ObjectIsFunction(_context, fun.toObjectOrNull()));
+}
+
+ScriptingFunction MozJSImplScope::_createFunction(const char* raw,
+ ScriptingFunction functionNumber) {
+ MozJSEntry entry(this);
+
+ JS::RootedValue fun(_context);
+ _MozJSCreateFunction(raw, functionNumber, &fun);
+ _funcs.emplace_back(_context, fun.get());
+
+ return functionNumber;
+}
+
+void MozJSImplScope::setFunction(const char* field, const char* code) {
+ MozJSEntry entry(this);
+
+ JS::RootedValue fun(_context);
+
+ _MozJSCreateFunction(code, getFunctionCache().size() + 1, &fun);
+
+ ObjectWrapper(_context, _global).setValue(field, fun);
+}
+
+void MozJSImplScope::rename(const char* from, const char* to) {
+ MozJSEntry entry(this);
+
+ ObjectWrapper(_context, _global).rename(from, to);
+}
+
+int MozJSImplScope::invoke(ScriptingFunction func,
+ const BSONObj* argsObject,
+ const BSONObj* recv,
+ int timeoutMs,
+ bool ignoreReturn,
+ bool readOnlyArgs,
+ bool readOnlyRecv) {
+ MozJSEntry entry(this);
+
+ auto funcValue = _funcs[func - 1];
+ JS::RootedValue result(_context);
+
+ const int nargs = argsObject ? argsObject->nFields() : 0;
+
+ JS::AutoValueVector args(_context);
+
+ if (nargs) {
+ BSONObjIterator it(*argsObject);
+ for (int i = 0; i < nargs; i++) {
+ BSONElement next = it.next();
+
+ JS::RootedValue value(_context);
+ ValueReader(_context, &value).fromBSONElement(next, readOnlyArgs);
+
+ args.append(value);
+ }
+ }
+
+ JS::RootedValue smrecv(_context);
+ if (recv)
+ ValueReader(_context, &smrecv).fromBSON(*recv, readOnlyRecv);
+ else
+ smrecv.setObjectOrNull(_global);
+
+ if (timeoutMs)
+ _engine->getDeadlineMonitor().startDeadline(this, timeoutMs);
+
+ JS::RootedValue out(_context);
+ JS::RootedObject obj(_context, smrecv.toObjectOrNull());
+
+ bool success = JS::Call(_context, obj, funcValue, args, &out);
+
+ if (timeoutMs)
+ _engine->getDeadlineMonitor().stopDeadline(this);
+
+ _checkErrorState(success);
+
+ if (!ignoreReturn) {
+ // must validate the handle because TerminateExecution may have
+ // been thrown after the above checks
+ if (out.isObject() && _nativeFunctionProto.instanceOf(out)) {
+ warning() << "storing native function as return value";
+ _lastRetIsNativeCode = true;
+ } else {
+ _lastRetIsNativeCode = false;
+ }
+
+ ObjectWrapper(_context, _global).setValue(kInvokeResult, out);
+ }
+
+ return 0;
+}
+
+bool MozJSImplScope::exec(StringData code,
+ const std::string& name,
+ bool printResult,
+ bool reportError,
+ bool assertOnError,
+ int timeoutMs) {
+ MozJSEntry entry(this);
+
+ JS::CompileOptions co(_context);
+ setCompileOptions(&co);
+ JS::RootedScript script(_context);
+
+ bool success = JS::Compile(_context, _global, co, code.rawData(), code.size(), &script);
+
+ if (_checkErrorState(success, reportError, assertOnError))
+ return false;
+
+ if (timeoutMs)
+ _engine->getDeadlineMonitor().startDeadline(this, timeoutMs);
+
+ JS::RootedValue out(_context);
+
+ success = JS_ExecuteScript(_context, _global, script, &out);
+
+ if (timeoutMs)
+ _engine->getDeadlineMonitor().stopDeadline(this);
+
+ if (_checkErrorState(success, reportError, assertOnError))
+ return false;
+
+ ObjectWrapper(_context, _global).setValue(kExecResult, out);
+
+ if (printResult && !out.isUndefined()) {
+ // TODO: We seem to use this productively in v8, but it seems
+ // unecessary under sm. That probably means somethings off
+ //
+ // appears to only be used by shell
+ // std::cout << ValueWriter(_context, out).toString() << std::endl;
+ }
+
+ return true;
+}
+
+void MozJSImplScope::injectNative(const char* field, NativeFunction func, void* data) {
+ MozJSEntry entry(this);
+
+ JS::RootedObject obj(_context);
+
+ NativeFunctionInfo::make(_context, &obj, func, data);
+
+ JS::RootedValue value(_context);
+ value.setObjectOrNull(obj);
+ ObjectWrapper(_context, _global).setValue(field, value);
+}
+
+void MozJSImplScope::gc() {
+ _pendingGC.store(true);
+ JS_RequestInterruptCallback(_runtime);
+}
+
+void MozJSImplScope::localConnectForDbEval(OperationContext* txn, const char* dbName) {
+ MozJSEntry entry(this);
+
+ invariant(_opCtx == NULL);
+ _opCtx = txn;
+
+ if (_connectState == ConnectState::External)
+ uasserted(12510, "externalSetup already called, can't call localConnect");
+ if (_connectState == ConnectState::Local) {
+ if (_localDBName == dbName)
+ return;
+ uasserted(12511,
+ str::stream() << "localConnect previously called with name " << _localDBName);
+ }
+
+ // NOTE: order is important here. the following methods must be called after
+ // the above conditional statements.
+
+ // install db access functions in the global object
+ installDBAccess();
+
+ // install the Mongo function object and instantiate the 'db' global
+ _mongoLocalProto.install(_global);
+ execCoreFiles();
+
+ const char* const makeMongo = "_mongo = new Mongo()";
+ exec(makeMongo, "local connect 2", false, true, true, 0);
+
+ std::string makeDB = str::stream() << "db = _mongo.getDB(\"" << dbName << "\");";
+ exec(makeDB, "local connect 3", false, true, true, 0);
+
+ _connectState = ConnectState::Local;
+ _localDBName = dbName;
+
+ loadStored(txn);
+}
+
+void MozJSImplScope::externalSetup() {
+ MozJSEntry entry(this);
+
+ if (_connectState == ConnectState::External)
+ return;
+ if (_connectState == ConnectState::Local)
+ uasserted(12512, "localConnect already called, can't call externalSetup");
+
+ mongo::sm::reset(0);
+
+ // install db access functions in the global object
+ installDBAccess();
+
+ // install thread-related functions (e.g. _threadInject)
+ installFork();
+
+ // install the Mongo function object
+ _mongoExternalProto.install(_global);
+ execCoreFiles();
+ _connectState = ConnectState::External;
+}
+
+void MozJSImplScope::reset() {
+ unregisterOperation();
+ _pendingKill.store(false);
+ _pendingGC.store(false);
+}
+
+void MozJSImplScope::installBSONTypes() {
+ _binDataProto.install(_global);
+ _bsonProto.install(_global);
+ _dbPointerProto.install(_global);
+ _dbRefProto.install(_global);
+ _maxKeyProto.install(_global);
+ _minKeyProto.install(_global);
+ _nativeFunctionProto.install(_global);
+ _numberIntProto.install(_global);
+ _numberLongProto.install(_global);
+ _objectProto.install(_global);
+ _oidProto.install(_global);
+ _regExpProto.install(_global);
+ _timestampProto.install(_global);
+
+ // This builtin map is a javascript 6 thing. We want our version. so
+ // take theirs out
+ ObjectWrapper(_context, _global).deleteProperty("Map");
+}
+
+void MozJSImplScope::installDBAccess() {
+ _cursorProto.install(_global);
+ _dbProto.install(_global);
+ _dbQueryProto.install(_global);
+ _dbCollectionProto.install(_global);
+}
+
+void MozJSImplScope::installFork() {
+ _countDownLatchProto.install(_global);
+ _jsThreadProto.install(_global);
+}
+
+bool MozJSImplScope::_checkErrorState(bool success, bool reportError, bool assertOnError) {
+ if (success)
+ return false;
+
+ if (_status.isOK()) {
+ _status = Status(ErrorCodes::UnknownError, "Unknown Failure from JSInterpreter");
+ }
+
+ _error = _status.reason();
+
+ if (reportError)
+ error() << _error << std::endl;
+
+ // Clear the status state
+ auto status = std::move(_status);
+
+ if (assertOnError) {
+ // Throw if necessary
+ uassertStatusOK(status);
+ }
+
+ return true;
+}
+
+
+void MozJSImplScope::setCompileOptions(JS::CompileOptions* co) {
+ co->setUTF8(true);
+}
+
+MozJSImplScope* MozJSImplScope::getThreadScope() {
+ return kCurrentScope;
+}
+
+void MozJSImplScope::setOOM() {
+ _status = Status(ErrorCodes::JSInterpreterFailure, "Out of memory");
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/implscope.h b/src/mongo/scripting/mozjs/implscope.h
new file mode 100644
index 00000000000..4bd29e76676
--- /dev/null
+++ b/src/mongo/scripting/mozjs/implscope.h
@@ -0,0 +1,323 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+
+#include "mongo/client/dbclientcursor.h"
+#include "mongo/scripting/mozjs/bindata.h"
+#include "mongo/scripting/mozjs/bson.h"
+#include "mongo/scripting/mozjs/countdownlatch.h"
+#include "mongo/scripting/mozjs/cursor.h"
+#include "mongo/scripting/mozjs/db.h"
+#include "mongo/scripting/mozjs/dbcollection.h"
+#include "mongo/scripting/mozjs/dbpointer.h"
+#include "mongo/scripting/mozjs/dbquery.h"
+#include "mongo/scripting/mozjs/dbref.h"
+#include "mongo/scripting/mozjs/engine.h"
+#include "mongo/scripting/mozjs/global.h"
+#include "mongo/scripting/mozjs/jsthread.h"
+#include "mongo/scripting/mozjs/maxkey.h"
+#include "mongo/scripting/mozjs/minkey.h"
+#include "mongo/scripting/mozjs/mongo.h"
+#include "mongo/scripting/mozjs/nativefunction.h"
+#include "mongo/scripting/mozjs/numberint.h"
+#include "mongo/scripting/mozjs/numberlong.h"
+#include "mongo/scripting/mozjs/object.h"
+#include "mongo/scripting/mozjs/oid.h"
+#include "mongo/scripting/mozjs/regexp.h"
+#include "mongo/scripting/mozjs/timestamp.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Implementation Scope for MozJS
+ *
+ * The Implementation scope holds the actual mozjs runtime and context objects,
+ * along with a number of global prototypes for mongoDB specific types. Each
+ * ImplScope requires it's own thread and cannot be accessed from any thread
+ * other than the one it was created on (this is a detail inherited from the
+ * JSRuntime). If you need a scope that can be accessed by different threads
+ * over the course of it's lifetime, see MozJSProxyScope
+ *
+ * For more information about overriden fields, see mongo::Scope
+ */
+class MozJSImplScope final : public Scope {
+ MONGO_DISALLOW_COPYING(MozJSImplScope);
+
+public:
+ explicit MozJSImplScope(MozJSScriptEngine* engine);
+ ~MozJSImplScope();
+
+ void init(const BSONObj* data) override;
+
+ void reset() override;
+
+ void kill();
+
+ bool isKillPending() const override;
+
+ OperationContext* getOpContext() const;
+
+ void registerOperation(OperationContext* txn) override;
+
+ void unregisterOperation() override;
+
+ void localConnectForDbEval(OperationContext* txn, const char* dbName) override;
+
+ void externalSetup() override;
+
+ std::string getError() override;
+
+ bool hasOutOfMemoryException() override;
+
+ void gc() override;
+
+ double getNumber(const char* field) override;
+ int getNumberInt(const char* field) override;
+ long long getNumberLongLong(const char* field) override;
+ std::string getString(const char* field) override;
+ bool getBoolean(const char* field) override;
+ BSONObj getObject(const char* field) override;
+
+ void setNumber(const char* field, double val) override;
+ void setString(const char* field, StringData val) override;
+ void setBoolean(const char* field, bool val) override;
+ void setElement(const char* field, const BSONElement& e) override;
+ void setObject(const char* field, const BSONObj& obj, bool readOnly) override;
+ void setFunction(const char* field, const char* code) override;
+
+ int type(const char* field) override;
+
+ void rename(const char* from, const char* to) override;
+
+ int invoke(ScriptingFunction func,
+ const BSONObj* args,
+ const BSONObj* recv,
+ int timeoutMs = 0,
+ bool ignoreReturn = false,
+ bool readOnlyArgs = false,
+ bool readOnlyRecv = false) override;
+
+ bool exec(StringData code,
+ const std::string& name,
+ bool printResult,
+ bool reportError,
+ bool assertOnError,
+ int timeoutMs) override;
+
+ void injectNative(const char* field, NativeFunction func, void* data = 0) override;
+
+ ScriptingFunction _createFunction(const char* code,
+ ScriptingFunction functionNumber = 0) override;
+
+ void newFunction(StringData code, JS::MutableHandleValue out);
+
+ BSONObj callThreadArgs(const BSONObj& obj);
+
+ WrapType<BinDataInfo>& getBinDataProto() {
+ return _binDataProto;
+ }
+
+ WrapType<BSONInfo>& getBsonProto() {
+ return _bsonProto;
+ }
+
+ WrapType<CountDownLatchInfo>& getCountDownLatchProto() {
+ return _countDownLatchProto;
+ }
+
+ WrapType<CursorInfo>& getCursorProto() {
+ return _cursorProto;
+ }
+
+ WrapType<DBCollectionInfo>& getDbCollectionProto() {
+ return _dbCollectionProto;
+ }
+
+ WrapType<DBPointerInfo>& getDbPointerProto() {
+ return _dbPointerProto;
+ }
+
+ WrapType<DBQueryInfo>& getDbQueryProto() {
+ return _dbQueryProto;
+ }
+
+ WrapType<DBInfo>& getDbProto() {
+ return _dbProto;
+ }
+
+ WrapType<DBRefInfo>& getDbRefProto() {
+ return _dbRefProto;
+ }
+
+ WrapType<JSThreadInfo>& getJSThreadProto() {
+ return _jsThreadProto;
+ }
+
+ WrapType<MaxKeyInfo>& getMaxKeyProto() {
+ return _maxKeyProto;
+ }
+
+ WrapType<MinKeyInfo>& getMinKeyProto() {
+ return _minKeyProto;
+ }
+
+ WrapType<MongoExternalInfo>& getMongoExternalProto() {
+ return _mongoExternalProto;
+ }
+
+ WrapType<MongoLocalInfo>& getMongoLocalProto() {
+ return _mongoLocalProto;
+ }
+
+ WrapType<NativeFunctionInfo>& getNativeFunctionProto() {
+ return _nativeFunctionProto;
+ }
+
+ WrapType<NumberIntInfo>& getNumberIntProto() {
+ return _numberIntProto;
+ }
+
+ WrapType<NumberLongInfo>& getNumberLongProto() {
+ return _numberLongProto;
+ }
+
+ WrapType<ObjectInfo>& getObjectProto() {
+ return _objectProto;
+ }
+
+ WrapType<OIDInfo>& getOidProto() {
+ return _oidProto;
+ }
+
+ WrapType<RegExpInfo>& getRegExpProto() {
+ return _regExpProto;
+ }
+
+ WrapType<TimestampInfo>& getTimestampProto() {
+ return _timestampProto;
+ }
+
+ static const char* const kExecResult;
+ static const char* const kInvokeResult;
+
+ static MozJSImplScope* getThreadScope();
+ void setOOM();
+
+private:
+ void _MozJSCreateFunction(const char* raw,
+ ScriptingFunction functionNumber,
+ JS::MutableHandleValue fun);
+
+ /**
+ * This structure exists exclusively to construct the runtime and context
+ * ahead of the various global prototypes in the ImplScope construction.
+ * Basically, we have to call some c apis on the way up and down and this
+ * takes care of that
+ */
+ struct MozRuntime {
+ public:
+ MozRuntime();
+ ~MozRuntime();
+
+ JSRuntime* _runtime;
+ JSContext* _context;
+ };
+
+ /**
+ * The connection state of the scope.
+ *
+ * This is for dbeval and the shell
+ */
+ enum class ConnectState : char {
+ Not,
+ Local,
+ External,
+ };
+
+ struct MozJSEntry;
+ friend struct MozJSEntry;
+
+ static void _reportError(JSContext* cx, const char* message, JSErrorReport* report);
+ static bool _interruptCallback(JSContext* cx);
+ static void _gcCallback(JSRuntime* rt, JSGCStatus status, void* data);
+ bool _checkErrorState(bool success, bool reportError = true, bool assertOnError = true);
+
+ void installDBAccess();
+ void installBSONTypes();
+ void installFork();
+
+ void setCompileOptions(JS::CompileOptions* co);
+
+ MozJSScriptEngine* _engine;
+ MozRuntime _mr;
+ JSRuntime* _runtime;
+ JSContext* _context;
+ WrapType<GlobalInfo> _globalProto;
+ JS::HandleObject _global;
+ std::vector<JS::PersistentRootedValue> _funcs;
+ std::atomic<bool> _pendingKill;
+ std::string _error;
+ unsigned int _opId; // op id for this scope
+ OperationContext* _opCtx; // Op context for DbEval
+ std::atomic<bool> _pendingGC;
+ ConnectState _connectState;
+ Status _status;
+
+ WrapType<BinDataInfo> _binDataProto;
+ WrapType<BSONInfo> _bsonProto;
+ WrapType<CountDownLatchInfo> _countDownLatchProto;
+ WrapType<CursorInfo> _cursorProto;
+ WrapType<DBCollectionInfo> _dbCollectionProto;
+ WrapType<DBPointerInfo> _dbPointerProto;
+ WrapType<DBQueryInfo> _dbQueryProto;
+ WrapType<DBInfo> _dbProto;
+ WrapType<DBRefInfo> _dbRefProto;
+ WrapType<JSThreadInfo> _jsThreadProto;
+ WrapType<MaxKeyInfo> _maxKeyProto;
+ WrapType<MinKeyInfo> _minKeyProto;
+ WrapType<MongoExternalInfo> _mongoExternalProto;
+ WrapType<MongoLocalInfo> _mongoLocalProto;
+ WrapType<NativeFunctionInfo> _nativeFunctionProto;
+ WrapType<NumberIntInfo> _numberIntProto;
+ WrapType<NumberLongInfo> _numberLongProto;
+ WrapType<ObjectInfo> _objectProto;
+ WrapType<OIDInfo> _oidProto;
+ WrapType<RegExpInfo> _regExpProto;
+ WrapType<TimestampInfo> _timestampProto;
+};
+
+inline MozJSImplScope* getScope(JSContext* cx) {
+ return static_cast<MozJSImplScope*>(JS_GetContextPrivate(cx));
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/jscustomallocator.cpp b/src/mongo/scripting/mozjs/jscustomallocator.cpp
new file mode 100644
index 00000000000..adba220129d
--- /dev/null
+++ b/src/mongo/scripting/mozjs/jscustomallocator.cpp
@@ -0,0 +1,234 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include <cstddef>
+#include <type_traits>
+#include <jscustomallocator.h>
+
+#include "mongo/config.h"
+#include "mongo/util/concurrency/threadlocal.h"
+#include "mongo/scripting/mozjs/implscope.h"
+
+#ifdef __linux__
+#include <malloc.h>
+#elif defined(__APPLE__)
+#include <malloc/malloc.h>
+#elif defined(_WIN32)
+#include <malloc.h>
+#else
+#define MONGO_NO_MALLOC_USABLE_SIZE
+#endif
+
+/**
+ * This shim interface (which controls dynamic allocation within SpiderMonkey),
+ * consciously uses std::malloc and friends over mongoMalloc. It does this
+ * because SpiderMonkey has some plausible options in the event of OOM,
+ * specifically it can begin aggressive garbage collection. It would also be
+ * reasonable to go the other route and fail, but for the moment I erred on the
+ * side of maintaining the contract that SpiderMonkey expects.
+ *
+ * The overall strategy here is to keep track of allocations in a thread local,
+ * offering us the chance to enforce soft limits on memory use rather than
+ * waiting for the OS to OOM us.
+ */
+
+namespace mongo {
+namespace sm {
+
+namespace {
+/**
+ * These two variables track the total number of bytes handed out, and the
+ * maximum number of bytes we will consider handing out. They are set by
+ * MozJSImplScope on start up.
+ */
+MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL size_t total_bytes;
+MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL size_t max_bytes;
+
+/**
+ * When we don't have malloc_usable_size, we manage by adjusting our pointer by
+ * kMaxAlign bytes and storing the size of the allocation kMaxAlign bytes
+ * behind the pointer we hand back. That let's us get to the value at runtime.
+ * We know kMaxAlign is enough (generally 8 or 16 bytes), because that's
+ * literally the contract between malloc and std::max_align_t.
+ *
+ * This is commented out right now because std::max_align_t didn't seem to be
+ * available on our solaris builder. TODO: revisit in the future to see if that
+ * still holds.
+ */
+// const size_t kMaxAlign = std::alignment_of<std::max_align_t>::value;
+const size_t kMaxAlign = 16;
+} // namespace
+
+size_t get_total_bytes() {
+ return total_bytes;
+}
+
+void reset(size_t bytes) {
+ total_bytes = 0;
+ max_bytes = bytes;
+}
+
+size_t get_max_bytes() {
+ return max_bytes;
+}
+
+/**
+ * Wraps std::Xalloc functions
+ *
+ * The idea here is to abstract soft limits on allocations, as well as possibly
+ * necessary pointer adjustment (if we don't have a malloc_usable_size
+ * replacement).
+ *
+ */
+template <typename T>
+void* wrap_alloc(T&& func, void* ptr, size_t bytes) {
+ size_t mb = get_max_bytes();
+ size_t tb = get_total_bytes();
+
+ if (mb && (tb + bytes > mb)) {
+ auto scope = mongo::mozjs::MozJSImplScope::getThreadScope();
+ invariant(scope);
+
+ scope->setOOM();
+
+ return nullptr;
+ }
+
+#ifdef MONGO_NO_MALLOC_USABLE_SIZE
+ void* p = func(ptr ? static_cast<char*>(ptr) - kMaxAlign : nullptr, bytes + kMaxAlign);
+#else
+ void* p = func(ptr, bytes);
+#endif
+
+ if (!p) {
+ return nullptr;
+ }
+
+#ifdef MONGO_NO_MALLOC_USABLE_SIZE
+ *reinterpret_cast<size_t*>(p) = bytes;
+ p = static_cast<char*>(p) + kMaxAlign;
+#endif
+
+ total_bytes = tb + bytes;
+
+ return p;
+}
+
+size_t get_current(void* ptr) {
+#ifdef MONGO_NO_MALLOC_USABLE_SIZE
+ if (!ptr)
+ return 0;
+
+ return *reinterpret_cast<size_t*>(static_cast<char*>(ptr) - kMaxAlign);
+#elif defined(__linux__)
+ return malloc_usable_size(ptr);
+#elif defined(__APPLE__)
+ return malloc_size(ptr);
+#elif defined(_WIN32)
+ return _msize(ptr);
+#else
+#error "Should be unreachable"
+#endif
+}
+
+} // namespace sm
+} // namespace mongo
+
+void* js_malloc(size_t bytes) {
+ return mongo::sm::wrap_alloc(
+ [](void* ptr, size_t b) { return std::malloc(b); }, nullptr, bytes);
+}
+
+void* js_calloc(size_t bytes) {
+ return mongo::sm::wrap_alloc(
+ [](void* ptr, size_t b) { return std::calloc(b, 1); }, nullptr, bytes);
+}
+
+void* js_calloc(size_t nmemb, size_t size) {
+ return mongo::sm::wrap_alloc(
+ [](void* ptr, size_t b) { return std::calloc(b, 1); }, nullptr, nmemb * size);
+}
+
+void js_free(void* p) {
+ if (!p)
+ return;
+
+ size_t current = mongo::sm::get_current(p);
+ size_t tb = mongo::sm::get_total_bytes();
+
+ if (tb >= current) {
+ mongo::sm::total_bytes = tb - current;
+ }
+
+ mongo::sm::wrap_alloc([](void* ptr, size_t b) {
+ std::free(ptr);
+ return nullptr;
+ }, p, 0);
+}
+
+void* js_realloc(void* p, size_t bytes) {
+ if (!p) {
+ return js_malloc(bytes);
+ }
+
+ if (!bytes) {
+ js_free(p);
+ return nullptr;
+ }
+
+ size_t current = mongo::sm::get_current(p);
+
+ if (current >= bytes) {
+ return p;
+ }
+
+ size_t tb = mongo::sm::total_bytes;
+
+ if (tb >= current) {
+ mongo::sm::total_bytes = tb - current;
+ }
+
+ return mongo::sm::wrap_alloc(
+ [](void* ptr, size_t b) { return std::realloc(ptr, b); }, p, bytes);
+}
+
+char* js_strdup(const char* s) {
+ size_t bytes = std::strlen(s) + 1;
+
+ char* new_s = static_cast<char*>(js_malloc(bytes));
+
+ if (!new_s) {
+ return nullptr;
+ }
+
+ std::memcpy(new_s, s, bytes);
+
+ return new_s;
+}
diff --git a/src/mongo/scripting/mozjs/jsstringwrapper.cpp b/src/mongo/scripting/mozjs/jsstringwrapper.cpp
new file mode 100644
index 00000000000..8261b44a2fe
--- /dev/null
+++ b/src/mongo/scripting/mozjs/jsstringwrapper.cpp
@@ -0,0 +1,84 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/jsstringwrapper.h"
+
+#include <js/CharacterEncoding.h>
+#include <jsapi.h>
+#include <utility>
+
+#include "mongo/base/error_codes.h"
+#include "mongo/scripting/mozjs/exception.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+namespace mozjs {
+
+JSStringWrapper::JSStringWrapper(JSContext* cx, JSString* str) : _context(cx) {
+ if (!str)
+ throwCurrentJSException(cx, ErrorCodes::InternalError, "Cannot encode null JSString");
+
+ // We have to do this flatstring thing because no public api tells us
+ // how long the utf8 strings we get out are.
+ //
+ // Well, at least js/CharacterEncoding's GetDeflatedUTF8StringLength
+ // and JS_flattenString are all in the public headers...
+ JSFlatString* flat = JS_FlattenString(cx, str);
+ if (!flat)
+ throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to flatten JSString");
+
+ _length = JS::GetDeflatedUTF8StringLength(flat);
+
+ JS::RootedString rstr(cx, str);
+
+ JSAutoByteString abs;
+ abs.encodeUtf8(cx, rstr);
+
+ if (!abs)
+ throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to encode JSString");
+
+ _str.reset(new char[_length]);
+ std::memcpy(_str.get(), abs.ptr(), _length);
+}
+
+StringData JSStringWrapper::toStringData() const {
+ return StringData(_str.get(), _length);
+}
+
+std::string JSStringWrapper::toString() const {
+ return toStringData().toString();
+}
+
+JSStringWrapper::operator bool() const {
+ return _str.get();
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/jsstringwrapper.h b/src/mongo/scripting/mozjs/jsstringwrapper.h
new file mode 100644
index 00000000000..9a0ee21e900
--- /dev/null
+++ b/src/mongo/scripting/mozjs/jsstringwrapper.h
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+#include <memory>
+#include <string>
+
+#include "mongo/base/string_data.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Wraps JSStrings to simplify coercing them to and from C++ style StringData
+ * and std::strings.
+ */
+class JSStringWrapper {
+public:
+ JSStringWrapper() = default;
+ JSStringWrapper(JSContext* cx, JSString* str);
+
+ StringData toStringData() const;
+ std::string toString() const;
+
+ explicit operator bool() const;
+
+private:
+ JSContext* _context = nullptr;
+ std::unique_ptr<char[]> _str;
+ size_t _length = 0;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/jsthread.cpp b/src/mongo/scripting/mozjs/jsthread.cpp
new file mode 100644
index 00000000000..24b4c5d88af
--- /dev/null
+++ b/src/mongo/scripting/mozjs/jsthread.cpp
@@ -0,0 +1,274 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/jsthread.h"
+
+#include <cstdio>
+
+#include "mongo/db/jsobj.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/stdx/condition_variable.h"
+#include "mongo/stdx/memory.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/stdx/thread.h"
+#include "mongo/util/log.h"
+#include "mongo/util/stacktrace.h"
+
+namespace mongo {
+namespace mozjs {
+
+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()) {
+ 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();
+ }
+
+ 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 two members aren't protected in any way, so you have to be
+ * mindful about how they're used. I.e. _args needs to be set before
+ * start() and _returnData can't be touched until after join().
+ */
+ BSONObj _args;
+ BSONObj _returnData;
+
+ 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));
+
+ _sharedData->_returnData = scope.callThreadArgs(_sharedData->_args);
+ } catch (...) {
+ auto status = exceptionToStatus();
+
+ log() << "js thread threw c++ exception: " << status;
+ _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::InternalError, "_JSThreadConfig not an object");
+
+ 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(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ JS::RootedObject obj(cx);
+ scope->getJSThreadProto().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(JSContext* cx, JS::CallArgs args) {
+ getConfig(cx, args)->start();
+
+ args.rval().setUndefined();
+}
+
+void JSThreadInfo::Functions::join(JSContext* cx, JS::CallArgs args) {
+ getConfig(cx, args)->join();
+
+ args.rval().setUndefined();
+}
+
+void JSThreadInfo::Functions::hasFailed(JSContext* cx, JS::CallArgs args) {
+ args.rval().setBoolean(getConfig(cx, args)->hasFailed());
+}
+
+void JSThreadInfo::Functions::returnData(JSContext* cx, JS::CallArgs args) {
+ ValueReader(cx, args.rval())
+ .fromBSONElement(getConfig(cx, args)->returnData().firstElement(), true);
+}
+
+void JSThreadInfo::Functions::_threadInject(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(JSContext* cx, JS::CallArgs args) {
+ _threadInject(cx, args);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/jsthread.h b/src/mongo/scripting/mozjs/jsthread.h
new file mode 100644
index 00000000000..ff468d987ab
--- /dev/null
+++ b/src/mongo/scripting/mozjs/jsthread.h
@@ -0,0 +1,74 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Helper for the JSThread javascript object
+ *
+ * The workflow is strange because we have a thing in javascript called a
+ * JSThread, but we don't actually get to construct it. Instead, we have to
+ * inject methods into that thing (via _threadInject) and hang our C++ thread
+ * separately (via init() on that type).
+ *
+ * To manage lifetime, we just add a field into the injected object that's our
+ * JSThread and add our holder in as our JSThread's private member.
+ */
+struct JSThreadInfo : public BaseInfo {
+ static void finalize(JSFreeOp* fop, JSObject* obj);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(init);
+ MONGO_DEFINE_JS_FUNCTION(start);
+ MONGO_DEFINE_JS_FUNCTION(join);
+ MONGO_DEFINE_JS_FUNCTION(hasFailed);
+ MONGO_DEFINE_JS_FUNCTION(returnData);
+
+ MONGO_DEFINE_JS_FUNCTION(_threadInject);
+ MONGO_DEFINE_JS_FUNCTION(_scopedThreadInject);
+ };
+
+ /**
+ * Note that this isn't meant to supply methods for JSThread, it's just
+ * there to work with _threadInject. So the name isn't a mistake
+ */
+ static const JSFunctionSpec threadMethods[6];
+ static const JSFunctionSpec freeFunctions[3];
+
+ static const char* const className;
+ static const unsigned classFlags = JSCLASS_HAS_PRIVATE;
+ static const InstallType installType = InstallType::Private;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/maxkey.cpp b/src/mongo/scripting/mozjs/maxkey.cpp
new file mode 100644
index 00000000000..022ce347773
--- /dev/null
+++ b/src/mongo/scripting/mozjs/maxkey.cpp
@@ -0,0 +1,94 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/maxkey.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec MaxKeyInfo::methods[2] = {
+ MONGO_ATTACH_JS_FUNCTION(tojson), JS_FS_END,
+};
+
+const char* const MaxKeyInfo::className = "MaxKey";
+
+namespace {
+const char* const kSingleton = "singleton";
+} // namespace
+
+void MaxKeyInfo::construct(JSContext* cx, JS::CallArgs args) {
+ call(cx, args);
+}
+
+/**
+ * The idea here is that MinKey and MaxKey are singleton callable objects that
+ * return the singleton when called. This enables all instances to compare
+ * == and === to MinKey even if created by "new MinKey()" in JS.
+ */
+void MaxKeyInfo::call(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ ObjectWrapper o(cx, scope->getMaxKeyProto().getProto());
+
+ JS::RootedValue val(cx);
+
+ if (!o.hasField(kSingleton)) {
+ JS::RootedObject thisv(cx);
+ scope->getMaxKeyProto().newObject(&thisv);
+
+ val.setObjectOrNull(thisv);
+ o.setValue(kSingleton, val);
+ } else {
+ o.getValue(kSingleton, &val);
+ }
+
+ args.rval().setObjectOrNull(val.toObjectOrNull());
+}
+
+void MaxKeyInfo::Functions::tojson(JSContext* cx, JS::CallArgs args) {
+ ValueReader(cx, args.rval()).fromStringData("{ \"$maxKey\" : 1 }");
+}
+
+void MaxKeyInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
+ ObjectWrapper protoWrapper(cx, proto);
+
+ JS::RootedValue value(cx);
+ getScope(cx)->getMaxKeyProto().newObject(&value);
+
+ ObjectWrapper(cx, global).setValue("MaxKey", value);
+ protoWrapper.setValue(kSingleton, value);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/maxkey.h b/src/mongo/scripting/mozjs/maxkey.h
new file mode 100644
index 00000000000..ac5d937f157
--- /dev/null
+++ b/src/mongo/scripting/mozjs/maxkey.h
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "MaxKey" Javascript object.
+ *
+ * These are slightly special, in that there is only one MaxKey object and
+ * whenever you call the constructor to make a new one you just get the
+ * "singleton" MaxKey from the prototype. See the postInstall for details.
+ */
+struct MaxKeyInfo : public BaseInfo {
+ static void call(JSContext* cx, JS::CallArgs args);
+ static void construct(JSContext* cx, JS::CallArgs args);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(tojson);
+ };
+
+ static const JSFunctionSpec methods[2];
+
+ static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto);
+
+ static const char* const className;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/minkey.cpp b/src/mongo/scripting/mozjs/minkey.cpp
new file mode 100644
index 00000000000..fe06fa6bb49
--- /dev/null
+++ b/src/mongo/scripting/mozjs/minkey.cpp
@@ -0,0 +1,94 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/minkey.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec MinKeyInfo::methods[2] = {
+ MONGO_ATTACH_JS_FUNCTION(tojson), JS_FS_END,
+};
+
+const char* const MinKeyInfo::className = "MinKey";
+
+namespace {
+const char* const kSingleton = "singleton";
+} // namespace
+
+void MinKeyInfo::construct(JSContext* cx, JS::CallArgs args) {
+ call(cx, args);
+}
+
+/**
+ * The idea here is that MinKey and MaxKey are singleton callable objects that
+ * return the singleton when called. This enables all instances to compare
+ * == and === to MinKey even if created by "new MinKey()" in JS.
+ */
+void MinKeyInfo::call(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ ObjectWrapper o(cx, scope->getMinKeyProto().getProto());
+
+ JS::RootedValue val(cx);
+
+ if (!o.hasField(kSingleton)) {
+ JS::RootedObject thisv(cx);
+ scope->getMinKeyProto().newObject(&thisv);
+
+ val.setObjectOrNull(thisv);
+ o.setValue(kSingleton, val);
+ } else {
+ o.getValue(kSingleton, &val);
+ }
+
+ args.rval().setObjectOrNull(val.toObjectOrNull());
+}
+
+void MinKeyInfo::Functions::tojson(JSContext* cx, JS::CallArgs args) {
+ ValueReader(cx, args.rval()).fromStringData("{ \"$minKey\" : 1 }");
+}
+
+void MinKeyInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
+ ObjectWrapper protoWrapper(cx, proto);
+
+ JS::RootedValue value(cx);
+ getScope(cx)->getMinKeyProto().newObject(&value);
+
+ ObjectWrapper(cx, global).setValue("MinKey", value);
+ protoWrapper.setValue(kSingleton, value);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/minkey.h b/src/mongo/scripting/mozjs/minkey.h
new file mode 100644
index 00000000000..caea91d54ac
--- /dev/null
+++ b/src/mongo/scripting/mozjs/minkey.h
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "MinKey" Javascript object.
+ *
+ * These are slightly special, in that there is only one MinKey object and
+ * whenever you call the constructor to make a new one you just get the
+ * "singleton" MinKey from the prototype. See the postInstall for details.
+ */
+struct MinKeyInfo : public BaseInfo {
+ static void call(JSContext* cx, JS::CallArgs args);
+ static void construct(JSContext* cx, JS::CallArgs args);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(tojson);
+ };
+
+ static const JSFunctionSpec methods[2];
+
+ static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto);
+
+ static const char* const className;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp
new file mode 100644
index 00000000000..f1f69d0d39c
--- /dev/null
+++ b/src/mongo/scripting/mozjs/mongo.cpp
@@ -0,0 +1,565 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/mongo.h"
+
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/client/native_sasl_client_session.h"
+#include "mongo/client/sasl_client_authenticate.h"
+#include "mongo/client/sasl_client_session.h"
+#include "mongo/db/namespace_string.h"
+#include "mongo/scripting/mozjs/cursor.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec MongoBase::methods[13] = {
+ MONGO_ATTACH_JS_FUNCTION(auth),
+ MONGO_ATTACH_JS_FUNCTION(copyDatabaseWithSCRAM),
+ MONGO_ATTACH_JS_FUNCTION(cursorFromId),
+ MONGO_ATTACH_JS_FUNCTION(find),
+ MONGO_ATTACH_JS_FUNCTION(getClientRPCProtocols),
+ MONGO_ATTACH_JS_FUNCTION(getServerRPCProtocols),
+ MONGO_ATTACH_JS_FUNCTION(insert),
+ MONGO_ATTACH_JS_FUNCTION(logout),
+ MONGO_ATTACH_JS_FUNCTION(remove),
+ MONGO_ATTACH_JS_FUNCTION(runCommand),
+ MONGO_ATTACH_JS_FUNCTION(setClientRPCProtocols),
+ MONGO_ATTACH_JS_FUNCTION(update),
+ JS_FS_END,
+};
+
+const char* const MongoBase::className = "Mongo";
+
+const JSFunctionSpec MongoExternalInfo::freeFunctions[2] = {
+ MONGO_ATTACH_JS_FUNCTION(load), JS_FS_END,
+};
+
+namespace {
+DBClientBase* getConnection(JS::CallArgs& args) {
+ return static_cast<std::shared_ptr<DBClientBase>*>(JS_GetPrivate(args.thisv().toObjectOrNull()))
+ ->get();
+}
+
+void setCursor(JS::HandleObject target,
+ std::unique_ptr<DBClientCursor> cursor,
+ JS::CallArgs& args) {
+ auto client =
+ static_cast<std::shared_ptr<DBClientBase>*>(JS_GetPrivate(args.thisv().toObjectOrNull()));
+
+ // Copy the client shared pointer to up the refcount
+ JS_SetPrivate(target, new CursorInfo::CursorHolder(std::move(cursor), *client));
+}
+} // namespace
+
+void MongoBase::finalize(JSFreeOp* fop, JSObject* obj) {
+ auto conn = static_cast<std::shared_ptr<DBClientBase>*>(JS_GetPrivate(obj));
+
+ if (conn) {
+ delete conn;
+ }
+}
+
+void MongoBase::Functions::runCommand(JSContext* cx, JS::CallArgs args) {
+ if (args.length() != 3)
+ uasserted(ErrorCodes::BadValue, "runCommand needs 3 args");
+
+ if (!args.get(0).isString())
+ uasserted(ErrorCodes::BadValue, "the database parameter to runCommand must be a string");
+
+ if (!args.get(1).isObject())
+ uasserted(ErrorCodes::BadValue, "the cmdObj parameter to runCommand must be an object");
+
+ if (!args.get(2).isNumber())
+ uasserted(ErrorCodes::BadValue, "the options parameter to runCommand must be a number");
+
+ auto conn = getConnection(args);
+
+ std::string database = ValueWriter(cx, args.get(0)).toString();
+
+ BSONObj cmdObj = ValueWriter(cx, args.get(1)).toBSON();
+
+ int queryOptions = ValueWriter(cx, args.get(2)).toInt32();
+ BSONObj cmdRes;
+ conn->runCommand(database, cmdObj, cmdRes, queryOptions);
+
+ // the returned object is not read only as some of our tests depend on modifying it.
+ ValueReader(cx, args.rval()).fromBSON(cmdRes, false /* read only */);
+}
+
+void MongoBase::Functions::find(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 7)
+ uasserted(ErrorCodes::BadValue, "find needs 7 args");
+
+ if (!args.get(1).isObject())
+ uasserted(ErrorCodes::BadValue, "needs to be an object");
+
+ auto conn = getConnection(args);
+
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ BSONObj fields;
+ BSONObj q = ValueWriter(cx, args.get(1)).toBSON();
+
+ bool haveFields = false;
+
+ if (args.get(2).isObject()) {
+ size_t i = 0;
+
+ JS::RootedObject obj(cx, args.get(2).toObjectOrNull());
+
+ ObjectWrapper(cx, obj).enumerate([&i](jsid) { ++i; });
+
+ if (i > 0)
+ haveFields = true;
+ }
+
+ if (haveFields)
+ fields = ValueWriter(cx, args.get(2)).toBSON();
+
+ int nToReturn = ValueWriter(cx, args.get(3)).toInt32();
+ int nToSkip = ValueWriter(cx, args.get(4)).toInt32();
+ int batchSize = ValueWriter(cx, args.get(5)).toInt32();
+ int options = ValueWriter(cx, args.get(6)).toInt32();
+
+ std::unique_ptr<DBClientCursor> cursor(
+ conn->query(ns, q, nToReturn, nToSkip, haveFields ? &fields : NULL, options, batchSize));
+ if (!cursor.get()) {
+ uasserted(ErrorCodes::InternalError, "error doing query: failed");
+ }
+
+ JS::RootedObject c(cx);
+ scope->getCursorProto().newInstance(&c);
+
+ setCursor(c, std::move(cursor), args);
+
+ args.rval().setObjectOrNull(c);
+}
+
+void MongoBase::Functions::insert(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 3)
+ uasserted(ErrorCodes::BadValue, "insert needs 3 args");
+
+ if (!args.get(1).isObject())
+ uasserted(ErrorCodes::BadValue, "attempted to insert a non-object");
+
+ ObjectWrapper o(cx, args.thisv());
+
+ if (o.hasField("readOnly") && o.getBoolean("readOnly"))
+ uasserted(ErrorCodes::BadValue, "js db in read only mode");
+
+ auto conn = getConnection(args);
+
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ int flags = ValueWriter(cx, args.get(2)).toInt32();
+
+ auto addId = [cx, scope](JS::HandleValue value) {
+ if (!value.isObject())
+ uasserted(ErrorCodes::BadValue, "attempted to insert a non-object type");
+
+ JS::RootedObject elementObj(cx, value.toObjectOrNull());
+
+ ObjectWrapper ele(cx, elementObj);
+
+ if (!ele.hasField("_id")) {
+ JS::RootedValue value(cx);
+ scope->getOidProto().newInstance(&value);
+ ele.setValue("_id", value);
+ }
+
+ return ValueWriter(cx, value).toBSON();
+ };
+
+ if (args.get(1).isObject() && JS_IsArrayObject(cx, args.get(1))) {
+ JS::RootedObject obj(cx, args.get(1).toObjectOrNull());
+ ObjectWrapper array(cx, obj);
+
+ std::vector<BSONObj> bos;
+
+ bool foundElement = false;
+
+ array.enumerate([&](JS::HandleId id) {
+ foundElement = true;
+
+ JS::RootedValue value(cx);
+ array.getValue(id, &value);
+
+ bos.push_back(addId(value));
+ });
+
+ if (!foundElement)
+ uasserted(ErrorCodes::BadValue, "attempted to insert an empty array");
+
+ conn->insert(ns, bos, flags);
+ } else {
+ conn->insert(ns, addId(args.get(1)));
+ }
+
+ args.rval().setUndefined();
+}
+
+void MongoBase::Functions::remove(JSContext* cx, JS::CallArgs args) {
+ if (!(args.length() == 2 || args.length() == 3))
+ uasserted(ErrorCodes::BadValue, "remove needs 2 or 3 args");
+
+ if (!(args.get(1).isObject()))
+ uasserted(ErrorCodes::BadValue, "attempted to remove a non-object");
+
+ ObjectWrapper o(cx, args.thisv());
+
+ if (o.hasField("readOnly") && o.getBoolean("readOnly"))
+ uasserted(ErrorCodes::BadValue, "js db in read only mode");
+
+ auto conn = getConnection(args);
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ BSONObj bson = ValueWriter(cx, args.get(1)).toBSON();
+
+ bool justOne = false;
+ if (args.length() > 2) {
+ justOne = args.get(2).toBoolean();
+ }
+
+ conn->remove(ns, bson, justOne);
+ args.rval().setUndefined();
+}
+
+void MongoBase::Functions::update(JSContext* cx, JS::CallArgs args) {
+ if (args.length() < 3)
+ uasserted(ErrorCodes::BadValue, "update needs at least 3 args");
+
+ if (!args.get(1).isObject())
+ uasserted(ErrorCodes::BadValue, "1st param to update has to be an object");
+
+ if (!args.get(2).isObject())
+ uasserted(ErrorCodes::BadValue, "2nd param to update has to be an object");
+
+ ObjectWrapper o(cx, args.thisv());
+
+ if (o.hasField("readOnly") && o.getBoolean("readOnly"))
+ uasserted(ErrorCodes::BadValue, "js db in read only mode");
+
+ auto conn = getConnection(args);
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ BSONObj q1 = ValueWriter(cx, args.get(1)).toBSON();
+ BSONObj o1 = ValueWriter(cx, args.get(2)).toBSON();
+
+ bool upsert = args.length() > 3 && args.get(3).isBoolean() && args.get(3).toBoolean();
+ bool multi = args.length() > 4 && args.get(4).isBoolean() && args.get(4).toBoolean();
+
+ conn->update(ns, q1, o1, upsert, multi);
+ args.rval().setUndefined();
+}
+
+void MongoBase::Functions::auth(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+ if (!conn)
+ uasserted(ErrorCodes::BadValue, "no connection");
+
+ BSONObj params;
+ switch (args.length()) {
+ case 1:
+ params = ValueWriter(cx, args.get(0)).toBSON();
+ break;
+ case 3:
+ params = BSON(saslCommandMechanismFieldName
+ << "MONGODB-CR" << saslCommandUserDBFieldName
+ << ValueWriter(cx, args[0]).toString() << saslCommandUserFieldName
+ << ValueWriter(cx, args[1]).toString() << saslCommandPasswordFieldName
+ << ValueWriter(cx, args[2]).toString());
+ break;
+ default:
+ uasserted(ErrorCodes::BadValue, "mongoAuth takes 1 object or 3 string arguments");
+ }
+
+ conn->auth(params);
+
+ args.rval().setBoolean(true);
+}
+
+void MongoBase::Functions::logout(JSContext* cx, JS::CallArgs args) {
+ if (args.length() != 1)
+ uasserted(ErrorCodes::BadValue, "logout needs 1 arg");
+
+ BSONObj ret;
+
+ std::string db = ValueWriter(cx, args.get(0)).toString();
+
+ auto conn = getConnection(args);
+ if (conn) {
+ conn->logout(db, ret);
+ }
+
+ ValueReader(cx, args.rval()).fromBSON(ret, false);
+}
+
+void MongoBase::Functions::cursorFromId(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (!(args.length() == 2 || args.length() == 3))
+ uasserted(ErrorCodes::BadValue, "cursorFromId needs 2 or 3 args");
+
+ if (!scope->getNumberLongProto().instanceOf(args.get(1)))
+ uasserted(ErrorCodes::BadValue, "2nd arg must be a NumberLong");
+
+ if (!(args.get(2).isNumber() || args.get(2).isUndefined()))
+ uasserted(ErrorCodes::BadValue, "3rd arg must be a js Number");
+
+ auto conn = getConnection(args);
+
+ std::string ns = ValueWriter(cx, args.get(0)).toString();
+
+ long long cursorId = NumberLongInfo::ToNumberLong(cx, args.get(1));
+
+ auto cursor = stdx::make_unique<DBClientCursor>(conn, ns, cursorId, 0, 0);
+
+ if (args.get(2).isNumber())
+ cursor->setBatchSize(ValueWriter(cx, args.get(2)).toInt32());
+
+ JS::RootedObject c(cx);
+ scope->getCursorProto().newInstance(&c);
+
+ setCursor(c, std::move(cursor), args);
+
+ args.rval().setObjectOrNull(c);
+}
+
+void MongoBase::Functions::copyDatabaseWithSCRAM(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+
+ if (!conn)
+ uasserted(ErrorCodes::BadValue, "no connection");
+
+ if (args.length() != 5)
+ uasserted(ErrorCodes::BadValue, "copyDatabase needs 5 arg");
+
+ // copyDatabase(fromdb, todb, fromhost, username, password);
+ std::string fromDb = ValueWriter(cx, args.get(0)).toString();
+ std::string toDb = ValueWriter(cx, args.get(1)).toString();
+ std::string fromHost = ValueWriter(cx, args.get(2)).toString();
+ std::string user = ValueWriter(cx, args.get(3)).toString();
+ std::string password = ValueWriter(cx, args.get(4)).toString();
+
+ std::string hashedPwd = DBClientWithCommands::createPasswordDigest(user, password);
+
+ std::unique_ptr<SaslClientSession> session(new NativeSaslClientSession());
+
+ session->setParameter(SaslClientSession::parameterMechanism, "SCRAM-SHA-1");
+ session->setParameter(SaslClientSession::parameterUser, user);
+ session->setParameter(SaslClientSession::parameterPassword, hashedPwd);
+ session->initialize();
+
+ BSONObj saslFirstCommandPrefix =
+ BSON("copydbsaslstart" << 1 << "fromhost" << fromHost << "fromdb" << fromDb
+ << saslCommandMechanismFieldName << "SCRAM-SHA-1");
+
+ BSONObj saslFollowupCommandPrefix =
+ BSON("copydb" << 1 << "fromhost" << fromHost << "fromdb" << fromDb << "todb" << toDb);
+
+ BSONObj saslCommandPrefix = saslFirstCommandPrefix;
+ BSONObj inputObj = BSON(saslCommandPayloadFieldName << "");
+ bool isServerDone = false;
+
+ while (!session->isDone()) {
+ std::string payload;
+ BSONType type;
+
+ Status status = saslExtractPayload(inputObj, &payload, &type);
+ uassertStatusOK(status);
+
+ std::string responsePayload;
+ status = session->step(payload, &responsePayload);
+ uassertStatusOK(status);
+
+ BSONObjBuilder commandBuilder;
+
+ commandBuilder.appendElements(saslCommandPrefix);
+ commandBuilder.appendBinData(saslCommandPayloadFieldName,
+ static_cast<int>(responsePayload.size()),
+ BinDataGeneral,
+ responsePayload.c_str());
+ BSONElement conversationId = inputObj[saslCommandConversationIdFieldName];
+ if (!conversationId.eoo())
+ commandBuilder.append(conversationId);
+
+ BSONObj command = commandBuilder.obj();
+
+ bool ok = conn->runCommand("admin", command, inputObj);
+
+ ErrorCodes::Error code =
+ ErrorCodes::fromInt(inputObj[saslCommandCodeFieldName].numberInt());
+
+ if (!ok || code != ErrorCodes::OK) {
+ if (code == ErrorCodes::OK)
+ code = ErrorCodes::UnknownError;
+
+ ValueReader(cx, args.rval()).fromBSON(inputObj, true);
+ return;
+ }
+
+ isServerDone = inputObj[saslCommandDoneFieldName].trueValue();
+ saslCommandPrefix = saslFollowupCommandPrefix;
+ }
+
+ if (!isServerDone) {
+ uasserted(ErrorCodes::InternalError, "copydb client finished before server.");
+ }
+
+ ValueReader(cx, args.rval()).fromBSON(inputObj, true);
+}
+
+void MongoBase::Functions::getClientRPCProtocols(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+
+ if (args.length() != 0)
+ uasserted(ErrorCodes::BadValue, "getClientRPCProtocols takes no args");
+
+ auto clientRPCProtocols = rpc::toString(conn->getClientRPCProtocols());
+ uassertStatusOK(clientRPCProtocols);
+
+ auto protoStr = clientRPCProtocols.getValue().toString();
+
+ ValueReader(cx, args.rval()).fromStringData(protoStr);
+}
+
+void MongoBase::Functions::setClientRPCProtocols(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+
+ if (args.length() != 1)
+ uasserted(ErrorCodes::BadValue, "setClientRPCProtocols needs 1 arg");
+ if (!args.get(0).isString())
+ uasserted(ErrorCodes::BadValue, "first argument to setClientRPCProtocols must be a string");
+
+ std::string rpcProtosStr = ValueWriter(cx, args.get(0)).toString();
+
+ auto clientRPCProtocols = rpc::parseProtocolSet(rpcProtosStr);
+ uassertStatusOK(clientRPCProtocols);
+
+ conn->setClientRPCProtocols(clientRPCProtocols.getValue());
+
+ args.rval().setUndefined();
+}
+
+void MongoBase::Functions::getServerRPCProtocols(JSContext* cx, JS::CallArgs args) {
+ auto conn = getConnection(args);
+
+ if (args.length() != 0)
+ uasserted(ErrorCodes::BadValue, "getServerRPCProtocols takes no args");
+
+ auto serverRPCProtocols = rpc::toString(conn->getServerRPCProtocols());
+ uassertStatusOK(serverRPCProtocols);
+
+ auto protoStr = serverRPCProtocols.getValue().toString();
+
+ ValueReader(cx, args.rval()).fromStringData(protoStr);
+}
+
+void MongoLocalInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ if (args.length() != 0)
+ uasserted(ErrorCodes::BadValue, "local Mongo constructor takes no args");
+
+ std::unique_ptr<DBClientBase> conn;
+
+ conn.reset(createDirectClient(scope->getOpContext()));
+
+ JS::RootedObject thisv(cx);
+ scope->getMongoLocalProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ JS_SetPrivate(thisv, new std::shared_ptr<DBClientBase>(conn.release()));
+
+ o.setBoolean("slaveOk", false);
+ o.setString("host", "EMBEDDED");
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ std::string host("127.0.0.1");
+
+ if (args.length() > 0 && args.get(0).isString()) {
+ host = ValueWriter(cx, args.get(0)).toString();
+ }
+
+ auto statusWithHost = ConnectionString::parse(host);
+ uassertStatusOK(statusWithHost);
+
+ const ConnectionString cs(statusWithHost.getValue());
+
+ std::string errmsg;
+ std::unique_ptr<DBClientBase> conn(cs.connect(errmsg));
+
+ if (!conn.get()) {
+ uasserted(ErrorCodes::InternalError, errmsg);
+ }
+
+ JS::RootedObject thisv(cx);
+ scope->getMongoExternalProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ JS_SetPrivate(thisv, new std::shared_ptr<DBClientBase>(conn.release()));
+
+ o.setBoolean("slaveOk", false);
+ o.setString("host", host);
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+void MongoExternalInfo::Functions::load(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ for (unsigned i = 0; i < args.length(); ++i) {
+ std::string filename = ValueWriter(cx, args.get(i)).toString();
+
+ if (!scope->execFile(filename, false, true)) {
+ uasserted(ErrorCodes::BadValue, std::string("error loading js file: ") + filename);
+ }
+ }
+
+ args.rval().setBoolean(true);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/mongo.h b/src/mongo/scripting/mozjs/mongo.h
new file mode 100644
index 00000000000..35815da5455
--- /dev/null
+++ b/src/mongo/scripting/mozjs/mongo.h
@@ -0,0 +1,88 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Shared code for the "Mongo" javascript object.
+ *
+ * The idea here is that there is a lot of shared functionality between the
+ * "Mongo" we see in the shell and the "Mongo" in dbeval. So we provide one
+ * info type with common code and differentiate with varying constructors.
+ */
+struct MongoBase : public BaseInfo {
+ static void finalize(JSFreeOp* fop, JSObject* obj);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(auth);
+ MONGO_DEFINE_JS_FUNCTION(copyDatabaseWithSCRAM);
+ MONGO_DEFINE_JS_FUNCTION(cursorFromId);
+ MONGO_DEFINE_JS_FUNCTION(find);
+ MONGO_DEFINE_JS_FUNCTION(getClientRPCProtocols);
+ MONGO_DEFINE_JS_FUNCTION(getServerRPCProtocols);
+ MONGO_DEFINE_JS_FUNCTION(insert);
+ MONGO_DEFINE_JS_FUNCTION(logout);
+ MONGO_DEFINE_JS_FUNCTION(remove);
+ MONGO_DEFINE_JS_FUNCTION(runCommand);
+ MONGO_DEFINE_JS_FUNCTION(setClientRPCProtocols);
+ MONGO_DEFINE_JS_FUNCTION(update);
+ };
+
+ static const JSFunctionSpec methods[13];
+
+ static const char* const className;
+ static const unsigned classFlags = JSCLASS_HAS_PRIVATE;
+};
+
+/**
+ * The dbeval variant of "Mongo"
+ */
+struct MongoLocalInfo : public MongoBase {
+ static void construct(JSContext* cx, JS::CallArgs args);
+};
+
+/**
+ * The shell variant of "Mongo"
+ */
+struct MongoExternalInfo : public MongoBase {
+ static void construct(JSContext* cx, JS::CallArgs args);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(load);
+ };
+
+ static const JSFunctionSpec freeFunctions[2];
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/nativefunction.cpp b/src/mongo/scripting/mozjs/nativefunction.cpp
new file mode 100644
index 00000000000..010b6a13587
--- /dev/null
+++ b/src/mongo/scripting/mozjs/nativefunction.cpp
@@ -0,0 +1,124 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/nativefunction.h"
+
+#include <cstdio>
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace mozjs {
+
+const char* const NativeFunctionInfo::inheritFrom = "Function";
+const char* const NativeFunctionInfo::className = "NativeFunction";
+
+const JSFunctionSpec NativeFunctionInfo::methods[2] = {
+ MONGO_ATTACH_JS_FUNCTION(toString), JS_FS_END,
+};
+
+namespace {
+
+/**
+ * Holder for the caller of ::make()'s callback function and context pointer
+ */
+class NativeHolder {
+public:
+ NativeHolder(NativeFunction func, void* ctx) : _func(func), _ctx(ctx) {}
+
+ NativeFunction _func;
+ void* _ctx;
+};
+
+NativeHolder* getHolder(JS::CallArgs args) {
+ return static_cast<NativeHolder*>(JS_GetPrivate(&args.callee()));
+}
+
+} // namespace
+
+void NativeFunctionInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ scope->getNativeFunctionProto().newObject(args.rval());
+}
+
+void NativeFunctionInfo::call(JSContext* cx, JS::CallArgs args) {
+ auto holder = getHolder(args);
+
+ BSONObjBuilder bob;
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ // 11 is enough here. unsigned's are only 32 bits, and 1 << 32 is only
+ // 10 decimal digits. +1 for the null and we're only at 11.
+ char buf[11];
+ std::sprintf(buf, "%i", i);
+
+ ValueWriter(cx, args.get(i)).writeThis(&bob, buf);
+ }
+
+ BSONObj out = holder->_func(bob.obj(), holder->_ctx);
+
+ ValueReader(cx, args.rval()).fromBSONElement(out.firstElement(), false);
+}
+
+void NativeFunctionInfo::finalize(JSFreeOp* fop, JSObject* obj) {
+ auto holder = static_cast<NativeHolder*>(JS_GetPrivate(obj));
+
+ if (holder)
+ delete holder;
+}
+
+void NativeFunctionInfo::Functions::toString(JSContext* cx, JS::CallArgs args) {
+ ObjectWrapper o(cx, args.thisv());
+
+ str::stream ss;
+ ss << "[native code]";
+
+ ValueReader(cx, args.rval()).fromStringData(ss.operator std::string());
+}
+
+
+void NativeFunctionInfo::make(JSContext* cx,
+ JS::MutableHandleObject obj,
+ NativeFunction function,
+ void* data) {
+ auto scope = getScope(cx);
+
+ scope->getNativeFunctionProto().newInstance(obj);
+
+ JS_SetPrivate(obj, new NativeHolder(function, data));
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/nativefunction.h b/src/mongo/scripting/mozjs/nativefunction.h
new file mode 100644
index 00000000000..0467b2c4743
--- /dev/null
+++ b/src/mongo/scripting/mozjs/nativefunction.h
@@ -0,0 +1,73 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/engine.h"
+#include "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Wrapper for JS Interpreter agnostic functions. Think mapReduce, or any use
+ * case that can tolerate automatic json <-> bson translation.
+ *
+ * The business end of the shim methods comes via ::call(). These types are
+ * invokable as js functions, with a little bit of automatic translation for
+ * arguments.
+ *
+ * This inherits from the global Function type.
+ *
+ * Also note that installType is private. So you can only get NativeFunctions
+ * in JS via ::make() from C++.
+ */
+struct NativeFunctionInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+ static void call(JSContext* cx, JS::CallArgs args);
+ static void finalize(JSFreeOp* fop, JSObject* obj);
+
+ static const char* const inheritFrom;
+ static const char* const className;
+ static const unsigned classFlags = JSCLASS_HAS_PRIVATE;
+ static const InstallType installType = InstallType::Private;
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(toString);
+ };
+
+ static const JSFunctionSpec methods[2];
+
+ static void make(JSContext* cx,
+ JS::MutableHandleObject obj,
+ NativeFunction function,
+ void* data);
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/numberint.cpp b/src/mongo/scripting/mozjs/numberint.cpp
new file mode 100644
index 00000000000..1704154ac74
--- /dev/null
+++ b/src/mongo/scripting/mozjs/numberint.cpp
@@ -0,0 +1,112 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/numberint.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec NumberIntInfo::methods[4] = {
+ MONGO_ATTACH_JS_FUNCTION(toNumber),
+ MONGO_ATTACH_JS_FUNCTION(toString),
+ MONGO_ATTACH_JS_FUNCTION(valueOf),
+ JS_FS_END,
+};
+
+const char* const NumberIntInfo::className = "NumberInt";
+
+void NumberIntInfo::finalize(JSFreeOp* fop, JSObject* obj) {
+ auto x = static_cast<int*>(JS_GetPrivate(obj));
+
+ if (x)
+ delete x;
+}
+
+int NumberIntInfo::ToNumberInt(JSContext* cx, JS::HandleValue thisv) {
+ auto x = static_cast<int*>(JS_GetPrivate(thisv.toObjectOrNull()));
+
+ return x ? *x : 0;
+}
+
+int NumberIntInfo::ToNumberInt(JSContext* cx, JS::HandleObject thisv) {
+ auto x = static_cast<int*>(JS_GetPrivate(thisv));
+
+ return x ? *x : 0;
+}
+
+void NumberIntInfo::Functions::valueOf(JSContext* cx, JS::CallArgs args) {
+ int out = NumberIntInfo::ToNumberInt(cx, args.thisv());
+
+ args.rval().setInt32(out);
+}
+
+void NumberIntInfo::Functions::toNumber(JSContext* cx, JS::CallArgs args) {
+ valueOf(cx, args);
+}
+
+void NumberIntInfo::Functions::toString(JSContext* cx, JS::CallArgs args) {
+ int val = NumberIntInfo::ToNumberInt(cx, args.thisv());
+
+ str::stream ss;
+ ss << "NumberInt(" << val << ")";
+
+ ValueReader(cx, args.rval()).fromStringData(ss.operator std::string());
+}
+
+void NumberIntInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ JS::RootedObject thisv(cx);
+
+ scope->getNumberIntProto().newObject(&thisv);
+
+ int32_t x = 0;
+
+ if (args.length() == 0) {
+ // Do nothing
+ } else if (args.length() == 1) {
+ x = ValueWriter(cx, args.get(0)).toInt32();
+ } else {
+ uasserted(ErrorCodes::BadValue, "NumberInt takes 0 or 1 arguments");
+ }
+
+ JS_SetPrivate(thisv, new int(x));
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/numberint.h b/src/mongo/scripting/mozjs/numberint.h
new file mode 100644
index 00000000000..378c3a0d57e
--- /dev/null
+++ b/src/mongo/scripting/mozjs/numberint.h
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "NumberInt" Javascript object.
+ *
+ * Wraps an actual c++ 'int' as its private member
+ */
+struct NumberIntInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+ static void finalize(JSFreeOp* fop, JSObject* obj);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(toNumber);
+ MONGO_DEFINE_JS_FUNCTION(toString);
+ MONGO_DEFINE_JS_FUNCTION(valueOf);
+ };
+
+ static const JSFunctionSpec methods[4];
+
+ static const char* const className;
+ static const unsigned classFlags = JSCLASS_HAS_PRIVATE;
+
+ static int ToNumberInt(JSContext* cx, JS::HandleObject object);
+ static int ToNumberInt(JSContext* cx, JS::HandleValue value);
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/numberlong.cpp b/src/mongo/scripting/mozjs/numberlong.cpp
new file mode 100644
index 00000000000..f8955689c9e
--- /dev/null
+++ b/src/mongo/scripting/mozjs/numberlong.cpp
@@ -0,0 +1,164 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/numberlong.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/text.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec NumberLongInfo::methods[4] = {
+ MONGO_ATTACH_JS_FUNCTION(toNumber),
+ MONGO_ATTACH_JS_FUNCTION(toString),
+ MONGO_ATTACH_JS_FUNCTION(valueOf),
+ JS_FS_END,
+};
+
+const char* const NumberLongInfo::className = "NumberLong";
+
+namespace {
+const char* const kTop = "top";
+const char* const kBottom = "bottom";
+const char* const kFloatApprox = "floatApprox";
+} // namespace
+
+long long NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleValue thisv) {
+ JS::RootedObject obj(cx, thisv.toObjectOrNull());
+ return ToNumberLong(cx, obj);
+}
+
+long long NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleObject thisv) {
+ ObjectWrapper o(cx, thisv);
+
+ if (!o.hasField(kTop)) {
+ if (!o.hasField(kFloatApprox))
+ uasserted(ErrorCodes::InternalError, "No top and no floatApprox fields");
+
+ return o.getNumber(kFloatApprox);
+ }
+
+ if (!o.hasField(kBottom))
+ uasserted(ErrorCodes::InternalError, "top but no bottom field");
+
+ return ((unsigned long long)((long long)o.getNumber(kTop) << 32) +
+ (unsigned)(o.getNumber(kBottom)));
+}
+
+void NumberLongInfo::Functions::valueOf(JSContext* cx, JS::CallArgs args) {
+ long long out = NumberLongInfo::ToNumberLong(cx, args.thisv());
+
+ args.rval().setDouble(out);
+}
+
+void NumberLongInfo::Functions::toNumber(JSContext* cx, JS::CallArgs args) {
+ valueOf(cx, args);
+}
+
+void NumberLongInfo::Functions::toString(JSContext* cx, JS::CallArgs args) {
+ str::stream ss;
+
+ long long val = NumberLongInfo::ToNumberLong(cx, args.thisv());
+
+ const long long limit = 2LL << 30;
+
+ if (val <= -limit || limit <= val)
+ ss << "NumberLong(\"" << val << "\")";
+ else
+ ss << "NumberLong(" << val << ")";
+
+ ValueReader(cx, args.rval()).fromStringData(ss.operator std::string());
+}
+
+void NumberLongInfo::construct(JSContext* cx, JS::CallArgs args) {
+ uassert(ErrorCodes::BadValue,
+ "NumberLong needs 0, 1 or 3 arguments",
+ args.length() == 0 || args.length() == 1 || args.length() == 3);
+
+ auto scope = getScope(cx);
+
+ JS::RootedObject thisv(cx);
+
+ scope->getNumberLongProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ JS::RootedValue floatApprox(cx);
+ JS::RootedValue top(cx);
+ JS::RootedValue bottom(cx);
+
+ if (args.length() == 0) {
+ o.setNumber(kFloatApprox, 0);
+ } else if (args.length() == 1) {
+ if (args.get(0).isNumber()) {
+ o.setValue(kFloatApprox, args.get(0));
+ } else {
+ std::string str = ValueWriter(cx, args.get(0)).toString();
+
+ unsigned long long val = parseLL(str.c_str());
+
+ // values above 2^53 are not accurately represented in JS
+ if ((long long)val == (long long)(double)(long long)(val) &&
+ val < 9007199254740992ULL) {
+ o.setNumber(kFloatApprox, val);
+ } else {
+ o.setNumber(kFloatApprox, val);
+ o.setNumber(kTop, val >> 32);
+ o.setNumber(kBottom, val & 0x00000000ffffffff);
+ }
+ }
+ } else {
+ if (!args.get(0).isNumber())
+ uasserted(ErrorCodes::BadValue, "floatApprox must be a number");
+
+ if (!args.get(1).isNumber() ||
+ args.get(1).toNumber() !=
+ static_cast<double>(static_cast<uint32_t>(args.get(1).toNumber())))
+ uasserted(ErrorCodes::BadValue, "top must be a 32 bit unsigned number");
+
+ if (!args.get(2).isNumber() ||
+ args.get(2).toNumber() !=
+ static_cast<double>(static_cast<uint32_t>(args.get(2).toNumber())))
+ uasserted(ErrorCodes::BadValue, "bottom must be a 32 bit unsigned number");
+
+ o.setValue(kFloatApprox, args.get(0));
+ o.setValue(kTop, args.get(1));
+ o.setValue(kBottom, args.get(2));
+ }
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/numberlong.h b/src/mongo/scripting/mozjs/numberlong.h
new file mode 100644
index 00000000000..bedfa558b2b
--- /dev/null
+++ b/src/mongo/scripting/mozjs/numberlong.h
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "NumberLong" Javascript object.
+ *
+ * Represents a 64 integer with a JS representation like:
+ *
+ * {
+ * top : Double,
+ * bottom : Double,
+ * floatApprox : Double,
+ * }
+ *
+ * Where top is the high 32 bits, bottom the low 32 bits and floatApprox a
+ * floating point approximation.
+ */
+struct NumberLongInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(toNumber);
+ MONGO_DEFINE_JS_FUNCTION(toString);
+ MONGO_DEFINE_JS_FUNCTION(valueOf);
+ };
+
+ static const JSFunctionSpec methods[4];
+
+ static const char* const className;
+
+ static long long ToNumberLong(JSContext* cx, JS::HandleObject object);
+ static long long ToNumberLong(JSContext* cx, JS::HandleValue value);
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/object.cpp b/src/mongo/scripting/mozjs/object.cpp
new file mode 100644
index 00000000000..a90bec36a72
--- /dev/null
+++ b/src/mongo/scripting/mozjs/object.cpp
@@ -0,0 +1,87 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/object.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+
+namespace mongo {
+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(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(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 mongo
diff --git a/src/mongo/scripting/mozjs/object.h b/src/mongo/scripting/mozjs/object.h
new file mode 100644
index 00000000000..70f475c3f0e
--- /dev/null
+++ b/src/mongo/scripting/mozjs/object.h
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Adds some methods onto the JS type "Object"
+ *
+ * Note that this installs "overNative", so we don't actually do anything other
+ * than layer a couple of our own functions on top of the existing prototype.
+ */
+struct ObjectInfo : public BaseInfo {
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(bsonsize);
+ MONGO_DEFINE_JS_FUNCTION(invalidForStorage);
+ };
+
+ static const JSFunctionSpec methods[3];
+
+ static const char* const className;
+
+ static const InstallType installType = InstallType::OverNative;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/objectwrapper.cpp b/src/mongo/scripting/mozjs/objectwrapper.cpp
new file mode 100644
index 00000000000..0450eeee42b
--- /dev/null
+++ b/src/mongo/scripting/mozjs/objectwrapper.cpp
@@ -0,0 +1,385 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/objectwrapper.h"
+
+#include "mongo/base/error_codes.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/scripting/mozjs/idwrapper.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+
+namespace mongo {
+namespace mozjs {
+
+void ObjectWrapper::Key::get(JSContext* cx, JS::HandleObject o, JS::MutableHandleValue value) {
+ switch (_type) {
+ case Type::Field:
+ if (JS_GetProperty(cx, o, _field, value))
+ return;
+ break;
+ case Type::Index:
+ if (JS_GetElement(cx, o, _idx, value))
+ return;
+ break;
+ case Type::Id: {
+ JS::RootedId id(cx, _id);
+
+ if (JS_GetPropertyById(cx, o, id, value))
+ return;
+ break;
+ }
+ }
+
+ throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to get value on a JSObject");
+}
+
+void ObjectWrapper::Key::set(JSContext* cx, JS::HandleObject o, JS::HandleValue value) {
+ switch (_type) {
+ case Type::Field:
+ if (JS_SetProperty(cx, o, _field, value))
+ return;
+ break;
+ case Type::Index:
+ if (JS_SetElement(cx, o, _idx, value))
+ return;
+ break;
+ case Type::Id: {
+ JS::RootedId id(cx, _id);
+
+ if (JS_SetPropertyById(cx, o, id, value))
+ return;
+ break;
+ }
+ }
+
+ throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to set value on a JSObject");
+}
+
+void ObjectWrapper::Key::define(JSContext* cx,
+ JS::HandleObject o,
+ JS::HandleValue value,
+ unsigned attrs) {
+ switch (_type) {
+ case Type::Field:
+ if (JS_DefineProperty(cx, o, _field, value, attrs))
+ return;
+ break;
+ case Type::Index:
+ if (JS_DefineElement(cx, o, _idx, value, attrs))
+ return;
+ break;
+ case Type::Id: {
+ JS::RootedId id(cx, _id);
+
+ if (JS_DefinePropertyById(cx, o, id, value, attrs))
+ return;
+ break;
+ }
+ }
+
+ throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to define value on a JSObject");
+}
+
+bool ObjectWrapper::Key::has(JSContext* cx, JS::HandleObject o) {
+ bool has;
+
+ switch (_type) {
+ case Type::Field:
+ if (JS_HasProperty(cx, o, _field, &has))
+ return has;
+ break;
+ case Type::Index:
+ if (JS_HasElement(cx, o, _idx, &has))
+ return has;
+ break;
+ case Type::Id: {
+ JS::RootedId id(cx, _id);
+
+ if (JS_HasPropertyById(cx, o, id, &has))
+ return has;
+ break;
+ }
+ }
+
+ throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to has value on a JSObject");
+}
+
+void ObjectWrapper::Key::del(JSContext* cx, JS::HandleObject o) {
+ switch (_type) {
+ case Type::Field:
+ if (JS_DeleteProperty(cx, o, _field))
+ return;
+ break;
+ case Type::Index:
+ if (JS_DeleteElement(cx, o, _idx))
+ return;
+ break;
+ case Type::Id: {
+ JS::RootedId id(cx, _id);
+
+ // For some reason JS_DeletePropertyById doesn't link
+ if (JS_DeleteProperty(cx, o, IdWrapper(cx, id).toString().c_str()))
+ return;
+ break;
+ }
+ }
+
+ throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to delete value on a JSObject");
+}
+
+std::string ObjectWrapper::Key::toString(JSContext* cx) {
+ switch (_type) {
+ case Type::Field:
+ return _field;
+ case Type::Index:
+ return std::to_string(_idx);
+ case Type::Id: {
+ JS::RootedId id(cx, _id);
+ return IdWrapper(cx, id).toString();
+ }
+ }
+
+ throwCurrentJSException(
+ 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::HandleValue value, int depth)
+ : _context(cx), _object(cx, value.toObjectOrNull()), _depth(depth) {}
+
+double ObjectWrapper::getNumber(Key key) {
+ JS::RootedValue x(_context);
+ getValue(key, &x);
+
+ return ValueWriter(_context, x).toNumber();
+}
+
+int ObjectWrapper::getNumberInt(Key key) {
+ JS::RootedValue x(_context);
+ getValue(key, &x);
+
+ return ValueWriter(_context, x).toInt32();
+}
+
+long long ObjectWrapper::getNumberLongLong(Key key) {
+ JS::RootedValue x(_context);
+ getValue(key, &x);
+
+ return ValueWriter(_context, x).toInt64();
+}
+
+std::string ObjectWrapper::getString(Key key) {
+ JS::RootedValue x(_context);
+ getValue(key, &x);
+
+ return ValueWriter(_context, x).toString();
+}
+
+bool ObjectWrapper::getBoolean(Key key) {
+ JS::RootedValue x(_context);
+ getValue(key, &x);
+
+ return ValueWriter(_context, x).toBoolean();
+}
+
+BSONObj ObjectWrapper::getObject(Key key) {
+ JS::RootedValue x(_context);
+ getValue(key, &x);
+
+ return ValueWriter(_context, x, _depth).toBSON();
+}
+
+void ObjectWrapper::getValue(Key key, JS::MutableHandleValue value) {
+ key.get(_context, _object, value);
+}
+
+void ObjectWrapper::setNumber(Key key, double val) {
+ JS::RootedValue jsValue(_context);
+ jsValue.setDouble(val);
+
+ setValue(key, jsValue);
+}
+
+void ObjectWrapper::setString(Key key, StringData val) {
+ JS::RootedValue jsValue(_context);
+ ValueReader(_context, &jsValue).fromStringData(val);
+
+ setValue(key, jsValue);
+}
+
+void ObjectWrapper::setBoolean(Key key, bool val) {
+ JS::RootedValue jsValue(_context);
+ jsValue.setBoolean(val);
+
+ setValue(key, jsValue);
+}
+
+void ObjectWrapper::setBSONElement(Key key, const BSONElement& elem, bool readOnly) {
+ JS::RootedValue value(_context);
+ ValueReader(_context, &value, _depth).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);
+
+ setValue(key, value);
+}
+
+void ObjectWrapper::setValue(Key key, JS::HandleValue val) {
+ key.set(_context, _object, val);
+}
+
+void ObjectWrapper::setObject(Key key, JS::HandleObject object) {
+ JS::RootedValue value(_context);
+ value.setObjectOrNull(object);
+
+ setValue(key, value);
+}
+
+void ObjectWrapper::defineProperty(Key key, JS::HandleValue val, unsigned attrs) {
+ key.define(_context, _object, val, attrs);
+}
+
+void ObjectWrapper::deleteProperty(Key key) {
+ key.del(_context, _object);
+}
+
+int ObjectWrapper::type(Key key) {
+ JS::RootedValue x(_context);
+ getValue(key, &x);
+
+ return ValueWriter(_context, x).type();
+}
+
+void ObjectWrapper::rename(Key from, const char* to) {
+ JS::RootedValue value(_context);
+
+ JS::RootedValue undefValue(_context);
+ undefValue.setUndefined();
+
+ getValue(from, &value);
+
+ setValue(to, value);
+ setValue(from, undefValue);
+}
+
+bool ObjectWrapper::hasField(Key key) {
+ return key.has(_context, _object);
+}
+
+void ObjectWrapper::callMethod(const char* field,
+ const JS::HandleValueArray& args,
+ JS::MutableHandleValue out) {
+ if (JS::Call(_context, _object, field, args, out))
+ return;
+
+ throwCurrentJSException(_context, ErrorCodes::InternalError, "Failed to call method");
+}
+
+void ObjectWrapper::callMethod(const char* field, JS::MutableHandleValue out) {
+ JS::AutoValueVector args(_context);
+
+ callMethod(field, args, out);
+}
+
+void ObjectWrapper::callMethod(JS::HandleValue fun,
+ const JS::HandleValueArray& args,
+ JS::MutableHandleValue out) {
+ if (JS::Call(_context, _object, fun, args, out))
+ return;
+
+ throwCurrentJSException(_context, ErrorCodes::InternalError, "Failed to call method");
+}
+
+void ObjectWrapper::callMethod(JS::HandleValue fun, JS::MutableHandleValue out) {
+ JS::AutoValueVector args(_context);
+
+ callMethod(fun, args, out);
+}
+
+void ObjectWrapper::writeThis(BSONObjBuilder* b) {
+ auto scope = getScope(_context);
+
+ BSONObj* originalBSON = nullptr;
+ if (scope->getBsonProto().instanceOf(_object)) {
+ bool altered;
+
+ std::tie(originalBSON, altered) = BSONInfo::originalBSON(_context, _object);
+
+ if (!altered) {
+ b->appendElements(*originalBSON);
+ return;
+ }
+ }
+
+ // 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);
+ }
+
+ enumerate([&](JS::HandleId id) {
+ JS::RootedValue x(_context);
+
+ IdWrapper idw(_context, id);
+
+ if (_depth == 0 && idw.isString() && idw.equals("_id"))
+ return;
+
+ _writeField(b, id, 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);
+}
+
+void ObjectWrapper::_writeField(BSONObjBuilder* b, Key key, BSONObj* originalParent) {
+ JS::RootedValue value(_context);
+ key.get(_context, _object, &value);
+
+ ValueWriter x(_context, value, _depth);
+ x.setOriginalBSON(originalParent);
+
+ x.writeThis(b, key.toString(_context));
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/objectwrapper.h b/src/mongo/scripting/mozjs/objectwrapper.h
new file mode 100644
index 00000000000..efac05a1e71
--- /dev/null
+++ b/src/mongo/scripting/mozjs/objectwrapper.h
@@ -0,0 +1,181 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+#include <string>
+
+#include "mongo/scripting/mozjs/exception.h"
+
+namespace mongo {
+
+class BSONObj;
+class BSONObjBuilder;
+class BSONElement;
+
+namespace mozjs {
+
+class MozJSImplScope;
+
+/**
+ * Wraps JSObject's with helpers for accessing their properties
+ *
+ * This wraps a RootedObject, so should only be allocated on the stack and is
+ * not movable or copyable
+ */
+class ObjectWrapper {
+public:
+ /**
+ * Helper subclass that provides some easy boilerplate for accessing
+ * properties by string, index or id.
+ */
+ class Key {
+ friend class ObjectWrapper;
+
+ enum class Type : char {
+ Field,
+ Index,
+ Id,
+ };
+
+ public:
+ Key(const char* field) : _field(field), _type(Type::Field) {}
+ Key(uint32_t idx) : _idx(idx), _type(Type::Index) {}
+ Key(JS::HandleId id) : _id(id), _type(Type::Id) {}
+
+ private:
+ void get(JSContext* cx, JS::HandleObject o, JS::MutableHandleValue value);
+ void set(JSContext* cx, JS::HandleObject o, JS::HandleValue value);
+ bool has(JSContext* cx, JS::HandleObject o);
+ void define(JSContext* cx, JS::HandleObject o, JS::HandleValue value, unsigned attrs);
+ void del(JSContext* cx, JS::HandleObject o);
+ std::string toString(JSContext* cx);
+
+ union {
+ const char* _field;
+ uint32_t _idx;
+ jsid _id;
+ };
+ 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);
+
+ double getNumber(Key key);
+ int getNumberInt(Key key);
+ long long getNumberLongLong(Key key);
+ std::string getString(Key key);
+ bool getBoolean(Key key);
+ BSONObj getObject(Key key);
+ void getValue(Key key, JS::MutableHandleValue value);
+
+ void setNumber(Key key, double val);
+ void setString(Key key, StringData val);
+ void setBoolean(Key key, bool val);
+ void setBSONElement(Key key, const BSONElement& elem, bool readOnly);
+ void setBSON(Key key, const BSONObj& obj, bool readOnly);
+ void setValue(Key key, JS::HandleValue value);
+ void setObject(Key key, JS::HandleObject value);
+
+ /**
+ * See JS_DefineProperty for what sort of attributes might be useful
+ */
+ void defineProperty(Key key, JS::HandleValue value, unsigned attrs);
+
+ void deleteProperty(Key key);
+
+ /**
+ * Returns the bson type of the property
+ */
+ int type(Key key);
+
+ void rename(Key key, const char* to);
+
+ bool hasField(Key key);
+
+ void callMethod(const char* name, const JS::HandleValueArray& args, JS::MutableHandleValue out);
+ void callMethod(const char* name, JS::MutableHandleValue out);
+ void callMethod(JS::HandleValue fun,
+ const JS::HandleValueArray& args,
+ JS::MutableHandleValue out);
+ void callMethod(JS::HandleValue fun, JS::MutableHandleValue out);
+
+ /**
+ * Safely enumerates fields in the object, invoking a callback for each id
+ */
+ template <typename T>
+ void enumerate(T&& callback) {
+ JS::AutoIdArray ids(_context, JS_Enumerate(_context, _object));
+
+ if (!ids)
+ throwCurrentJSException(
+ _context, ErrorCodes::JSInterpreterFailure, "Failure to enumerate object");
+
+ JS::RootedId rid(_context);
+ for (size_t i = 0; i < ids.length(); ++i) {
+ rid.set(ids[i]);
+ callback(rid);
+ }
+ }
+
+ /**
+ * concatenates all of the fields in the object into the associated builder
+ */
+ void writeThis(BSONObjBuilder* b);
+
+ JS::HandleObject thisv() {
+ return _object;
+ }
+
+private:
+ /**
+ * 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);
+
+ 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
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/oid.cpp b/src/mongo/scripting/mozjs/oid.cpp
new file mode 100644
index 00000000000..cf391bf03e8
--- /dev/null
+++ b/src/mongo/scripting/mozjs/oid.cpp
@@ -0,0 +1,87 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/oid.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/stdx/memory.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace mozjs {
+
+const JSFunctionSpec OIDInfo::methods[2] = {
+ MONGO_ATTACH_JS_FUNCTION(toString), JS_FS_END,
+};
+
+const char* const OIDInfo::className = "ObjectId";
+
+void OIDInfo::Functions::toString(JSContext* cx, JS::CallArgs args) {
+ ObjectWrapper o(cx, args.thisv());
+
+ if (!o.hasField("str"))
+ uasserted(ErrorCodes::BadValue, "Must have \"str\" field");
+
+ JS::RootedValue value(cx);
+ o.getValue("str", &value);
+
+ std::string str = str::stream() << "ObjectId(\"" << ValueWriter(cx, value).toString() << "\")";
+
+ ValueReader(cx, args.rval()).fromStringData(str);
+}
+
+void OIDInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ auto oid = stdx::make_unique<OID>();
+
+ if (args.length() == 0) {
+ oid->init();
+ } else {
+ auto str = ValueWriter(cx, args.get(0)).toString();
+
+ Scope::validateObjectIdString(str);
+ oid->init(str);
+ }
+
+ JS::RootedObject thisv(cx);
+ scope->getOidProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ o.setString("str", oid->toString());
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/oid.h b/src/mongo/scripting/mozjs/oid.h
new file mode 100644
index 00000000000..109282be882
--- /dev/null
+++ b/src/mongo/scripting/mozjs/oid.h
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "ObjectId" Javascript object.
+ *
+ * Holds a private bson OID
+ */
+struct OIDInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+
+ struct Functions {
+ MONGO_DEFINE_JS_FUNCTION(toString);
+ };
+
+ static const JSFunctionSpec methods[2];
+
+ static const char* const className;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/proxyscope.cpp b/src/mongo/scripting/mozjs/proxyscope.cpp
new file mode 100644
index 00000000000..665d249f4c5
--- /dev/null
+++ b/src/mongo/scripting/mozjs/proxyscope.cpp
@@ -0,0 +1,318 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/proxyscope.h"
+
+#include "mongo/db/client.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/service_context.h"
+#include "mongo/scripting/mozjs/implscope.h"
+
+namespace mongo {
+namespace mozjs {
+
+MozJSProxyScope::MozJSProxyScope(MozJSScriptEngine* engine)
+ : _engine(engine),
+ _implScope(nullptr),
+ _mutex(),
+ _state(State::Idle),
+ _status(Status::OK()),
+ _condvar(),
+ _thread(&MozJSProxyScope::implThread, this) {
+ // Test the child on startup to make sure it's awake and that the
+ // implementation scope sucessfully constructed.
+ try {
+ runOnImplThread([] {});
+ } catch (...) {
+ shutdownThread();
+ throw;
+ }
+}
+
+MozJSProxyScope::~MozJSProxyScope() {
+ DESTRUCTOR_GUARD(kill(); shutdownThread(););
+}
+
+void MozJSProxyScope::init(const BSONObj* data) {
+ runOnImplThread([&] { _implScope->init(data); });
+}
+
+void MozJSProxyScope::reset() {
+ runOnImplThread([&] { _implScope->reset(); });
+}
+
+bool MozJSProxyScope::isKillPending() const {
+ return _implScope->isKillPending();
+}
+
+void MozJSProxyScope::registerOperation(OperationContext* txn) {
+ runOnImplThread([&] { _implScope->registerOperation(txn); });
+}
+
+void MozJSProxyScope::unregisterOperation() {
+ runOnImplThread([&] { _implScope->unregisterOperation(); });
+}
+
+void MozJSProxyScope::localConnectForDbEval(OperationContext* txn, const char* dbName) {
+ runOnImplThread([&] { _implScope->localConnectForDbEval(txn, dbName); });
+}
+
+void MozJSProxyScope::externalSetup() {
+ runOnImplThread([&] { _implScope->externalSetup(); });
+}
+
+std::string MozJSProxyScope::getError() {
+ std::string out;
+ runOnImplThread([&] { out = _implScope->getError(); });
+ return out;
+}
+
+/**
+ * This is an artifact of how out of memory errors were communicated in V8. We
+ * just throw out of memory errors from spidermonkey when we get them, rather
+ * than setting a flag and having to pick them up here.
+ */
+bool MozJSProxyScope::hasOutOfMemoryException() {
+ return false;
+}
+
+void MozJSProxyScope::gc() {
+ _implScope->gc();
+}
+
+double MozJSProxyScope::getNumber(const char* field) {
+ double out;
+ runOnImplThread([&] { out = _implScope->getNumber(field); });
+ return out;
+}
+
+int MozJSProxyScope::getNumberInt(const char* field) {
+ int out;
+ runOnImplThread([&] { out = _implScope->getNumberInt(field); });
+ return out;
+}
+
+long long MozJSProxyScope::getNumberLongLong(const char* field) {
+ long long out;
+ runOnImplThread([&] { out = _implScope->getNumberLongLong(field); });
+ return out;
+}
+
+std::string MozJSProxyScope::getString(const char* field) {
+ std::string out;
+ runOnImplThread([&] { out = _implScope->getString(field); });
+ return out;
+}
+
+bool MozJSProxyScope::getBoolean(const char* field) {
+ bool out;
+ runOnImplThread([&] { out = _implScope->getBoolean(field); });
+ return out;
+}
+
+BSONObj MozJSProxyScope::getObject(const char* field) {
+ BSONObj out;
+ runOnImplThread([&] { out = _implScope->getObject(field); });
+ return out;
+}
+
+void MozJSProxyScope::setNumber(const char* field, double val) {
+ runOnImplThread([&] { _implScope->setNumber(field, val); });
+}
+
+void MozJSProxyScope::setString(const char* field, StringData val) {
+ runOnImplThread([&] { _implScope->setString(field, val); });
+}
+
+void MozJSProxyScope::setBoolean(const char* field, bool val) {
+ runOnImplThread([&] { _implScope->setBoolean(field, val); });
+}
+
+void MozJSProxyScope::setElement(const char* field, const BSONElement& e) {
+ runOnImplThread([&] { _implScope->setElement(field, e); });
+}
+
+void MozJSProxyScope::setObject(const char* field, const BSONObj& obj, bool readOnly) {
+ runOnImplThread([&] { _implScope->setObject(field, obj, readOnly); });
+}
+
+void MozJSProxyScope::setFunction(const char* field, const char* code) {
+ runOnImplThread([&] { _implScope->setFunction(field, code); });
+}
+
+int MozJSProxyScope::type(const char* field) {
+ int out;
+ runOnImplThread([&] { out = _implScope->type(field); });
+ return out;
+}
+
+void MozJSProxyScope::rename(const char* from, const char* to) {
+ runOnImplThread([&] { _implScope->rename(from, to); });
+}
+
+int MozJSProxyScope::invoke(ScriptingFunction func,
+ const BSONObj* argsObject,
+ const BSONObj* recv,
+ int timeoutMs,
+ bool ignoreReturn,
+ bool readOnlyArgs,
+ bool readOnlyRecv) {
+ int out;
+ runOnImplThread([&] {
+ out = _implScope->invoke(
+ func, argsObject, recv, timeoutMs, ignoreReturn, readOnlyArgs, readOnlyRecv);
+ });
+
+ return out;
+}
+
+bool MozJSProxyScope::exec(StringData code,
+ const std::string& name,
+ bool printResult,
+ bool reportError,
+ bool assertOnError,
+ int timeoutMs) {
+ bool out;
+ runOnImplThread([&] {
+ out = _implScope->exec(code, name, printResult, reportError, assertOnError, timeoutMs);
+ });
+ return out;
+}
+
+void MozJSProxyScope::injectNative(const char* field, NativeFunction func, void* data) {
+ runOnImplThread([&] { _implScope->injectNative(field, func, data); });
+}
+
+ScriptingFunction MozJSProxyScope::_createFunction(const char* raw,
+ ScriptingFunction functionNumber) {
+ ScriptingFunction out;
+ runOnImplThread([&] { out = _implScope->_createFunction(raw, functionNumber); });
+ return out;
+}
+
+OperationContext* MozJSProxyScope::getOpContext() const {
+ return _implScope->getOpContext();
+}
+
+void MozJSProxyScope::kill() {
+ _implScope->kill();
+}
+
+/**
+ * Invokes a function on the implementation thread
+ *
+ * It does this by serializing the invocation through a stdx::function and
+ * capturing any exceptions through _status.
+ *
+ * We transition:
+ *
+ * Idle -> ProxyRequest -> ImplResponse -> Idle
+ */
+void MozJSProxyScope::runOnImplThread(std::function<void()> f) {
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+ _function = std::move(f);
+
+ invariant(_state == State::Idle);
+ _state = State::ProxyRequest;
+
+ _condvar.notify_one();
+
+ _condvar.wait(lk, [this] { return _state == State::ImplResponse; });
+
+ _state = State::Idle;
+
+ // Clear the _status state and throw it if necessary
+ auto status = std::move(_status);
+ uassertStatusOK(status);
+}
+
+void MozJSProxyScope::shutdownThread() {
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ invariant(_state == State::Idle);
+
+ _state = State::Shutdown;
+ }
+
+ _condvar.notify_one();
+
+ _thread.join();
+}
+
+/**
+ * The main loop for the implementation thread
+ *
+ * This owns the actual implementation scope (which needs to be created on this
+ * child thread) and has essentially two transition paths:
+ *
+ * Standard: ProxyRequest -> ImplResponse
+ * Invoke _function. Serialize exceptions to _status.
+ *
+ * Shutdown: Shutdown -> _
+ * break out of the loop and return.
+ */
+void MozJSProxyScope::implThread() {
+ if (hasGlobalServiceContext())
+ Client::initThread("js");
+
+ std::unique_ptr<MozJSImplScope> scope;
+
+ // 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();
+ } catch (...) {
+ _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)
+ break;
+
+ try {
+ _function();
+ } catch (...) {
+ _status = exceptionToStatus();
+ }
+
+ _state = State::ImplResponse;
+
+ _condvar.notify_one();
+ }
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/proxyscope.h b/src/mongo/scripting/mozjs/proxyscope.h
new file mode 100644
index 00000000000..8b874360a68
--- /dev/null
+++ b/src/mongo/scripting/mozjs/proxyscope.h
@@ -0,0 +1,195 @@
+/**
+ * Copyright (C) 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 "mongo/client/dbclientcursor.h"
+#include "mongo/scripting/mozjs/engine.h"
+#include "mongo/stdx/condition_variable.h"
+#include "mongo/stdx/functional.h"
+#include "mongo/stdx/mutex.h"
+#include "mongo/stdx/thread.h"
+
+namespace mongo {
+namespace mozjs {
+
+class MozJSImplScope;
+
+/**
+ * Proxies all methods on the implementation scope over a side channel that
+ * allows the SpiderMonkey runtime to operate entirely on one thread. This
+ * implements the public scope interface and is the right way to talk to an
+ * implementation scope from multiple threads.
+ *
+ * In terms of implementation, it does most of it's heavy lifting through a
+ * stdx::function. The proxy scope owns an implementation scope transitively
+ * through the thread it owns. They communicate by setting a variable, then
+ * signaling each other. That communication has to work, there's no fallback
+ * for timing out.
+ *
+ * There are probably performance gains to be had from making
+ * the argument capture and method dispatch explicit, but I'll wait until we've
+ * measured it before bothering.
+ *
+ * See mongo::Scope for details on all of the overridden functions
+ *
+ */
+class MozJSProxyScope final : public Scope {
+ MONGO_DISALLOW_COPYING(MozJSProxyScope);
+
+ /**
+ * The FSM is fairly tight:
+ *
+ * +----------+ shutdownThread() +--------------------+
+ * | Shutdown | <------------------ | Idle | <+
+ * +----------+ +--------------------+ |
+ * | |
+ * | runOnImplThread() |
+ * v |
+ * +--------------------+ |
+ * | ProxyRequest | | impl -> proxy
+ * +--------------------+ |
+ * | |
+ * | proxy -> impl |
+ * v |
+ * +--------------------+ |
+ * | ImplResponse | -+
+ * +--------------------+
+ *
+ * The regular flow:
+ * - We start at Idle and on the proxy thread.
+ * - runOnImplThread sets ProxyRequest and notifies the impl thread
+ * - The impl thread wakes up, invokes _function(), sets ImplResponse and notifies the proxy
+ * thread
+ * - The proxy thread wakes up and sets Idle
+ *
+ * Shutdown:
+ * - On destruction, The proxy thread sets Shutdown and notifies the impl thread
+ * - The impl thread wakes up, breaks out of it's loop and returns
+ * - The proxy thread joins the impl thread
+ *
+ */
+ enum class State : char {
+ Idle,
+ ProxyRequest,
+ ImplResponse,
+ Shutdown,
+ };
+
+public:
+ MozJSProxyScope(MozJSScriptEngine* engine);
+ ~MozJSProxyScope();
+
+ void init(const BSONObj* data) override;
+
+ void reset() override;
+
+ bool isKillPending() const override;
+
+ void registerOperation(OperationContext* txn) override;
+
+ void unregisterOperation() override;
+
+ void localConnectForDbEval(OperationContext* txn, const char* dbName) override;
+
+ void externalSetup() override;
+
+ std::string getError() override;
+
+ bool hasOutOfMemoryException() override;
+
+ void gc() override;
+
+ double getNumber(const char* field) override;
+ int getNumberInt(const char* field) override;
+ long long getNumberLongLong(const char* field) override;
+ std::string getString(const char* field) override;
+ bool getBoolean(const char* field) override;
+ BSONObj getObject(const char* field) override;
+
+ void setNumber(const char* field, double val) override;
+ void setString(const char* field, StringData val) override;
+ void setBoolean(const char* field, bool val) override;
+ void setElement(const char* field, const BSONElement& e) override;
+ void setObject(const char* field, const BSONObj& obj, bool readOnly) override;
+ void setFunction(const char* field, const char* code) override;
+
+ int type(const char* field) override;
+
+ void rename(const char* from, const char* to) override;
+
+ int invoke(ScriptingFunction func,
+ const BSONObj* args,
+ const BSONObj* recv,
+ int timeoutMs = 0,
+ bool ignoreReturn = false,
+ bool readOnlyArgs = false,
+ bool readOnlyRecv = false) override;
+
+ bool exec(StringData code,
+ const std::string& name,
+ bool printResult,
+ bool reportError,
+ bool assertOnError,
+ int timeoutMs) override;
+
+ void injectNative(const char* field, NativeFunction func, void* data = 0) override;
+
+ ScriptingFunction _createFunction(const char* code,
+ ScriptingFunction functionNumber = 0) override;
+
+ OperationContext* getOpContext() const;
+
+ /**
+ * Thread safe. Kills the running operation
+ */
+ void kill();
+
+private:
+ void runOnImplThread(std::function<void()> f);
+ void shutdownThread();
+ void implThread();
+
+ MozJSScriptEngine* const _engine;
+ MozJSImplScope* _implScope;
+
+ /**
+ * This mutex protects _function, _state and _status as channels for
+ * function invocation and exception handling
+ */
+ stdx::mutex _mutex;
+ stdx::function<void()> _function;
+ State _state;
+ Status _status;
+
+ stdx::condition_variable _condvar;
+ stdx::thread _thread;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/regexp.cpp b/src/mongo/scripting/mozjs/regexp.cpp
new file mode 100644
index 00000000000..b935a9fb354
--- /dev/null
+++ b/src/mongo/scripting/mozjs/regexp.cpp
@@ -0,0 +1,39 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/regexp.h"
+
+namespace mongo {
+namespace mozjs {
+
+const char* const RegExpInfo::className = "RegExp";
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/regexp.h b/src/mongo/scripting/mozjs/regexp.h
new file mode 100644
index 00000000000..b8553518127
--- /dev/null
+++ b/src/mongo/scripting/mozjs/regexp.h
@@ -0,0 +1,49 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "RegExp" Javascript object.
+ *
+ * Note that this installs over native. We only use this to grab the regexp
+ * prototype early in case users overwrite it.
+ */
+struct RegExpInfo : public BaseInfo {
+ static const char* const className;
+
+ static const InstallType installType = InstallType::OverNative;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/timestamp.cpp b/src/mongo/scripting/mozjs/timestamp.cpp
new file mode 100644
index 00000000000..f81cc4050ce
--- /dev/null
+++ b/src/mongo/scripting/mozjs/timestamp.cpp
@@ -0,0 +1,77 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/timestamp.h"
+
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/scripting/mozjs/valuewriter.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace mozjs {
+
+const char* const TimestampInfo::className = "Timestamp";
+
+void TimestampInfo::construct(JSContext* cx, JS::CallArgs args) {
+ auto scope = getScope(cx);
+
+ JS::RootedObject thisv(cx);
+ scope->getTimestampProto().newObject(&thisv);
+ ObjectWrapper o(cx, thisv);
+
+ if (args.length() == 0) {
+ o.setNumber("t", 0);
+ o.setNumber("i", 0);
+ } else if (args.length() == 2) {
+ if (!args.get(0).isNumber())
+ uasserted(ErrorCodes::BadValue, "Timestamp time must be a number");
+ if (!args.get(1).isNumber())
+ uasserted(ErrorCodes::BadValue, "Timestamp increment must be a number");
+
+ int64_t t = ValueWriter(cx, args.get(0)).toInt64();
+ int64_t largestVal = int64_t(Timestamp::max().getSecs());
+ if (t > largestVal)
+ uasserted(ErrorCodes::BadValue,
+ str::stream() << "The first argument must be in seconds; " << t
+ << " is too large (max " << largestVal << ")");
+
+ o.setValue("t", args.get(0));
+ o.setValue("i", args.get(1));
+ } else {
+ uasserted(ErrorCodes::BadValue, "Timestamp needs 0 or 2 arguments");
+ }
+
+ args.rval().setObjectOrNull(thisv);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/timestamp.h b/src/mongo/scripting/mozjs/timestamp.h
new file mode 100644
index 00000000000..1dc4a998420
--- /dev/null
+++ b/src/mongo/scripting/mozjs/timestamp.h
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 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 "mongo/scripting/mozjs/wraptype.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * The "Timestamp" Javascript object.
+ *
+ * Represents a bson timestamp that looks like:
+ *
+ * {
+ * t : Double,
+ * i : Double,
+ * }
+ */
+struct TimestampInfo : public BaseInfo {
+ static void construct(JSContext* cx, JS::CallArgs args);
+
+ static const char* const className;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/valuereader.cpp b/src/mongo/scripting/mozjs/valuereader.cpp
new file mode 100644
index 00000000000..ad45fafc06e
--- /dev/null
+++ b/src/mongo/scripting/mozjs/valuereader.cpp
@@ -0,0 +1,272 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/valuereader.h"
+
+#include <cstdio>
+#include <js/CharacterEncoding.h>
+
+#include "mongo/base/error_codes.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/util/base64.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+namespace mozjs {
+
+ValueReader::ValueReader(JSContext* cx, JS::MutableHandleValue value, int depth)
+ : _context(cx), _value(value), _depth(depth) {}
+
+void ValueReader::fromBSONElement(const BSONElement& elem, bool readOnly) {
+ auto scope = getScope(_context);
+
+ switch (elem.type()) {
+ case mongo::Code:
+ scope->newFunction(elem.valueStringData(), _value);
+ return;
+ case mongo::CodeWScope:
+ if (!elem.codeWScopeObject().isEmpty())
+ warning() << "CodeWScope doesn't transfer to db.eval";
+ scope->newFunction(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen() - 1),
+ _value);
+ return;
+ case mongo::Symbol:
+ case mongo::String:
+ fromStringData(elem.valueStringData());
+ return;
+ case mongo::jstOID: {
+ JS::AutoValueArray<1> args(_context);
+
+ ValueReader(_context, args[0]).fromStringData(elem.OID().toString());
+
+ scope->getOidProto().newInstance(args, _value);
+ return;
+ }
+ case mongo::NumberDouble:
+ _value.setDouble(elem.Number());
+ return;
+ case mongo::NumberInt:
+ _value.setInt32(elem.Int());
+ return;
+ case mongo::Array: {
+ auto arrayPtr = JS_NewArrayObject(_context, 0);
+ uassert(ErrorCodes::JSInterpreterFailure, "Failed to JS_NewArrayObject", arrayPtr);
+ JS::RootedObject array(_context, arrayPtr);
+
+ unsigned i = 0;
+ BSONForEach(subElem, elem.embeddedObject()) {
+ // We use an unsigned 32 bit integer, so 10 base 10 digits and
+ // 1 null byte
+ char str[11];
+ sprintf(str, "%i", i++);
+ JS::RootedValue member(_context);
+
+ ValueReader(_context, &member, _depth + 1).fromBSONElement(subElem, readOnly);
+ ObjectWrapper(_context, array, _depth + 1).setValue(str, member);
+ }
+ _value.setObjectOrNull(array);
+ return;
+ }
+ case mongo::Object:
+ fromBSON(elem.embeddedObject(), readOnly);
+ return;
+ case mongo::Date:
+ _value.setObjectOrNull(
+ JS_NewDateObjectMsec(_context, elem.Date().toMillisSinceEpoch()));
+ return;
+ case mongo::Bool:
+ _value.setBoolean(elem.Bool());
+ return;
+ case mongo::EOO:
+ case mongo::jstNULL:
+ case mongo::Undefined:
+ _value.setNull();
+ return;
+ case mongo::RegEx: {
+ // TODO parse into a custom type that can support any patterns and flags SERVER-9803
+
+ JS::AutoValueArray<2> args(_context);
+
+ ValueReader(_context, args[0]).fromStringData(elem.regex());
+ ValueReader(_context, args[1]).fromStringData(elem.regexFlags());
+
+ JS::RootedObject obj(_context);
+ scope->getRegExpProto().newInstance(args, &obj);
+
+ _value.setObjectOrNull(obj);
+
+ return;
+ }
+ case mongo::BinData: {
+ int len;
+ const char* data = elem.binData(len);
+ std::stringstream ss;
+ base64::encode(ss, data, len);
+
+ JS::AutoValueArray<2> args(_context);
+
+ args[0].setInt32(elem.binDataType());
+
+ ValueReader(_context, args[1]).fromStringData(ss.str());
+
+ scope->getBinDataProto().newInstance(args, _value);
+ return;
+ }
+ case mongo::bsonTimestamp: {
+ JS::AutoValueArray<2> args(_context);
+
+ args[0].setDouble(elem.timestampTime().toMillisSinceEpoch() / 1000);
+ args[1].setNumber(elem.timestampInc());
+
+ scope->getTimestampProto().newInstance(args, _value);
+
+ return;
+ }
+ case mongo::NumberLong: {
+ unsigned long long nativeUnsignedLong = elem.numberLong();
+ // values above 2^53 are not accurately represented in JS
+ if (static_cast<long long>(nativeUnsignedLong) ==
+ static_cast<long long>(
+ static_cast<double>(static_cast<long long>(nativeUnsignedLong))) &&
+ nativeUnsignedLong < 9007199254740992ULL) {
+ JS::AutoValueArray<1> args(_context);
+ args[0].setNumber(static_cast<double>(static_cast<long long>(nativeUnsignedLong)));
+
+ scope->getNumberLongProto().newInstance(args, _value);
+ } else {
+ JS::AutoValueArray<3> args(_context);
+ args[0].setNumber(static_cast<double>(static_cast<long long>(nativeUnsignedLong)));
+ args[1].setDouble(nativeUnsignedLong >> 32);
+ args[2].setDouble(
+ static_cast<unsigned long>(nativeUnsignedLong & 0x00000000ffffffff));
+ scope->getNumberLongProto().newInstance(args, _value);
+ }
+
+ return;
+ }
+ case mongo::MinKey:
+ scope->getMinKeyProto().newInstance(_value);
+ return;
+ case mongo::MaxKey:
+ scope->getMaxKeyProto().newInstance(_value);
+ return;
+ case mongo::DBRef: {
+ JS::AutoValueArray<1> oidArgs(_context);
+ ValueReader(_context, oidArgs[0]).fromStringData(elem.dbrefOID().toString());
+
+ JS::AutoValueArray<2> dbPointerArgs(_context);
+ ValueReader(_context, dbPointerArgs[0]).fromStringData(elem.dbrefNS());
+ scope->getOidProto().newInstance(oidArgs, dbPointerArgs[1]);
+
+ scope->getDbPointerProto().newInstance(dbPointerArgs, _value);
+ return;
+ }
+ default:
+ massert(16661,
+ str::stream() << "can't handle type: " << elem.type() << " " << elem.toString(),
+ false);
+ break;
+ }
+
+ _value.setUndefined();
+}
+
+void ValueReader::fromBSON(const BSONObj& obj, bool readOnly) {
+ if (obj.firstElementType() == String && str::equals(obj.firstElementFieldName(), "$ref")) {
+ BSONObjIterator it(obj);
+ const BSONElement ref = it.next();
+ const BSONElement id = it.next();
+
+ if (id.ok() && str::equals(id.fieldName(), "$id")) {
+ JS::AutoValueArray<2> args(_context);
+
+ ValueReader(_context, args[0]).fromBSONElement(ref, readOnly);
+
+ // id can be a subobject
+ ValueReader(_context, args[1], _depth + 1).fromBSONElement(id, readOnly);
+
+ JS::RootedObject obj(_context);
+
+ auto scope = getScope(_context);
+
+ scope->getDbRefProto().newInstance(args, &obj);
+ ObjectWrapper o(_context, obj);
+
+ while (it.more()) {
+ BSONElement elem = it.next();
+ o.setBSONElement(elem.fieldName(), elem, readOnly);
+ }
+
+ _value.setObjectOrNull(obj);
+ return;
+ }
+ }
+
+ JS::RootedObject child(_context);
+ BSONInfo::make(_context, &child, obj, readOnly);
+
+ _value.setObjectOrNull(child);
+}
+
+/**
+ * SpiderMonkey doesn't have a direct entry point to create a jsstring from
+ * utf8, so we have to flow through some slightly less public interfaces.
+ *
+ * Basically, we have to use their routines to convert to utf16, then assign
+ * those bytes with JS_NewUCStringCopyN
+ */
+void ValueReader::fromStringData(StringData sd) {
+ size_t utf16Len;
+
+ // TODO: we have tests that involve dropping garbage in. Do we want to
+ // throw, or to take the lossy conversion?
+ auto utf16 = JS::LossyUTF8CharsToNewTwoByteCharsZ(
+ _context, JS::UTF8Chars(sd.rawData(), sd.size()), &utf16Len);
+
+ mozilla::UniquePtr<char16_t, JS::FreePolicy> utf16Deleter(utf16.get());
+
+ uassert(ErrorCodes::JSInterpreterFailure,
+ str::stream() << "Failed to encode \"" << sd << "\" as utf16",
+ utf16);
+
+ auto jsStr = JS_NewUCStringCopyN(_context, utf16.get(), utf16Len);
+
+ uassert(ErrorCodes::JSInterpreterFailure,
+ str::stream() << "Unable to copy \"" << sd << "\" into MozJS",
+ jsStr);
+
+ _value.setString(jsStr);
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/valuereader.h b/src/mongo/scripting/mozjs/valuereader.h
new file mode 100644
index 00000000000..be916ccbc25
--- /dev/null
+++ b/src/mongo/scripting/mozjs/valuereader.h
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+#include <string>
+
+#include "mongo/bson/bsonobj.h"
+
+namespace mongo {
+namespace mozjs {
+
+/**
+ * Reads into a JS Value from some Mongo C++ primitive
+ */
+class ValueReader {
+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);
+
+ void fromBSONElement(const BSONElement& elem, bool readOnly);
+ void fromBSON(const BSONObj& obj, bool readOnly);
+ void fromStringData(StringData sd);
+
+private:
+ JSContext* _context;
+ JS::MutableHandleValue _value;
+ int _depth;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/valuewriter.cpp b/src/mongo/scripting/mozjs/valuewriter.cpp
new file mode 100644
index 00000000000..31928f7ab44
--- /dev/null
+++ b/src/mongo/scripting/mozjs/valuewriter.cpp
@@ -0,0 +1,252 @@
+/**
+ * Copyright (C) 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/scripting/mozjs/valuewriter.h"
+
+#include <js/Conversions.h>
+
+#include "mongo/base/error_codes.h"
+#include "mongo/scripting/mozjs/exception.h"
+#include "mongo/scripting/mozjs/implscope.h"
+#include "mongo/scripting/mozjs/jsstringwrapper.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/scripting/mozjs/valuereader.h"
+#include "mongo/util/base64.h"
+
+namespace mongo {
+namespace mozjs {
+
+ValueWriter::ValueWriter(JSContext* cx, JS::HandleValue value, int depth)
+ : _context(cx), _value(value), _depth(depth), _originalParent(nullptr) {}
+
+void ValueWriter::setOriginalBSON(BSONObj* obj) {
+ _originalParent = obj;
+}
+
+int ValueWriter::type() {
+ if (_value.isNull())
+ return jstNULL;
+ if (_value.isUndefined())
+ return Undefined;
+ if (_value.isString())
+ return String;
+ if (JS_IsArrayObject(_context, _value))
+ return Array;
+ if (_value.isBoolean())
+ return Bool;
+
+ // We could do something more sophisticated here by checking to see if we
+ // round trip through int32_t, int64_t and double and picking a type that
+ // way, for now just always come back as double for numbers though (it's
+ // what we did for v8)
+ if (_value.isNumber())
+ return NumberDouble;
+
+ if (_value.isObject()) {
+ JS::RootedObject obj(_context, _value.toObjectOrNull());
+ if (JS_ObjectIsDate(_context, obj))
+ return Date;
+ if (JS_ObjectIsFunction(_context, obj))
+ return Code;
+
+ return Object;
+ }
+
+ uasserted(ErrorCodes::BadValue, "unable to get type");
+}
+
+BSONObj ValueWriter::toBSON() {
+ if (!_value.isObject())
+ return BSONObj();
+
+ JS::RootedObject obj(_context, _value.toObjectOrNull());
+
+ if (getScope(_context)->getBsonProto().instanceOf(obj)) {
+ BSONObj* originalBSON;
+ bool altered;
+
+ std::tie(originalBSON, altered) = BSONInfo::originalBSON(_context, obj);
+
+ if (!altered)
+ return *originalBSON;
+ }
+
+ BSONObjBuilder bob;
+ ObjectWrapper(_context, obj, _depth).writeThis(&bob);
+
+ return bob.obj();
+}
+
+std::string ValueWriter::toString() {
+ return JSStringWrapper(_context, JS::ToString(_context, _value)).toString();
+}
+
+double ValueWriter::toNumber() {
+ double out;
+ if (JS::ToNumber(_context, _value, &out))
+ return out;
+
+ throwCurrentJSException(_context, ErrorCodes::BadValue, "Failure to convert value to number");
+}
+
+bool ValueWriter::toBoolean() {
+ return JS::ToBoolean(_value);
+}
+
+int32_t ValueWriter::toInt32() {
+ int32_t out;
+ if (JS::ToInt32(_context, _value, &out))
+ return out;
+
+ throwCurrentJSException(_context, ErrorCodes::BadValue, "Failure to convert value to number");
+}
+
+int64_t ValueWriter::toInt64() {
+ int64_t out;
+ if (JS::ToInt64(_context, _value, &out))
+ return out;
+
+ throwCurrentJSException(_context, ErrorCodes::BadValue, "Failure to convert value to number");
+}
+
+void ValueWriter::writeThis(BSONObjBuilder* b, StringData sd) {
+ uassert(17279,
+ str::stream() << "Exceeded depth limit of " << 150
+ << " when converting js object to BSON. Do you have a cycle?",
+ _depth < 149);
+
+ // Null char should be at the end, not in the string
+ uassert(16985,
+ str::stream() << "JavaScript property (name) contains a null char "
+ << "which is not allowed in BSON. "
+ << (_originalParent ? _originalParent->jsonString() : ""),
+ (std::string::npos == sd.find('\0')));
+
+ if (_value.isString()) {
+ b->append(sd, toString());
+ } else if (_value.isNumber()) {
+ double val = toNumber();
+
+ // if previous type was integer, keep it
+ int intval = static_cast<int>(val);
+
+ if (val == intval && _originalParent) {
+ // This makes copying an object of numbers O(n**2) :(
+ BSONElement elmt = _originalParent->getField(sd);
+ if (elmt.type() == mongo::NumberInt) {
+ b->append(sd, intval);
+ return;
+ }
+ }
+
+ b->append(sd, val);
+ } else if (_value.isObject()) {
+ JS::RootedObject childObj(_context, _value.toObjectOrNull());
+ _writeObject(b, sd, childObj);
+ } else if (_value.isBoolean()) {
+ b->appendBool(sd, _value.toBoolean());
+ } else if (_value.isUndefined()) {
+ b->appendUndefined(sd);
+ } else if (_value.isNull()) {
+ b->appendNull(sd);
+ } else {
+ uasserted(16662,
+ str::stream() << "unable to convert JavaScript property to mongo element " << sd);
+ }
+}
+
+void ValueWriter::_writeObject(BSONObjBuilder* b, StringData sd, JS::HandleObject obj) {
+ auto scope = getScope(_context);
+
+ ObjectWrapper o(_context, obj, _depth);
+
+ if (JS_ObjectIsFunction(_context, _value.toObjectOrNull())) {
+ uassert(16716,
+ "cannot convert native function to BSON",
+ !scope->getNativeFunctionProto().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->getOidProto().instanceOf(obj)) {
+ b->append(sd, OID(o.getString("str")));
+ } else if (scope->getNumberLongProto().instanceOf(obj)) {
+ long long out = NumberLongInfo::ToNumberLong(_context, obj);
+ b->append(sd, out);
+ } else if (scope->getNumberIntProto().instanceOf(obj)) {
+ b->append(sd, NumberIntInfo::ToNumberInt(_context, obj));
+ } else if (scope->getDbPointerProto().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->getBinDataProto().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->getTimestampProto().instanceOf(obj)) {
+ Timestamp ot(o.getNumber("t"), o.getNumber("i"));
+ b->append(sd, ot);
+ } else if (scope->getMinKeyProto().instanceOf(obj)) {
+ b->appendMinKey(sd);
+ } else if (scope->getMaxKeyProto().instanceOf(obj)) {
+ b->appendMaxKey(sd);
+ } else {
+ // nested object or array
+
+ BSONObjBuilder subbob(JS_IsArrayObject(_context, obj) ? b->subarrayStart(sd)
+ : b->subobjStart(sd));
+
+ ObjectWrapper child(_context, obj, _depth + 1);
+
+ child.writeThis(b);
+ }
+}
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/valuewriter.h b/src/mongo/scripting/mozjs/valuewriter.h
new file mode 100644
index 00000000000..47358398f81
--- /dev/null
+++ b/src/mongo/scripting/mozjs/valuewriter.h
@@ -0,0 +1,84 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+#include <string>
+
+#include "mongo/bson/bsonobj.h"
+
+namespace mongo {
+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);
+
+ BSONObj toBSON();
+
+ /**
+ * These coercions flow through JS::To_X. I.e. they can call toString() or
+ * toNumber()
+ */
+ std::string toString();
+ int type();
+ double toNumber();
+ int32_t toInt32();
+ int64_t toInt64();
+ bool toBoolean();
+
+ /**
+ * Writes the value into a bsonobjbuilder under the name in sd.
+ */
+ void writeThis(BSONObjBuilder* b, StringData sd);
+
+ void setOriginalBSON(BSONObj* obj);
+
+private:
+ /**
+ * Writes the object into a bsonobjbuilder under the name in sd.
+ */
+ void _writeObject(BSONObjBuilder* b, StringData sd, JS::HandleObject obj);
+
+ JSContext* _context;
+ JS::HandleValue _value;
+ int _depth;
+ BSONObj* _originalParent;
+};
+
+} // namespace mozjs
+} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/wraptype.h b/src/mongo/scripting/mozjs/wraptype.h
new file mode 100644
index 00000000000..c5ca6095e76
--- /dev/null
+++ b/src/mongo/scripting/mozjs/wraptype.h
@@ -0,0 +1,474 @@
+/**
+ * Copyright (C) 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 <jsapi.h>
+#include <type_traits>
+
+#include "mongo/scripting/mozjs/base.h"
+#include "mongo/scripting/mozjs/exception.h"
+#include "mongo/scripting/mozjs/objectwrapper.h"
+#include "mongo/util/assert_util.h"
+
+// The purpose of this class is to take in specially crafted types and generate
+// a wrapper which installs the type, along with any useful life cycle methods
+// and free functions that might be associated with it. The template magic in
+// here, along with some useful macros, hides a lot of the implementation
+// complexity of exposing C++ code into javascript. Most prominently, we have
+// to wrap every function that can be called from javascript to prevent any C++
+// exceptions from leaking out. We do this, with template and macro based
+// codegen, and turn mongo exceptions into instances of Status, then convert
+// those into javascript exceptions before returning. That allows all consumers
+// of this library to throw exceptions freely, with the understanding that
+// they'll be visible in javascript. Javascript exceptions are trapped at the
+// top level and converted back to mongo exceptions by an error handler on
+// ImplScope.
+
+// MONGO_*_JS_FUNCTION_* macros are public and allow wrapped types to install
+// their own functions on types and into the global scope
+#define MONGO_DEFINE_JS_FUNCTION(name) \
+ static void name(JSContext* cx, JS::CallArgs args); \
+ static bool WRAPPER_##name(JSContext* cx, unsigned argc, JS::Value* vp) { \
+ try { \
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \
+ name(cx, args); \
+ return true; \
+ } catch (...) { \
+ mongoToJSException(cx); \
+ return false; \
+ } \
+ }
+
+#define MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(name, flags) \
+ JS_FS(#name, Functions::WRAPPER_##name, 0, flags)
+
+#define MONGO_ATTACH_JS_FUNCTION(name) MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(name, 0)
+
+namespace mongo {
+namespace mozjs {
+
+namespace smUtils {
+
+// Now all the spidermonkey type methods
+template <typename T>
+static bool addProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue v) {
+ try {
+ T::addProperty(cx, obj, id, v);
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+template <typename T>
+static bool call(JSContext* cx, unsigned argc, JS::Value* vp) {
+ try {
+ T::call(cx, JS::CallArgsFromVp(argc, vp));
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+template <typename T>
+static bool construct(JSContext* cx, unsigned argc, JS::Value* vp) {
+ try {
+ T::construct(cx, JS::CallArgsFromVp(argc, vp));
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+template <typename T>
+static bool convert(JSContext* cx, JS::HandleObject obj, JSType type, JS::MutableHandleValue vp) {
+ try {
+ T::convert(cx, obj, type, vp);
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+template <typename T>
+static bool delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded) {
+ try {
+ T::delProperty(cx, obj, id, succeeded);
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+template <typename T>
+static bool enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties) {
+ try {
+ T::enumerate(cx, obj, properties);
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+template <typename T>
+static bool getProperty(JSContext* cx,
+ JS::HandleObject obj,
+ JS::HandleId id,
+ JS::MutableHandleValue vp) {
+ try {
+ T::getProperty(cx, obj, id, vp);
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+template <typename T>
+static bool hasInstance(JSContext* cx, JS::HandleObject obj, JS::MutableHandleValue vp, bool* bp) {
+ try {
+ T::hasInstance(cx, obj, vp, bp);
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+template <typename T>
+static bool setProperty(
+ JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict, JS::MutableHandleValue vp) {
+ try {
+ T::setProperty(cx, obj, id, strict, vp);
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+template <typename T>
+static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) {
+ try {
+ T::resolve(cx, obj, id, resolvedp);
+ return true;
+ } catch (...) {
+ mongoToJSException(cx);
+ return false;
+ }
+};
+
+} // namespace smUtils
+
+template <typename T>
+class WrapType : public T {
+public:
+ WrapType(JSContext* context)
+ : _context(context),
+ _proto(),
+ _jsclass({T::className,
+ T::classFlags,
+ T::addProperty != BaseInfo::addProperty ? smUtils::addProperty<T> : nullptr,
+ T::delProperty != BaseInfo::delProperty ? smUtils::delProperty<T> : nullptr,
+ T::getProperty != BaseInfo::getProperty ? smUtils::getProperty<T> : nullptr,
+ T::setProperty != BaseInfo::setProperty ? smUtils::setProperty<T> : nullptr,
+ // We don't use the regular enumerate because we want the fancy new one
+ nullptr,
+ T::resolve != BaseInfo::resolve ? smUtils::resolve<T> : nullptr,
+ T::convert != BaseInfo::convert ? smUtils::convert<T> : nullptr,
+ T::finalize != BaseInfo::finalize ? T::finalize : nullptr,
+ T::call != BaseInfo::call ? smUtils::call<T> : nullptr,
+ T::hasInstance != BaseInfo::hasInstance ? smUtils::hasInstance<T> : nullptr,
+ T::construct != BaseInfo::construct ? smUtils::construct<T> : nullptr,
+ nullptr}) {
+ _installEnumerate(T::enumerate != BaseInfo::enumerate ? smUtils::enumerate<T> : nullptr);
+
+ // The global object is different. We need it for basic setup
+ // before the other types are installed. Might as well just do it
+ // in the constructor.
+ if (T::classFlags & JSCLASS_GLOBAL_FLAGS) {
+ JS::RootedObject proto(_context);
+
+ _proto.init(_context,
+ _assertPtr(JS_NewGlobalObject(
+ _context, &_jsclass, nullptr, JS::DontFireOnNewGlobalHook)));
+
+ JSAutoCompartment ac(_context, _proto);
+ _installFunctions(_proto, T::freeFunctions);
+ }
+ }
+
+ ~WrapType() {
+ // Persistent globals don't RAII, you have to reset() them manually
+ _proto.reset();
+ }
+
+ void install(JS::HandleObject global) {
+ switch (static_cast<InstallType>(T::installType)) {
+ case InstallType::Global:
+ _installGlobal(global);
+ break;
+ case InstallType::Private:
+ _installPrivate(global);
+ break;
+ case InstallType::OverNative:
+ _installOverNative(global);
+ break;
+ }
+ }
+
+ /**
+ * newObject methods don't invoke the constructor. So they're good for
+ * types without a constructor or inside the constructor
+ */
+ void newObject(JS::MutableHandleObject out) {
+ // The regular form of JS_NewObject, where we pass proto as the
+ // third param, actually does a global object lookup for some
+ // reason. This way allows object creation with non-public
+ // prototypes and if someone deletes the symbol up the chain.
+ out.set(_assertPtr(JS_NewObject(_context, &_jsclass, JS::NullPtr())));
+
+ if (!JS_SetPrototype(_context, out, _proto))
+ throwCurrentJSException(
+ _context, ErrorCodes::JSInterpreterFailure, "Failed to set prototype");
+ }
+
+ void newObject(JS::MutableHandleValue out) {
+ JS::RootedObject obj(_context);
+ newObject(&obj);
+
+ out.setObjectOrNull(obj);
+ }
+
+ /**
+ * newInstance calls the constructor, a la new Type() in js
+ */
+ void newInstance(JS::MutableHandleObject out) {
+ JS::AutoValueVector args(_context);
+
+ newInstance(args, out);
+ }
+
+ void newInstance(const JS::HandleValueArray& args, JS::MutableHandleObject out) {
+ out.set(_assertPtr(JS_New(_context, _proto, args)));
+ }
+
+ void newInstance(JS::MutableHandleValue out) {
+ JS::AutoValueVector args(_context);
+
+ newInstance(args, out);
+ }
+
+ void newInstance(const JS::HandleValueArray& args, JS::MutableHandleValue out) {
+ out.setObjectOrNull(_assertPtr(JS_New(_context, _proto, args)));
+ }
+
+ // instanceOf doesn't go up the prototype tree. It's a lower level more specific match
+ bool instanceOf(JS::HandleObject obj) {
+ return JS_InstanceOf(_context, obj, &_jsclass, nullptr);
+ }
+
+ bool instanceOf(JS::HandleValue value) {
+ if (!value.isObject())
+ return false;
+
+ JS::RootedObject obj(_context, value.toObjectOrNull());
+
+ return instanceOf(obj);
+ }
+
+ const JSClass* getJSClass() const {
+ return &_jsclass;
+ }
+
+ JS::HandleObject getProto() const {
+ return _proto;
+ }
+
+private:
+ /**
+ * Use this if you want your types installed visibly in the global scope
+ */
+ void _installGlobal(JS::HandleObject global) {
+ JS::RootedObject parent(_context);
+ _inheritFrom(T::inheritFrom, global, &parent);
+
+ _proto.init(_context,
+ _assertPtr(JS_InitClass(
+ _context,
+ global,
+ parent,
+ &_jsclass,
+ T::construct != BaseInfo::construct ? smUtils::construct<T> : nullptr,
+ 0,
+ nullptr,
+ T::methods,
+ nullptr,
+ nullptr)));
+
+ _installFunctions(global, T::freeFunctions);
+ _postInstall(global, T::postInstall);
+ }
+
+ // Use this if you want your types installed, but not visible in the
+ // global scope
+ void _installPrivate(JS::HandleObject global) {
+ JS::RootedObject parent(_context);
+ _inheritFrom(T::inheritFrom, global, &parent);
+
+ // See newObject() for why we have to do this dance with the explicit
+ // SetPrototype
+ _proto.init(_context, _assertPtr(JS_NewObject(_context, &_jsclass, JS::NullPtr())));
+ if (parent.get() && !JS_SetPrototype(_context, _proto, parent))
+ throwCurrentJSException(
+ _context, ErrorCodes::JSInterpreterFailure, "Failed to set prototype");
+
+ _installFunctions(_proto, T::methods);
+ _installFunctions(global, T::freeFunctions);
+
+ _installConstructor(T::construct != BaseInfo::construct ? smUtils::construct<T> : nullptr);
+
+ _postInstall(global, T::postInstall);
+ }
+
+ // Use this to attach things to types that we don't provide like
+ // Object, or Array
+ void _installOverNative(JS::HandleObject global) {
+ JS::RootedValue value(_context);
+ if (!JS_GetProperty(_context, global, T::className, &value))
+ throwCurrentJSException(
+ _context, ErrorCodes::JSInterpreterFailure, "Couldn't get className property");
+
+ if (!value.isObject())
+ uasserted(ErrorCodes::BadValue, "className isn't object");
+
+ _proto.init(_context, value.toObjectOrNull());
+
+ _installFunctions(_proto, T::methods);
+ _installFunctions(global, T::freeFunctions);
+ _postInstall(global, T::postInstall);
+ }
+
+ void _installFunctions(JS::HandleObject global, const JSFunctionSpec* fs) {
+ if (!fs)
+ return;
+ if (JS_DefineFunctions(_context, global, fs))
+ return;
+
+ throwCurrentJSException(
+ _context, ErrorCodes::JSInterpreterFailure, "Failed to define functions");
+ }
+
+ // We have to do this awkward dance to set the new style enumeration.
+ // You used to be able to set this with JSCLASS_NEW_ENUMERATE in class
+ // flags, in the future you'll probably only set ObjectOps, but for now
+ // we have this. There are a host of static_asserts in js/Class.h that
+ // ensure that these two structures are equal.
+ //
+ // This is a landmine to watch out for during upgrades
+ using enumerateT = bool (*)(JSContext*, JS::HandleObject, JS::AutoIdVector&);
+ void _installEnumerate(enumerateT enumerate) {
+ if (!enumerate)
+ return;
+
+ auto implClass = reinterpret_cast<js::Class*>(&_jsclass);
+
+ implClass->ops.enumerate = enumerate;
+ }
+
+ // This is for inheriting from something other than Object
+ void _inheritFrom(const char* name, JS::HandleObject global, JS::MutableHandleObject out) {
+ if (!name)
+ return;
+
+ JS::RootedValue val(_context);
+
+ if (!JS_GetProperty(_context, global, name, &val)) {
+ throwCurrentJSException(
+ _context, ErrorCodes::JSInterpreterFailure, "Failed to get parent");
+ }
+
+ if (!val.isObject()) {
+ uasserted(ErrorCodes::JSInterpreterFailure, "Parent is not an object");
+ }
+
+ out.set(val.toObjectOrNull());
+ }
+
+ using postInstallT = void (*)(JSContext*, JS::HandleObject, JS::HandleObject);
+ void _postInstall(JS::HandleObject global, postInstallT postInstall) {
+ if (!postInstall)
+ return;
+
+ postInstall(_context, global, _proto);
+ }
+
+ void _installConstructor(JSNative ctor) {
+ if (!ctor)
+ return;
+
+ auto ptr = JS_NewFunction(_context, ctor, 0, JSFUN_CONSTRUCTOR, JS::NullPtr(), nullptr);
+ if (!ptr) {
+ throwCurrentJSException(
+ _context, ErrorCodes::JSInterpreterFailure, "Failed to install constructor");
+ }
+
+ JS::RootedObject ctorObj(_context, JS_GetFunctionObject(ptr));
+
+ if (!JS_LinkConstructorAndPrototype(_context, ctorObj, _proto))
+ throwCurrentJSException(_context,
+ ErrorCodes::JSInterpreterFailure,
+ "Failed to link constructor and prototype");
+ }
+
+ JSObject* _assertPtr(JSObject* ptr) {
+ if (!ptr)
+ throwCurrentJSException(
+ _context, ErrorCodes::JSInterpreterFailure, "Failed to JS_NewX");
+
+ return ptr;
+ }
+
+ JSContext* _context;
+ JS::PersistentRootedObject _proto;
+ JSClass _jsclass;
+};
+
+} // namespace mozjs
+} // namespace mongo