summaryrefslogtreecommitdiff
path: root/src/mongo/scripting/mozjs/wraptype.h
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/mozjs/wraptype.h
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/mozjs/wraptype.h')
-rw-r--r--src/mongo/scripting/mozjs/wraptype.h474
1 files changed, 474 insertions, 0 deletions
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