diff options
Diffstat (limited to 'src/mongo/scripting/engine_v8.h')
-rw-r--r-- | src/mongo/scripting/engine_v8.h | 1027 |
1 files changed, 539 insertions, 488 deletions
diff --git a/src/mongo/scripting/engine_v8.h b/src/mongo/scripting/engine_v8.h index ae4f8f4c04d..8825580c987 100644 --- a/src/mongo/scripting/engine_v8.h +++ b/src/mongo/scripting/engine_v8.h @@ -1,4 +1,4 @@ -//engine_v8.h +// engine_v8.h /* Copyright 2009 10gen Inc. * @@ -46,528 +46,579 @@ * that work with v8 handles (and/or must be within the V8Scope's isolate * and context). Be sure to close the handle_scope if returning a v8::Handle! */ -#define V8_SIMPLE_HEADER \ - v8::Locker v8lock(_isolate); /* acquire isolate lock */ \ - v8::Isolate::Scope iscope(_isolate); /* enter the isolate; exit when out of scope */ \ - v8::HandleScope handle_scope; /* make the current scope own local handles */ \ - v8::Context::Scope context_scope(_context); /* enter the context; exit when out of scope */ +#define V8_SIMPLE_HEADER \ + v8::Locker v8lock(_isolate); /* acquire isolate lock */ \ + v8::Isolate::Scope iscope(_isolate); /* enter the isolate; exit when out of scope */ \ + v8::HandleScope handle_scope; /* make the current scope own local handles */ \ + v8::Context::Scope context_scope(_context); /* enter the context; exit when out of scope */ namespace mongo { - class V8ScriptEngine; - class V8Scope; - class BSONHolder; - class JSThreadConfig; +class V8ScriptEngine; +class V8Scope; +class BSONHolder; +class JSThreadConfig; - typedef v8::Handle<v8::Value> (*v8Function)(V8Scope* scope, const v8::Arguments& args); +typedef v8::Handle<v8::Value>(*v8Function)(V8Scope* scope, const v8::Arguments& args); +/** + * The ObjTracker class keeps track of all weakly referenced v8 objects. This is + * required because v8 does not invoke the WeakReferenceCallback when shutting down + * the context/isolate. To track a new object, add an ObjTracker<MyObjType> member + * variable to the V8Scope (if one does not already exist for that type). Instead + * of calling v8::Persistent::MakeWeak() directly, simply invoke track() with the + * persistent handle and the pointer to be freed. + */ +template <typename _ObjType> +class ObjTracker { +public: + /** Track an object to be freed when it is no longer referenced in JavaScript. + * Return handle to object instance shared pointer. + * @param instanceHandle persistent handle to the weakly referenced object + * @param rawData pointer to the object instance + */ + v8::Local<v8::External> track(v8::Persistent<v8::Value> instanceHandle, _ObjType* instance) { + TrackedPtr* collectionHandle = new TrackedPtr(instance, this); + _container.insert(collectionHandle); + instanceHandle.MakeWeak(collectionHandle, deleteOnCollect); + return v8::External::New(&(collectionHandle->_objPtr)); + } /** - * The ObjTracker class keeps track of all weakly referenced v8 objects. This is - * required because v8 does not invoke the WeakReferenceCallback when shutting down - * the context/isolate. To track a new object, add an ObjTracker<MyObjType> member - * variable to the V8Scope (if one does not already exist for that type). Instead - * of calling v8::Persistent::MakeWeak() directly, simply invoke track() with the - * persistent handle and the pointer to be freed. + * Free any remaining objects and their TrackedPtrs. Invoked when the + * V8Scope is destructed. */ - template <typename _ObjType> - class ObjTracker { - public: - /** Track an object to be freed when it is no longer referenced in JavaScript. - * Return handle to object instance shared pointer. - * @param instanceHandle persistent handle to the weakly referenced object - * @param rawData pointer to the object instance - */ - v8::Local<v8::External> track(v8::Persistent<v8::Value> instanceHandle, - _ObjType* instance) { - TrackedPtr* collectionHandle = new TrackedPtr(instance, this); - _container.insert(collectionHandle); - instanceHandle.MakeWeak(collectionHandle, deleteOnCollect); - return v8::External::New(&(collectionHandle->_objPtr)); - } - /** - * Free any remaining objects and their TrackedPtrs. Invoked when the - * V8Scope is destructed. - */ - ~ObjTracker() { - typename std::set<TrackedPtr*>::iterator it = _container.begin(); - while (it != _container.end()) { - delete *it; - _container.erase(it++); - } - } - private: - /** - * Simple struct which contains a pointer to the tracked object, and a pointer - * to the ObjTracker which owns it. This is the argument supplied to v8's - * WeakReferenceCallback and MakeWeak(). - */ - struct TrackedPtr { - public: - TrackedPtr(_ObjType* instance, ObjTracker<_ObjType>* tracker) : - _objPtr(instance), - _tracker(tracker) { } - std::shared_ptr<_ObjType> _objPtr; - ObjTracker<_ObjType>* _tracker; - }; - - /** - * v8 callback for weak persistent handles that have been marked for removal by the - * garbage collector. Signature conforms to v8's WeakReferenceCallback. - * @param instanceHandle persistent handle to the weakly referenced object - * @param rawData pointer to the TrackedPtr instance - */ - static void deleteOnCollect(v8::Persistent<v8::Value> instanceHandle, void* rawData) { - TrackedPtr* trackedPtr = static_cast<TrackedPtr*>(rawData); - trackedPtr->_tracker->_container.erase(trackedPtr); - delete trackedPtr; - instanceHandle.Dispose(); + ~ObjTracker() { + typename std::set<TrackedPtr*>::iterator it = _container.begin(); + while (it != _container.end()) { + delete *it; + _container.erase(it++); } + } - // container for all TrackedPtrs created by this ObjTracker instance - std::set<TrackedPtr*> _container; +private: + /** + * Simple struct which contains a pointer to the tracked object, and a pointer + * to the ObjTracker which owns it. This is the argument supplied to v8's + * WeakReferenceCallback and MakeWeak(). + */ + struct TrackedPtr { + public: + TrackedPtr(_ObjType* instance, ObjTracker<_ObjType>* tracker) + : _objPtr(instance), _tracker(tracker) {} + std::shared_ptr<_ObjType> _objPtr; + ObjTracker<_ObjType>* _tracker; }; /** - * A V8Scope represents a unit of javascript execution environment; specifically a single - * isolate and a single context executing in a single mongo thread. A V8Scope can be reused - * in another thread only after reset() has been called. - * - * NB: - * - v8 objects/handles/etc. cannot be shared between V8Scopes - * - in mongod, each scope is associated with an opId (for KillOp support) - * - any public functions that call the v8 API should use a V8_SIMPLE_HEADER - * - the caller of any public function that returns a v8 type or has a v8 handle argument - * must enter the isolate, context, and set up the appropriate handle scope + * v8 callback for weak persistent handles that have been marked for removal by the + * garbage collector. Signature conforms to v8's WeakReferenceCallback. + * @param instanceHandle persistent handle to the weakly referenced object + * @param rawData pointer to the TrackedPtr instance */ - class V8Scope : public Scope { - public: + static void deleteOnCollect(v8::Persistent<v8::Value> instanceHandle, void* rawData) { + TrackedPtr* trackedPtr = static_cast<TrackedPtr*>(rawData); + trackedPtr->_tracker->_container.erase(trackedPtr); + delete trackedPtr; + instanceHandle.Dispose(); + } - V8Scope(V8ScriptEngine* engine); - ~V8Scope(); - - virtual void init(const BSONObj* data); - - /** - * Reset the state of this scope for use by another thread or operation - */ - virtual void reset(); - - /** - * Terminate this scope - */ - virtual void kill(); - - /** check if there is a pending killOp request */ - bool isKillPending() const; - - /** - * Register this scope with the mongo op id. If executing outside the - * context of a mongo operation (e.g. from the shell), killOp will not - * be supported. - */ - virtual void registerOperation(OperationContext* txn); - - /** - * Unregister this scope with the mongo op id. - */ - virtual void unregisterOperation(); - - /** - * Obtains the operation context associated with this Scope, so it can be given to the - * DBDirectClient used by the V8 engine's connection. Only needed for dbEval. - */ - OperationContext* getOpContext() const; - - /** - * Connect to a local database, create a Mongo object instance, and load any - * server-side js into the global object - */ - virtual void localConnectForDbEval(OperationContext* txn, const char* dbName); - - virtual void externalSetup(); - - virtual void installDBAccess(); - - virtual void installBSONTypes(); - - virtual std::string getError() { return _error; } - - virtual bool hasOutOfMemoryException(); - - /** - * Run the garbage collector on this scope (native function). @see GCV8 for the - * javascript binding version. - */ - void gc(); - - /** - * get a global property. caller must set up the v8 state. - */ - v8::Handle<v8::Value> get(const char* field); - - virtual double getNumber(const char* field); - virtual int getNumberInt(const char* field); - virtual long long getNumberLongLong(const char* field); - virtual std::string getString(const char* field); - virtual bool getBoolean(const char* field); - virtual BSONObj getObject(const char* field); - - virtual void setNumber(const char* field, double val); - virtual void setString(const char* field, StringData val); - virtual void setBoolean(const char* field, bool val); - virtual void setElement(const char* field, const BSONElement& e); - virtual void setObject(const char* field, const BSONObj& obj, bool readOnly); - virtual void setFunction(const char* field, const char* code); - - virtual int type(const char* field); - - virtual void rename(const char* from, const char* to); - - virtual int invoke(ScriptingFunction func, const BSONObj* args, const BSONObj* recv, - int timeoutMs = 0, bool ignoreReturn = false, - bool readOnlyArgs = false, bool readOnlyRecv = false); - - virtual bool exec(StringData code, const std::string& name, bool printResult, - bool reportError, bool assertOnError, int timeoutMs); - - // functions to create v8 object and function templates - virtual void injectNative(const char* field, NativeFunction func, void* data = 0); - void injectNative(const char* field, NativeFunction func, v8::Handle<v8::Object>& obj, - void* data = 0); - - // These functions inject a function (either an unwrapped function pointer or a pre-wrapped - // FunctionTemplate) into the provided object. If no object is provided, the function will - // be injected at global scope. These functions take care of setting the function and class - // name on the returned FunctionTemplate. - v8::Handle<v8::FunctionTemplate> injectV8Function(const char* name, v8Function func); - v8::Handle<v8::FunctionTemplate> injectV8Function(const char* name, - v8Function func, - v8::Handle<v8::Object>& obj); - v8::Handle<v8::FunctionTemplate> injectV8Function(const char* name, - v8::Handle<v8::FunctionTemplate> ft, - v8::Handle<v8::Object>& obj); - - // Injects a method into the provided prototype - v8::Handle<v8::FunctionTemplate> injectV8Method(const char* name, - v8Function func, - v8::Handle<v8::ObjectTemplate>& proto); - v8::Handle<v8::FunctionTemplate> createV8Function(v8Function func); - virtual ScriptingFunction _createFunction(const char* code, - ScriptingFunction functionNumber = 0); - v8::Local<v8::Function> __createFunction(const char* code, - ScriptingFunction functionNumber = 0); - - /** - * Convert BSON types to v8 Javascript types - */ - v8::Handle<v8::Object> mongoToLZV8(const mongo::BSONObj& m, bool readOnly = false); - v8::Handle<v8::Value> mongoToV8Element(const BSONElement& f, bool readOnly = false); - - /** - * Convert v8 Javascript types to BSON types - */ - mongo::BSONObj v8ToMongo(v8::Handle<v8::Object> obj, int depth = 0); - void v8ToMongoElement(BSONObjBuilder& b, - StringData sname, - v8::Handle<v8::Value> value, - int depth = 0, - BSONObj* originalParent = 0); - void v8ToMongoObject(BSONObjBuilder& b, - StringData sname, - v8::Handle<v8::Value> value, - int depth, - BSONObj* originalParent); - void v8ToMongoNumber(BSONObjBuilder& b, - StringData elementName, - v8::Handle<v8::Number> value, - BSONObj* originalParent); - void v8ToMongoRegex(BSONObjBuilder& b, - StringData elementName, - v8::Handle<v8::RegExp> v8Regex); - void v8ToMongoDBRef(BSONObjBuilder& b, - StringData elementName, - v8::Handle<v8::Object> obj); - void v8ToMongoBinData(BSONObjBuilder& b, - StringData elementName, - v8::Handle<v8::Object> obj); - OID v8ToMongoObjectID(v8::Handle<v8::Object> obj); - - v8::Local<v8::Value> newId(const OID& id); - - /** - * Convert a JavaScript exception to a stl string. Requires - * access to the V8Scope instance to report source context information. - */ - std::string v8ExceptionToSTLString(const v8::TryCatch* try_catch); - - /** - * Create a V8 std::string with a local handle - */ - static inline v8::Handle<v8::String> v8StringData(StringData str) { - return v8::String::New(str.rawData(), str.size()); - } + // container for all TrackedPtrs created by this ObjTracker instance + std::set<TrackedPtr*> _container; +}; - /** - * Get the isolate this scope belongs to (can be called from any thread, but v8 requires - * the new thread enter the isolate and context. Only one thread can enter the isolate. - */ - v8::Isolate* getIsolate() { return _isolate; } - - /** - * Get the JS context this scope executes within. - */ - v8::Persistent<v8::Context> getContext() { return _context; } - - /** - * Get the global JS object - */ - v8::Persistent<v8::Object> getGlobal() { return _global; } - - ObjTracker<BSONHolder> bsonHolderTracker; - ObjTracker<DBClientBase> dbClientBaseTracker; - // Track both cursor and connection. - // This ensures the connection outlives the cursor. - struct DBConnectionAndCursor { - std::shared_ptr<DBClientBase> conn; - std::shared_ptr<DBClientCursor> cursor; - DBConnectionAndCursor(std::shared_ptr<DBClientBase> conn, - std::shared_ptr<DBClientCursor> cursor) - : conn(conn), cursor(cursor) { } - }; - ObjTracker<DBConnectionAndCursor> dbConnectionAndCursor; - ObjTracker<JSThreadConfig> jsThreadConfigTracker; - - // These are all named after the JS constructor name + FT - v8::Handle<v8::FunctionTemplate> ObjectIdFT() const { return _ObjectIdFT; } - v8::Handle<v8::FunctionTemplate> DBRefFT() const { return _DBRefFT; } - v8::Handle<v8::FunctionTemplate> DBPointerFT() const { return _DBPointerFT; } - v8::Handle<v8::FunctionTemplate> BinDataFT() const { return _BinDataFT; } - v8::Handle<v8::FunctionTemplate> NumberLongFT() const { return _NumberLongFT; } - v8::Handle<v8::FunctionTemplate> NumberIntFT() const { return _NumberIntFT; } - v8::Handle<v8::FunctionTemplate> TimestampFT() const { return _TimestampFT; } - v8::Handle<v8::FunctionTemplate> MinKeyFT() const { return _MinKeyFT; } - v8::Handle<v8::FunctionTemplate> MaxKeyFT() const { return _MaxKeyFT; } - v8::Handle<v8::FunctionTemplate> MongoFT() const { return _MongoFT; } - v8::Handle<v8::FunctionTemplate> DBFT() const { return _DBFT; } - v8::Handle<v8::FunctionTemplate> DBCollectionFT() const { return _DBCollectionFT; } - v8::Handle<v8::FunctionTemplate> DBQueryFT() const { return _DBQueryFT; } - v8::Handle<v8::FunctionTemplate> InternalCursorFT() const { return _InternalCursorFT; } - v8::Handle<v8::FunctionTemplate> LazyBsonFT() const { return _LazyBsonFT; } - v8::Handle<v8::FunctionTemplate> ROBsonFT() const { return _ROBsonFT; } - - template <size_t N> - v8::Handle<v8::String> strLitToV8(const char (&str)[N]) { - // Note that _strLitMap is keyed on std::string pointer not string - // value. This is OK because each std::string literal has a constant - // pointer for the program's lifetime. This works best if (but does - // not require) the linker interns all std::string literals giving - // identical strings used in different places the same pointer. - - StrLitMap::iterator it = _strLitMap.find(str); - if (it != _strLitMap.end()) - return it->second; - - StringData sd (str, StringData::LiteralTag()); - v8::Handle<v8::String> v8Str = v8StringData(sd); - - // We never need to Dispose since this should last as long as V8Scope exists - _strLitMap[str] = v8::Persistent<v8::String>::New(v8Str); - - return v8Str; - } +/** + * A V8Scope represents a unit of javascript execution environment; specifically a single + * isolate and a single context executing in a single mongo thread. A V8Scope can be reused + * in another thread only after reset() has been called. + * + * NB: + * - v8 objects/handles/etc. cannot be shared between V8Scopes + * - in mongod, each scope is associated with an opId (for KillOp support) + * - any public functions that call the v8 API should use a V8_SIMPLE_HEADER + * - the caller of any public function that returns a v8 type or has a v8 handle argument + * must enter the isolate, context, and set up the appropriate handle scope + */ +class V8Scope : public Scope { +public: + V8Scope(V8ScriptEngine* engine); + ~V8Scope(); - private: - /** - * Recursion limit when converting from JS objects to BSON. - */ - static const int objectDepthLimit = 150; - - /** - * Attach data to obj such that the data has the same lifetime as the Object obj points to. - * obj must have been created by either LazyBsonFT or ROBsonFT. - */ - void wrapBSONObject(v8::Handle<v8::Object> obj, BSONObj data, bool readOnly); - - /** - * Trampoline to call a c++ function with a specific signature (V8Scope*, v8::Arguments&). - * Handles interruption, exceptions, etc. - */ - static v8::Handle<v8::Value> v8Callback(const v8::Arguments& args); - - /** - * Interpreter agnostic 'Native Callback' trampoline. Note this is only called - * from v8Callback(). - */ - static v8::Handle<v8::Value> nativeCallback(V8Scope* scope, const v8::Arguments& args); - - /** - * v8-specific implementations of basic global functions - */ - static v8::Handle<v8::Value> load(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> Print(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> Version(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> GCV8(V8Scope* scope, const v8::Arguments& args); - - static v8::Handle<v8::Value> startCpuProfiler(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> stopCpuProfiler(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> getCpuProfile(V8Scope* scope, const v8::Arguments& args); - - /** Signal that this scope has entered a native (C++) execution context. - * @return false if execution has been interrupted - */ - bool nativePrologue(); - - /** Signal that this scope has completed native execution and is returning to v8. - * @return false if execution has been interrupted - */ - bool nativeEpilogue(); - - /** - * Create a new function; primarily used for BSON/V8 conversion. - */ - v8::Local<v8::Value> newFunction(StringData code); - - template <typename _HandleType> - bool checkV8ErrorState(const _HandleType& resultHandle, - const v8::TryCatch& try_catch, - bool reportError = true, - bool assertOnError = true); - - V8ScriptEngine* _engine; - - v8::Persistent<v8::Context> _context; - v8::Persistent<v8::Object> _global; - std::string _error; - std::vector<v8::Persistent<v8::Value> > _funcs; - - enum ConnectState { NOT, LOCAL, EXTERNAL }; - ConnectState _connectState; - - // These are all named after the JS constructor name + FT - v8::Persistent<v8::FunctionTemplate> _ObjectIdFT; - v8::Persistent<v8::FunctionTemplate> _DBRefFT; - v8::Persistent<v8::FunctionTemplate> _DBPointerFT; - v8::Persistent<v8::FunctionTemplate> _BinDataFT; - v8::Persistent<v8::FunctionTemplate> _NumberLongFT; - v8::Persistent<v8::FunctionTemplate> _NumberIntFT; - v8::Persistent<v8::FunctionTemplate> _TimestampFT; - v8::Persistent<v8::FunctionTemplate> _MinKeyFT; - v8::Persistent<v8::FunctionTemplate> _MaxKeyFT; - v8::Persistent<v8::FunctionTemplate> _MongoFT; - v8::Persistent<v8::FunctionTemplate> _DBFT; - v8::Persistent<v8::FunctionTemplate> _DBCollectionFT; - v8::Persistent<v8::FunctionTemplate> _DBQueryFT; - v8::Persistent<v8::FunctionTemplate> _InternalCursorFT; - v8::Persistent<v8::FunctionTemplate> _LazyBsonFT; - v8::Persistent<v8::FunctionTemplate> _ROBsonFT; - - v8::Persistent<v8::Function> _jsRegExpConstructor; - - /// Like v8::Isolate* but calls Dispose() in destructor. - class IsolateHolder { - MONGO_DISALLOW_COPYING(IsolateHolder); - public: - IsolateHolder() :_isolate(NULL) {} - ~IsolateHolder() { - if (_isolate) { - _isolate->Dispose(); - _isolate = NULL; - } - } + virtual void init(const BSONObj* data); - void set(v8::Isolate* isolate) { - fassert(17184, !_isolate); - _isolate = isolate; - } + /** + * Reset the state of this scope for use by another thread or operation + */ + virtual void reset(); - v8::Isolate* operator -> () const { return _isolate; }; - operator v8::Isolate* () const { return _isolate; }; - private: - v8::Isolate* _isolate; - }; + /** + * Terminate this scope + */ + virtual void kill(); - IsolateHolder _isolate; // NOTE: this must be destructed before the ObjTrackers + /** check if there is a pending killOp request */ + bool isKillPending() const; - V8CpuProfiler _cpuProfiler; + /** + * Register this scope with the mongo op id. If executing outside the + * context of a mongo operation (e.g. from the shell), killOp will not + * be supported. + */ + virtual void registerOperation(OperationContext* txn); - // See comments in strLitToV8 - typedef unordered_map<const char*, v8::Handle<v8::String> > StrLitMap; - StrLitMap _strLitMap; + /** + * Unregister this scope with the mongo op id. + */ + virtual void unregisterOperation(); - stdx::mutex _interruptLock; // protects interruption-related flags - bool _inNativeExecution; // protected by _interruptLock - bool _pendingKill; // protected by _interruptLock - unsigned int _opId; // op id for this scope - OperationContext* _opCtx; // Op context for DbEval - }; + /** + * Obtains the operation context associated with this Scope, so it can be given to the + * DBDirectClient used by the V8 engine's connection. Only needed for dbEval. + */ + OperationContext* getOpContext() const; - /// Helper to extract V8Scope for an Isolate - inline V8Scope* getScope(v8::Isolate* isolate) { - return static_cast<V8Scope*>(isolate->GetData()); + /** + * Connect to a local database, create a Mongo object instance, and load any + * server-side js into the global object + */ + virtual void localConnectForDbEval(OperationContext* txn, const char* dbName); + + virtual void externalSetup(); + + virtual void installDBAccess(); + + virtual void installBSONTypes(); + + virtual std::string getError() { + return _error; } - class V8ScriptEngine : public ScriptEngine { - public: - V8ScriptEngine(); - virtual ~V8ScriptEngine(); - virtual Scope* createScope() { return new V8Scope(this); } - virtual void runTest() {} - bool utf8Ok() const { return true; } - - /** - * Interrupt a single active v8 execution context - * NB: To interrupt a context, we must acquire the following locks (in order): - * - mutex to protect the the map of all scopes (_globalInterruptLock) - * - mutex to protect the scope that's being interrupted (_interruptLock) - * The scope will be removed from the map upon destruction, and the op id - * will be updated if the scope is ever reused from a pool. - */ - virtual void interrupt(unsigned opId); - - /** - * Interrupt all v8 contexts (and isolates). @see interrupt(). - */ - virtual void interruptAll(); + virtual bool hasOutOfMemoryException(); - private: - friend class V8Scope; + /** + * Run the garbage collector on this scope (native function). @see GCV8 for the + * javascript binding version. + */ + void gc(); + + /** + * get a global property. caller must set up the v8 state. + */ + v8::Handle<v8::Value> get(const char* field); + + virtual double getNumber(const char* field); + virtual int getNumberInt(const char* field); + virtual long long getNumberLongLong(const char* field); + virtual std::string getString(const char* field); + virtual bool getBoolean(const char* field); + virtual BSONObj getObject(const char* field); + + virtual void setNumber(const char* field, double val); + virtual void setString(const char* field, StringData val); + virtual void setBoolean(const char* field, bool val); + virtual void setElement(const char* field, const BSONElement& e); + virtual void setObject(const char* field, const BSONObj& obj, bool readOnly); + virtual void setFunction(const char* field, const char* code); + + virtual int type(const char* field); + + virtual void rename(const char* from, const char* to); + + virtual int invoke(ScriptingFunction func, + const BSONObj* args, + const BSONObj* recv, + int timeoutMs = 0, + bool ignoreReturn = false, + bool readOnlyArgs = false, + bool readOnlyRecv = false); + + virtual bool exec(StringData code, + const std::string& name, + bool printResult, + bool reportError, + bool assertOnError, + int timeoutMs); + + // functions to create v8 object and function templates + virtual void injectNative(const char* field, NativeFunction func, void* data = 0); + void injectNative(const char* field, + NativeFunction func, + v8::Handle<v8::Object>& obj, + void* data = 0); + + // These functions inject a function (either an unwrapped function pointer or a pre-wrapped + // FunctionTemplate) into the provided object. If no object is provided, the function will + // be injected at global scope. These functions take care of setting the function and class + // name on the returned FunctionTemplate. + v8::Handle<v8::FunctionTemplate> injectV8Function(const char* name, v8Function func); + v8::Handle<v8::FunctionTemplate> injectV8Function(const char* name, + v8Function func, + v8::Handle<v8::Object>& obj); + v8::Handle<v8::FunctionTemplate> injectV8Function(const char* name, + v8::Handle<v8::FunctionTemplate> ft, + v8::Handle<v8::Object>& obj); + + // Injects a method into the provided prototype + v8::Handle<v8::FunctionTemplate> injectV8Method(const char* name, + v8Function func, + v8::Handle<v8::ObjectTemplate>& proto); + v8::Handle<v8::FunctionTemplate> createV8Function(v8Function func); + virtual ScriptingFunction _createFunction(const char* code, + ScriptingFunction functionNumber = 0); + v8::Local<v8::Function> __createFunction(const char* code, + ScriptingFunction functionNumber = 0); + + /** + * Convert BSON types to v8 Javascript types + */ + v8::Handle<v8::Object> mongoToLZV8(const mongo::BSONObj& m, bool readOnly = false); + v8::Handle<v8::Value> mongoToV8Element(const BSONElement& f, bool readOnly = false); + + /** + * Convert v8 Javascript types to BSON types + */ + mongo::BSONObj v8ToMongo(v8::Handle<v8::Object> obj, int depth = 0); + void v8ToMongoElement(BSONObjBuilder& b, + StringData sname, + v8::Handle<v8::Value> value, + int depth = 0, + BSONObj* originalParent = 0); + void v8ToMongoObject(BSONObjBuilder& b, + StringData sname, + v8::Handle<v8::Value> value, + int depth, + BSONObj* originalParent); + void v8ToMongoNumber(BSONObjBuilder& b, + StringData elementName, + v8::Handle<v8::Number> value, + BSONObj* originalParent); + void v8ToMongoRegex(BSONObjBuilder& b, StringData elementName, v8::Handle<v8::RegExp> v8Regex); + void v8ToMongoDBRef(BSONObjBuilder& b, StringData elementName, v8::Handle<v8::Object> obj); + void v8ToMongoBinData(BSONObjBuilder& b, StringData elementName, v8::Handle<v8::Object> obj); + OID v8ToMongoObjectID(v8::Handle<v8::Object> obj); + + v8::Local<v8::Value> newId(const OID& id); + + /** + * Convert a JavaScript exception to a stl string. Requires + * access to the V8Scope instance to report source context information. + */ + std::string v8ExceptionToSTLString(const v8::TryCatch* try_catch); - std::string printKnownOps_inlock(); + /** + * Create a V8 std::string with a local handle + */ + static inline v8::Handle<v8::String> v8StringData(StringData str) { + return v8::String::New(str.rawData(), str.size()); + } - /** - * Get the deadline monitor instance for the v8 ScriptEngine - */ - DeadlineMonitor<V8Scope>* getDeadlineMonitor() { return &_deadlineMonitor; } + /** + * Get the isolate this scope belongs to (can be called from any thread, but v8 requires + * the new thread enter the isolate and context. Only one thread can enter the isolate. + */ + v8::Isolate* getIsolate() { + return _isolate; + } - typedef std::map<unsigned, V8Scope*> OpIdToScopeMap; - stdx::mutex _globalInterruptLock; // protects map of all operation ids -> scope - OpIdToScopeMap _opToScopeMap; // map of mongo op ids to scopes (protected by - // _globalInterruptLock). - DeadlineMonitor<V8Scope> _deadlineMonitor; + /** + * Get the JS context this scope executes within. + */ + v8::Persistent<v8::Context> getContext() { + return _context; + } + + /** + * Get the global JS object + */ + v8::Persistent<v8::Object> getGlobal() { + return _global; + } + + ObjTracker<BSONHolder> bsonHolderTracker; + ObjTracker<DBClientBase> dbClientBaseTracker; + // Track both cursor and connection. + // This ensures the connection outlives the cursor. + struct DBConnectionAndCursor { + std::shared_ptr<DBClientBase> conn; + std::shared_ptr<DBClientCursor> cursor; + DBConnectionAndCursor(std::shared_ptr<DBClientBase> conn, + std::shared_ptr<DBClientCursor> cursor) + : conn(conn), cursor(cursor) {} }; + ObjTracker<DBConnectionAndCursor> dbConnectionAndCursor; + ObjTracker<JSThreadConfig> jsThreadConfigTracker; + + // These are all named after the JS constructor name + FT + v8::Handle<v8::FunctionTemplate> ObjectIdFT() const { + return _ObjectIdFT; + } + v8::Handle<v8::FunctionTemplate> DBRefFT() const { + return _DBRefFT; + } + v8::Handle<v8::FunctionTemplate> DBPointerFT() const { + return _DBPointerFT; + } + v8::Handle<v8::FunctionTemplate> BinDataFT() const { + return _BinDataFT; + } + v8::Handle<v8::FunctionTemplate> NumberLongFT() const { + return _NumberLongFT; + } + v8::Handle<v8::FunctionTemplate> NumberIntFT() const { + return _NumberIntFT; + } + v8::Handle<v8::FunctionTemplate> TimestampFT() const { + return _TimestampFT; + } + v8::Handle<v8::FunctionTemplate> MinKeyFT() const { + return _MinKeyFT; + } + v8::Handle<v8::FunctionTemplate> MaxKeyFT() const { + return _MaxKeyFT; + } + v8::Handle<v8::FunctionTemplate> MongoFT() const { + return _MongoFT; + } + v8::Handle<v8::FunctionTemplate> DBFT() const { + return _DBFT; + } + v8::Handle<v8::FunctionTemplate> DBCollectionFT() const { + return _DBCollectionFT; + } + v8::Handle<v8::FunctionTemplate> DBQueryFT() const { + return _DBQueryFT; + } + v8::Handle<v8::FunctionTemplate> InternalCursorFT() const { + return _InternalCursorFT; + } + v8::Handle<v8::FunctionTemplate> LazyBsonFT() const { + return _LazyBsonFT; + } + v8::Handle<v8::FunctionTemplate> ROBsonFT() const { + return _ROBsonFT; + } + + template <size_t N> + v8::Handle<v8::String> strLitToV8(const char(&str)[N]) { + // Note that _strLitMap is keyed on std::string pointer not string + // value. This is OK because each std::string literal has a constant + // pointer for the program's lifetime. This works best if (but does + // not require) the linker interns all std::string literals giving + // identical strings used in different places the same pointer. + + StrLitMap::iterator it = _strLitMap.find(str); + if (it != _strLitMap.end()) + return it->second; + + StringData sd(str, StringData::LiteralTag()); + v8::Handle<v8::String> v8Str = v8StringData(sd); + + // We never need to Dispose since this should last as long as V8Scope exists + _strLitMap[str] = v8::Persistent<v8::String>::New(v8Str); + + return v8Str; + } + +private: + /** + * Recursion limit when converting from JS objects to BSON. + */ + static const int objectDepthLimit = 150; + + /** + * Attach data to obj such that the data has the same lifetime as the Object obj points to. + * obj must have been created by either LazyBsonFT or ROBsonFT. + */ + void wrapBSONObject(v8::Handle<v8::Object> obj, BSONObj data, bool readOnly); + + /** + * Trampoline to call a c++ function with a specific signature (V8Scope*, v8::Arguments&). + * Handles interruption, exceptions, etc. + */ + static v8::Handle<v8::Value> v8Callback(const v8::Arguments& args); + + /** + * Interpreter agnostic 'Native Callback' trampoline. Note this is only called + * from v8Callback(). + */ + static v8::Handle<v8::Value> nativeCallback(V8Scope* scope, const v8::Arguments& args); + + /** + * v8-specific implementations of basic global functions + */ + static v8::Handle<v8::Value> load(V8Scope* scope, const v8::Arguments& args); + static v8::Handle<v8::Value> Print(V8Scope* scope, const v8::Arguments& args); + static v8::Handle<v8::Value> Version(V8Scope* scope, const v8::Arguments& args); + static v8::Handle<v8::Value> GCV8(V8Scope* scope, const v8::Arguments& args); + + static v8::Handle<v8::Value> startCpuProfiler(V8Scope* scope, const v8::Arguments& args); + static v8::Handle<v8::Value> stopCpuProfiler(V8Scope* scope, const v8::Arguments& args); + static v8::Handle<v8::Value> getCpuProfile(V8Scope* scope, const v8::Arguments& args); + + /** Signal that this scope has entered a native (C++) execution context. + * @return false if execution has been interrupted + */ + bool nativePrologue(); + + /** Signal that this scope has completed native execution and is returning to v8. + * @return false if execution has been interrupted + */ + bool nativeEpilogue(); + + /** + * Create a new function; primarily used for BSON/V8 conversion. + */ + v8::Local<v8::Value> newFunction(StringData code); + + template <typename _HandleType> + bool checkV8ErrorState(const _HandleType& resultHandle, + const v8::TryCatch& try_catch, + bool reportError = true, + bool assertOnError = true); + + V8ScriptEngine* _engine; + + v8::Persistent<v8::Context> _context; + v8::Persistent<v8::Object> _global; + std::string _error; + std::vector<v8::Persistent<v8::Value>> _funcs; + + enum ConnectState { NOT, LOCAL, EXTERNAL }; + ConnectState _connectState; + + // These are all named after the JS constructor name + FT + v8::Persistent<v8::FunctionTemplate> _ObjectIdFT; + v8::Persistent<v8::FunctionTemplate> _DBRefFT; + v8::Persistent<v8::FunctionTemplate> _DBPointerFT; + v8::Persistent<v8::FunctionTemplate> _BinDataFT; + v8::Persistent<v8::FunctionTemplate> _NumberLongFT; + v8::Persistent<v8::FunctionTemplate> _NumberIntFT; + v8::Persistent<v8::FunctionTemplate> _TimestampFT; + v8::Persistent<v8::FunctionTemplate> _MinKeyFT; + v8::Persistent<v8::FunctionTemplate> _MaxKeyFT; + v8::Persistent<v8::FunctionTemplate> _MongoFT; + v8::Persistent<v8::FunctionTemplate> _DBFT; + v8::Persistent<v8::FunctionTemplate> _DBCollectionFT; + v8::Persistent<v8::FunctionTemplate> _DBQueryFT; + v8::Persistent<v8::FunctionTemplate> _InternalCursorFT; + v8::Persistent<v8::FunctionTemplate> _LazyBsonFT; + v8::Persistent<v8::FunctionTemplate> _ROBsonFT; + + v8::Persistent<v8::Function> _jsRegExpConstructor; + + /// Like v8::Isolate* but calls Dispose() in destructor. + class IsolateHolder { + MONGO_DISALLOW_COPYING(IsolateHolder); - class BSONHolder { - MONGO_DISALLOW_COPYING(BSONHolder); public: - explicit BSONHolder(BSONObj obj) : - _scope(NULL), - _obj(obj.getOwned()), - _modified(false) { - // give hint v8's GC - v8::V8::AdjustAmountOfExternalAllocatedMemory(_obj.objsize()); + IsolateHolder() : _isolate(NULL) {} + ~IsolateHolder() { + if (_isolate) { + _isolate->Dispose(); + _isolate = NULL; + } } - ~BSONHolder() { - if (_scope && _scope->getIsolate()) - // if v8 is still up, send hint to GC - v8::V8::AdjustAmountOfExternalAllocatedMemory(-_obj.objsize()); + + void set(v8::Isolate* isolate) { + fassert(17184, !_isolate); + _isolate = isolate; } - V8Scope* _scope; - BSONObj _obj; - bool _modified; - bool _readOnly; - std::set<std::string> _removed; + + v8::Isolate* operator->() const { + return _isolate; + }; + operator v8::Isolate*() const { + return _isolate; + }; + + private: + v8::Isolate* _isolate; }; - extern ScriptEngine* globalScriptEngine; + IsolateHolder _isolate; // NOTE: this must be destructed before the ObjTrackers + + V8CpuProfiler _cpuProfiler; + + // See comments in strLitToV8 + typedef unordered_map<const char*, v8::Handle<v8::String>> StrLitMap; + StrLitMap _strLitMap; + + stdx::mutex _interruptLock; // protects interruption-related flags + bool _inNativeExecution; // protected by _interruptLock + bool _pendingKill; // protected by _interruptLock + unsigned int _opId; // op id for this scope + OperationContext* _opCtx; // Op context for DbEval +}; + +/// Helper to extract V8Scope for an Isolate +inline V8Scope* getScope(v8::Isolate* isolate) { + return static_cast<V8Scope*>(isolate->GetData()); +} + +class V8ScriptEngine : public ScriptEngine { +public: + V8ScriptEngine(); + virtual ~V8ScriptEngine(); + virtual Scope* createScope() { + return new V8Scope(this); + } + virtual void runTest() {} + bool utf8Ok() const { + return true; + } + + /** + * Interrupt a single active v8 execution context + * NB: To interrupt a context, we must acquire the following locks (in order): + * - mutex to protect the the map of all scopes (_globalInterruptLock) + * - mutex to protect the scope that's being interrupted (_interruptLock) + * The scope will be removed from the map upon destruction, and the op id + * will be updated if the scope is ever reused from a pool. + */ + virtual void interrupt(unsigned opId); + + /** + * Interrupt all v8 contexts (and isolates). @see interrupt(). + */ + virtual void interruptAll(); + +private: + friend class V8Scope; + + std::string printKnownOps_inlock(); + /** + * Get the deadline monitor instance for the v8 ScriptEngine + */ + DeadlineMonitor<V8Scope>* getDeadlineMonitor() { + return &_deadlineMonitor; + } + + typedef std::map<unsigned, V8Scope*> OpIdToScopeMap; + stdx::mutex _globalInterruptLock; // protects map of all operation ids -> scope + OpIdToScopeMap _opToScopeMap; // map of mongo op ids to scopes (protected by + // _globalInterruptLock). + DeadlineMonitor<V8Scope> _deadlineMonitor; +}; + +class BSONHolder { + MONGO_DISALLOW_COPYING(BSONHolder); + +public: + explicit BSONHolder(BSONObj obj) : _scope(NULL), _obj(obj.getOwned()), _modified(false) { + // give hint v8's GC + v8::V8::AdjustAmountOfExternalAllocatedMemory(_obj.objsize()); + } + ~BSONHolder() { + if (_scope && _scope->getIsolate()) + // if v8 is still up, send hint to GC + v8::V8::AdjustAmountOfExternalAllocatedMemory(-_obj.objsize()); + } + V8Scope* _scope; + BSONObj _obj; + bool _modified; + bool _readOnly; + std::set<std::string> _removed; +}; + +extern ScriptEngine* globalScriptEngine; } |