//engine_v8.h /* Copyright 2009 10gen 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 . * * 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 #include #include "mongo/base/disallow_copying.h" #include "mongo/base/string_data.h" #include "mongo/client/dbclientinterface.h" #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/v8_profiler.h" /** * V8_SIMPLE_HEADER must be placed in any function called from a public API * 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 */ namespace mongo { class V8ScriptEngine; class V8Scope; class BSONHolder; class JSThreadConfig; typedef v8::Handle (*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 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 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 track(v8::Persistent 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::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 instanceHandle, void* rawData) { TrackedPtr* trackedPtr = static_cast(rawData); trackedPtr->_tracker->_container.erase(trackedPtr); delete trackedPtr; instanceHandle.Dispose(); } // container for all TrackedPtrs created by this ObjTracker instance std::set _container; }; /** * 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(); 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 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& 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 injectV8Function(const char* name, v8Function func); v8::Handle injectV8Function(const char* name, v8Function func, v8::Handle& obj); v8::Handle injectV8Function(const char* name, v8::Handle ft, v8::Handle& obj); // Injects a method into the provided prototype v8::Handle injectV8Method(const char* name, v8Function func, v8::Handle& proto); v8::Handle createV8Function(v8Function func); virtual ScriptingFunction _createFunction(const char* code, ScriptingFunction functionNumber = 0); v8::Local __createFunction(const char* code, ScriptingFunction functionNumber = 0); /** * Convert BSON types to v8 Javascript types */ v8::Handle mongoToLZV8(const mongo::BSONObj& m, bool readOnly = false); v8::Handle mongoToV8Element(const BSONElement& f, bool readOnly = false); /** * Convert v8 Javascript types to BSON types */ mongo::BSONObj v8ToMongo(v8::Handle obj, int depth = 0); void v8ToMongoElement(BSONObjBuilder& b, StringData sname, v8::Handle value, int depth = 0, BSONObj* originalParent = 0); void v8ToMongoObject(BSONObjBuilder& b, StringData sname, v8::Handle value, int depth, BSONObj* originalParent); void v8ToMongoNumber(BSONObjBuilder& b, StringData elementName, v8::Handle value, BSONObj* originalParent); void v8ToMongoRegex(BSONObjBuilder& b, StringData elementName, v8::Handle v8Regex); void v8ToMongoDBRef(BSONObjBuilder& b, StringData elementName, v8::Handle obj); void v8ToMongoBinData(BSONObjBuilder& b, StringData elementName, v8::Handle obj); OID v8ToMongoObjectID(v8::Handle obj); v8::Local 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 v8StringData(StringData str) { return v8::String::New(str.rawData(), str.size()); } /** * 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 getContext() { return _context; } /** * Get the global JS object */ v8::Persistent getGlobal() { return _global; } ObjTracker bsonHolderTracker; ObjTracker dbClientBaseTracker; // Track both cursor and connection. // This ensures the connection outlives the cursor. struct DBConnectionAndCursor { std::shared_ptr conn; std::shared_ptr cursor; DBConnectionAndCursor(std::shared_ptr conn, std::shared_ptr cursor) : conn(conn), cursor(cursor) { } }; ObjTracker dbConnectionAndCursor; ObjTracker jsThreadConfigTracker; // These are all named after the JS constructor name + FT v8::Handle ObjectIdFT() const { return _ObjectIdFT; } v8::Handle DBRefFT() const { return _DBRefFT; } v8::Handle DBPointerFT() const { return _DBPointerFT; } v8::Handle BinDataFT() const { return _BinDataFT; } v8::Handle NumberLongFT() const { return _NumberLongFT; } v8::Handle NumberIntFT() const { return _NumberIntFT; } v8::Handle TimestampFT() const { return _TimestampFT; } v8::Handle MinKeyFT() const { return _MinKeyFT; } v8::Handle MaxKeyFT() const { return _MaxKeyFT; } v8::Handle MongoFT() const { return _MongoFT; } v8::Handle DBFT() const { return _DBFT; } v8::Handle DBCollectionFT() const { return _DBCollectionFT; } v8::Handle DBQueryFT() const { return _DBQueryFT; } v8::Handle InternalCursorFT() const { return _InternalCursorFT; } v8::Handle LazyBsonFT() const { return _LazyBsonFT; } v8::Handle ROBsonFT() const { return _ROBsonFT; } template v8::Handle 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 v8Str = v8StringData(sd); // We never need to Dispose since this should last as long as V8Scope exists _strLitMap[str] = v8::Persistent::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 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 v8Callback(const v8::Arguments& args); /** * Interpreter agnostic 'Native Callback' trampoline. Note this is only called * from v8Callback(). */ static v8::Handle nativeCallback(V8Scope* scope, const v8::Arguments& args); /** * v8-specific implementations of basic global functions */ static v8::Handle load(V8Scope* scope, const v8::Arguments& args); static v8::Handle Print(V8Scope* scope, const v8::Arguments& args); static v8::Handle Version(V8Scope* scope, const v8::Arguments& args); static v8::Handle GCV8(V8Scope* scope, const v8::Arguments& args); static v8::Handle startCpuProfiler(V8Scope* scope, const v8::Arguments& args); static v8::Handle stopCpuProfiler(V8Scope* scope, const v8::Arguments& args); static v8::Handle 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 newFunction(StringData code); template bool checkV8ErrorState(const _HandleType& resultHandle, const v8::TryCatch& try_catch, bool reportError = true, bool assertOnError = true); V8ScriptEngine* _engine; v8::Persistent _context; v8::Persistent _global; std::string _error; std::vector > _funcs; enum ConnectState { NOT, LOCAL, EXTERNAL }; ConnectState _connectState; // These are all named after the JS constructor name + FT v8::Persistent _ObjectIdFT; v8::Persistent _DBRefFT; v8::Persistent _DBPointerFT; v8::Persistent _BinDataFT; v8::Persistent _NumberLongFT; v8::Persistent _NumberIntFT; v8::Persistent _TimestampFT; v8::Persistent _MinKeyFT; v8::Persistent _MaxKeyFT; v8::Persistent _MongoFT; v8::Persistent _DBFT; v8::Persistent _DBCollectionFT; v8::Persistent _DBQueryFT; v8::Persistent _InternalCursorFT; v8::Persistent _LazyBsonFT; v8::Persistent _ROBsonFT; v8::Persistent _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; } } void set(v8::Isolate* isolate) { fassert(17184, !_isolate); _isolate = isolate; } v8::Isolate* operator -> () const { return _isolate; }; operator v8::Isolate* () const { return _isolate; }; private: v8::Isolate* _isolate; }; IsolateHolder _isolate; // NOTE: this must be destructed before the ObjTrackers V8CpuProfiler _cpuProfiler; // See comments in strLitToV8 typedef unordered_map > 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(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* getDeadlineMonitor() { return &_deadlineMonitor; } typedef std::map OpIdToScopeMap; stdx::mutex _globalInterruptLock; // protects map of all operation ids -> scope OpIdToScopeMap _opToScopeMap; // map of mongo op ids to scopes (protected by // _globalInterruptLock). DeadlineMonitor _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 _removed; }; extern ScriptEngine* globalScriptEngine; }