diff options
-rw-r--r-- | SConstruct | 14 | ||||
-rw-r--r-- | buildscripts/utils.py | 5 | ||||
-rw-r--r-- | src/mongo/SConscript | 10 | ||||
-rw-r--r-- | src/mongo/scripting/engine_v8-3.25.cpp | 1852 | ||||
-rw-r--r-- | src/mongo/scripting/engine_v8-3.25.h | 615 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_db.cpp | 1117 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_db.h | 167 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_profiler.cpp | 77 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_profiler.h | 46 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_utils.cpp | 299 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_utils.h | 106 | ||||
-rw-r--r-- | src/third_party/SConscript | 8 | ||||
-rw-r--r-- | src/third_party/v8-3.25/SConscript | 437 |
13 files changed, 4741 insertions, 12 deletions
diff --git a/SConstruct b/SConstruct index 49bc434ff2b..001bf9ee62c 100644 --- a/SConstruct +++ b/SConstruct @@ -230,7 +230,9 @@ add_option( "asio" , "Use Asynchronous IO (NOT READY YET)" , 0 , True ) add_option( "ssl" , "Enable SSL" , 0 , True ) # library choices -add_option( "usev8" , "use v8 for javascript" , 0 , True ) +js_engine_choices = ['v8-3.12', 'v8-3.25', 'none'] +add_option( "js-engine", "JavaScript scripting engine implementation", 1, True, + type='choice', default=js_engine_choices[0], choices=js_engine_choices) add_option( "libc++", "use libc++ (experimental, requires clang)", 0, True ) # mongo feature options @@ -392,7 +394,12 @@ static = has_option( "static" ) noshell = has_option( "noshell" ) -usev8 = has_option( "usev8" ) +jsEngine = get_option( "js-engine") + +usev8 = (jsEngine != 'none') + +v8version = jsEngine[3:] if jsEngine.startswith('v8-') else 'none' +v8suffix = '' if v8version == '3.12' else '-' + v8version asio = has_option( "asio" ) @@ -548,8 +555,6 @@ if has_option( "durableDefaultOn" ): if has_option( "durableDefaultOff" ): env.Append( CPPDEFINES=[ "_DURABLEDEFAULTOFF" ] ) -usev8 = True - extraLibPlaces = [] env['EXTRACPPPATH'] = [] @@ -1751,6 +1756,7 @@ Export("get_option") Export("has_option use_system_version_of_library") Export("mongoCodeVersion") Export("usev8") +Export("v8version v8suffix") Export("darwin windows solaris linux freebsd nix openbsd") Export('module_sconscripts') Export("debugBuild optBuild") diff --git a/buildscripts/utils.py b/buildscripts/utils.py index 68273ee69c8..0a46ef440d4 100644 --- a/buildscripts/utils.py +++ b/buildscripts/utils.py @@ -24,6 +24,11 @@ def getAllSourceFiles( arr=None , prefix="." ): for x in os.listdir( prefix ): if x.startswith( "." ) or x.startswith( "pcre-" ) or x.startswith( "32bit" ) or x.startswith( "mongodb-" ) or x.startswith("debian") or x.startswith( "mongo-cxx-driver" ): continue + # XXX: Avoid conflict between v8 and v8-3.25 source files in + # src/mongo/scripting + # Remove after v8-3.25 migration. + if x.find("v8-3.25") != -1: + continue full = prefix + "/" + x if os.path.isdir( full ) and not os.path.islink( full ): getAllSourceFiles( arr , full ) diff --git a/src/mongo/SConscript b/src/mongo/SConscript index aa60324738d..57c87437ee0 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -12,6 +12,7 @@ Import("testEnv") Import("has_option") Import("get_option") Import("usev8") +Import("v8suffix") Import("enforce_glibc") Import("darwin windows solaris linux nix") @@ -505,10 +506,11 @@ env.CppUnitTest('bson_template_evaluator_test', ['scripting/bson_template_evalua LIBDEPS=['bson_template_evaluator']) if usev8: - env.Library('scripting', scripting_common_files + ['scripting/engine_v8.cpp', - 'scripting/v8_db.cpp', - 'scripting/v8_utils.cpp', - 'scripting/v8_profiler.cpp'], + env.Library('scripting', scripting_common_files + ['scripting/engine_v8' + v8suffix + '.cpp', + 'scripting/v8' + v8suffix + '_db.cpp', + 'scripting/v8' + v8suffix + '_utils.cpp', + 'scripting/v8' + v8suffix + + '_profiler.cpp'], LIBDEPS=['bson_template_evaluator', '$BUILD_DIR/third_party/shim_v8']) else: env.Library('scripting', scripting_common_files + ['scripting/engine_none.cpp'], diff --git a/src/mongo/scripting/engine_v8-3.25.cpp b/src/mongo/scripting/engine_v8-3.25.cpp new file mode 100644 index 00000000000..2d0dbfbce71 --- /dev/null +++ b/src/mongo/scripting/engine_v8-3.25.cpp @@ -0,0 +1,1852 @@ +//engine_v8.cpp + +/* Copyright 2014 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/scripting/engine_v8-3.25.h" + +#include "mongo/base/init.h" +#include "mongo/platform/unordered_set.h" +#include "mongo/scripting/v8-3.25_db.h" +#include "mongo/scripting/v8-3.25_utils.h" +#include "mongo/util/base64.h" +#include "mongo/util/mongoutils/str.h" + +using namespace mongoutils; + +namespace mongo { + +#ifndef _MSC_EXTENSIONS + const int V8Scope::objectDepthLimit; +#endif + + // Generated symbols for JS files + namespace JSFiles { + extern const JSFile types; + extern const JSFile assert; + } + + // The unwrapXXX functions extract internal fields from an object wrapped by wrapBSONObject. + // These functions are currently only used in places that should always have the correct + // type of object, however it may be possible for users to come up with a way to make these + // called with the wrong type so calling code should always check the returns. + static BSONHolder* unwrapHolder(V8Scope* scope, const v8::Local<v8::Object>& obj) { + // Warning: can't throw exceptions in this context. + if (!scope->LazyBsonFT()->HasInstance(obj)) + return NULL; + + v8::Local<v8::External> field = v8::Local<v8::External>::Cast(obj->GetInternalField(0)); + if (field.IsEmpty() || !field->IsExternal()) + return 0; + void* ptr = field->Value(); + return (BSONHolder*)ptr; + } + + static BSONObj unwrapBSONObj(V8Scope* scope, const v8::Local<v8::Object>& obj) { + // Warning: can't throw exceptions in this context. + BSONHolder* holder = unwrapHolder(scope, obj); + return holder ? holder->_obj : BSONObj(); + } + + static v8::Local<v8::Object> unwrapObject(V8Scope* scope, const v8::Local<v8::Object>& obj) { + // Warning: can't throw exceptions in this context. + if (!scope->LazyBsonFT()->HasInstance(obj)) + return v8::Local<v8::Object>(); + + return obj->GetInternalField(1).As<v8::Object>(); + } + + void V8Scope::wrapBSONObject(v8::Local<v8::Object> obj, BSONObj data, bool readOnly) { + verify(LazyBsonFT()->HasInstance(obj)); + + // Nothing below throws + BSONHolder* holder = new BSONHolder(this, data, readOnly); + obj->SetInternalField(0, v8::External::New(_isolate, holder)); // Holder + obj->SetInternalField(1, v8::Object::New(_isolate)); // Object + bsonHolderTracker.track(_isolate, obj, holder); + } + + static void namedGet(v8::Local<v8::String> name, + const v8::PropertyCallbackInfo<v8::Value>& info) { + v8::HandleScope handle_scope(info.GetIsolate()); + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = info.GetReturnValue(); + result.Set(val); + try { + V8Scope* scope = getScope(info.GetIsolate()); + v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); + if (realObject.IsEmpty()) return; + if (realObject->HasOwnProperty(name)) { + // value already cached or added + result.Set(realObject->Get(name)); + return; + } + + string key = toSTLString(name); + BSONHolder* holder = unwrapHolder(scope, info.Holder()); + if (!holder || holder->_removed.count(key)) + return; + + BSONObj obj = holder->_obj; + BSONElement elmt = obj.getField(key.c_str()); + if (elmt.eoo()) + return; + + val = scope->mongoToV8Element(elmt, holder->_readOnly); + result.Set(val); + + if (obj.objsize() > 128 || val->IsObject()) { + // Only cache if expected to help (large BSON) or is required due to js semantics + realObject->Set(name, val); + } + + if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) { + // if accessing a subobject, it may get modified and base obj would not know + // have to set base as modified, which means some optim is lost + holder->_modified = true; + } + } + catch (const DBException &dbEx) { + result.Set(v8AssertionException(dbEx.toString())); + } + catch (...) { + result.Set(v8AssertionException(string("error getting property ") + toSTLString(name))); + } + } + + static void namedGetRO(v8::Local<v8::String> name, + const v8::PropertyCallbackInfo<v8::Value> &info) { + namedGet(name, info); + } + + static void namedSet(v8::Local<v8::String> name, + v8::Local<v8::Value> value_obj, + const v8::PropertyCallbackInfo<v8::Value>& info) { + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = info.GetReturnValue(); + result.Set(val); + string key = toSTLString(name); + V8Scope* scope = getScope(info.GetIsolate()); + BSONHolder* holder = unwrapHolder(scope, info.Holder()); + if (!holder) return; + holder->_removed.erase(key); + holder->_modified = true; + + v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); + if (realObject.IsEmpty()) return; + realObject->Set(name, value_obj); + result.Set(value_obj); + } + + static void namedEnumerator(const v8::PropertyCallbackInfo<v8::Array> &info) { + v8::HandleScope handle_scope(info.GetIsolate()); + v8::Local<v8::Array> val; + v8::ReturnValue<v8::Array> result = info.GetReturnValue(); + result.Set(val); + V8Scope* scope = getScope(info.GetIsolate()); + BSONHolder* holder = unwrapHolder(scope, info.Holder()); + if (!holder) return; + BSONObj obj = holder->_obj; + v8::Local<v8::Array> out = v8::Array::New(scope->getIsolate()); + int outIndex = 0; + + unordered_set<StringData, StringData::Hasher> added; + // note here that if keys are parseable number, v8 will access them using index + for (BSONObjIterator it(obj); it.more();) { + const BSONElement& f = it.next(); + StringData sname (f.fieldName(), f.fieldNameSize()-1); + if (holder->_removed.count(sname.toString())) + continue; + + v8::Local<v8::String> name = scope->v8StringData(sname); + added.insert(sname); + out->Set(outIndex++, name); + } + + + v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); + if (realObject.IsEmpty()) return; + v8::Local<v8::Array> fields = realObject->GetOwnPropertyNames(); + const int len = fields->Length(); + for (int field=0; field < len; field++) { + v8::Local<v8::String> name = fields->Get(field).As<v8::String>(); + V8String sname (name); + if (added.count(sname)) + continue; + out->Set(outIndex++, name); + } + result.Set(out); + } + + void namedDelete(v8::Local<v8::String> name, + const v8::PropertyCallbackInfo<v8::Boolean>& info) { + v8::HandleScope handle_scope(info.GetIsolate()); + v8::Local<v8::Boolean> val; + v8::ReturnValue<v8::Boolean> result = info.GetReturnValue(); + result.Set(val); + string key = toSTLString(name); + V8Scope* scope = getScope(info.GetIsolate()); + BSONHolder* holder = unwrapHolder(scope, info.Holder()); + if (!holder) return; + holder->_removed.insert(key); + holder->_modified = true; + + v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); + if (realObject.IsEmpty()) return; + realObject->Delete(name); + result.Set(v8::True(scope->getIsolate())); + } + + static void indexedGet(uint32_t index, const v8::PropertyCallbackInfo<v8::Value> &info) { + v8::HandleScope handle_scope(info.GetIsolate()); + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = info.GetReturnValue(); + result.Set(val); + try { + V8Scope* scope = getScope(info.GetIsolate()); + v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); + if (realObject.IsEmpty()) return; + if (realObject->Has(index)) { + // value already cached or added + result.Set(realObject->Get(index)); + } + string key = str::stream() << index; + + BSONHolder* holder = unwrapHolder(scope, info.Holder()); + if (!holder) return; + if (holder->_removed.count(key)) + return; + + BSONObj obj = holder->_obj; + BSONElement elmt = obj.getField(key); + if (elmt.eoo()) + return; + val = scope->mongoToV8Element(elmt, holder->_readOnly); + result.Set(val); + realObject->Set(index, val); + + if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) { + // if accessing a subobject, it may get modified and base obj would not know + // have to set base as modified, which means some optim is lost + holder->_modified = true; + } + } + catch (const DBException &dbEx) { + result.Set(v8AssertionException(dbEx.toString())); + } + catch (...) { + result.Set(v8AssertionException(str::stream() << "error getting indexed property " + << index)); + } + } + + void indexedDelete(uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& info) { + v8::Local<v8::Boolean> val; + v8::ReturnValue<v8::Boolean> result = info.GetReturnValue(); + result.Set(val); + string key = str::stream() << index; + V8Scope* scope = getScope(info.GetIsolate()); + BSONHolder* holder = unwrapHolder(scope, info.Holder()); + if (!holder) return; + holder->_removed.insert(key); + holder->_modified = true; + + // also delete in JS obj + v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); + if (realObject.IsEmpty()) return; + realObject->Delete(index); + result.Set(v8::True(scope->getIsolate())); + } + + static void indexedGetRO(uint32_t index, const v8::PropertyCallbackInfo<v8::Value> &info) { + indexedGet(index, info); + } + + static void indexedSet(uint32_t index, v8::Local<v8::Value> value_obj, + const v8::PropertyCallbackInfo<v8::Value>& info) { + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = info.GetReturnValue(); + result.Set(val); + string key = str::stream() << index; + V8Scope* scope = getScope(info.GetIsolate()); + BSONHolder* holder = unwrapHolder(scope, info.Holder()); + if (!holder) return; + holder->_removed.erase(key); + holder->_modified = true; + + v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); + if (realObject.IsEmpty()) return; + realObject->Set(index, value_obj); + result.Set(value_obj); + } + + void NamedReadOnlySet(v8::Local<v8::String> property, v8::Local<v8::Value> value, + const v8::PropertyCallbackInfo<v8::Value>& info) { + cout << "cannot write property " << V8String(property) << " to read-only object" << endl; + v8::ReturnValue<v8::Value> result = info.GetReturnValue(); + result.Set(value); + } + + void NamedReadOnlyDelete(v8::Local<v8::String> property, + const v8::PropertyCallbackInfo<v8::Boolean>& info) { + v8::ReturnValue<v8::Boolean> result = info.GetReturnValue(); + result.Set(v8::Boolean::New(info.GetIsolate(), false)); + cout << "cannot delete property " << V8String(property) << " from read-only object" << endl; + } + + void IndexedReadOnlySet(uint32_t index, v8::Local<v8::Value> value, + const v8::PropertyCallbackInfo<v8::Value>& info) { + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = info.GetReturnValue(); + result.Set(val); + cout << "cannot write property " << index << " to read-only array" << endl; + } + + void IndexedReadOnlyDelete(uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& info) { + v8::ReturnValue<v8::Boolean> result = info.GetReturnValue(); + result.Set(v8::Boolean::New(info.GetIsolate(), false)); + cout << "cannot delete property " << index << " from read-only array" << endl; + } + + /** + * GC Prologue and Epilogue constants (used to display description constants) + */ + struct GCPrologueState { static const char* name; }; + const char* GCPrologueState::name = "prologue"; + struct GCEpilogueState { static const char* name; }; + const char* GCEpilogueState::name = "epilogue"; + + template <typename _GCState> + void gcCallback(v8::GCType type, v8::GCCallbackFlags flags) { + if (!logger::globalLogDomain()->shouldLog(logger::LogSeverity::Debug(1))) + // don't collect stats unless verbose + return; + + v8::HeapStatistics stats; + v8::Isolate::GetCurrent()->GetHeapStatistics(&stats); + log() << "V8 GC " << _GCState::name + << " heap stats - " + << " total: " << stats.total_heap_size() + << " exec: " << stats.total_heap_size_executable() + << " used: " << stats.used_heap_size()<< " limit: " + << stats.heap_size_limit() + << endl; + } + + V8ScriptEngine::V8ScriptEngine() : + _globalInterruptLock("GlobalV8InterruptLock"), + _opToScopeMap(), + _deadlineMonitor() { + } + + V8ScriptEngine::~V8ScriptEngine() { + } + + void ScriptEngine::setup() { + if (!globalScriptEngine) { + globalScriptEngine = new V8ScriptEngine(); + } + } + + std::string ScriptEngine::getInterpreterVersionString() { + return "V8 3.25.28"; + } + + void V8ScriptEngine::interrupt(unsigned opId) { + mongo::mutex::scoped_lock 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() << endl; + return; + } + LOG(1) << "interrupting op: " << opId << printKnownOps_inlock() << endl; + iScope->second->kill(); + } + + void V8ScriptEngine::interruptAll() { + mongo::mutex::scoped_lock interruptLock(_globalInterruptLock); + for (OpIdToScopeMap::iterator iScope = _opToScopeMap.begin(); + iScope != _opToScopeMap.end(); ++iScope) { + iScope->second->kill(); + } + } + + void V8Scope::registerOpId() { + scoped_lock giLock(_engine->_globalInterruptLock); + if (_engine->haveGetCurrentOpIdCallback()) { + // this scope has an associated operation + _opId = _engine->getCurrentOpId(); + _engine->_opToScopeMap[_opId] = this; + } + else + // no associated op id (e.g. running from shell) + _opId = 0; + LOG(2) << "V8Scope " << static_cast<const void*>(this) << " registered for op " + << _opId << endl; + } + + void V8Scope::unregisterOpId() { + scoped_lock giLock(_engine->_globalInterruptLock); + LOG(2) << "V8Scope " << static_cast<const void*>(this) << " unregistered for op " + << _opId << endl; + if (_engine->haveGetCurrentOpIdCallback() || _opId != 0) { + // scope is currently associated with an operation id + V8ScriptEngine::OpIdToScopeMap::iterator it = _engine->_opToScopeMap.find(_opId); + if (it != _engine->_opToScopeMap.end()) + _engine->_opToScopeMap.erase(it); + } + } + + bool V8Scope::nativePrologue() { + v8::Locker l(_isolate); + mongo::mutex::scoped_lock cbEnterLock(_interruptLock); + if (v8::V8::IsExecutionTerminating(_isolate)) { + LOG(2) << "v8 execution interrupted. isolate: " + << static_cast<const void*>(_isolate) << endl; + return false; + } + if (_pendingKill || globalScriptEngine->interrupted()) { + // kill flag was set before entering our callback + LOG(2) << "marked for death while leaving callback. isolate: " + << static_cast<const void*>(_isolate) << endl; + v8::V8::TerminateExecution(_isolate); + return false; + } + _inNativeExecution = true; + return true; + } + + bool V8Scope::nativeEpilogue() { + v8::Locker l(_isolate); + mongo::mutex::scoped_lock cbLeaveLock(_interruptLock); + _inNativeExecution = false; + if (v8::V8::IsExecutionTerminating(_isolate)) { + LOG(2) << "v8 execution interrupted. isolate: " + << static_cast<const void*>(_isolate) << endl; + return false; + } + if (_pendingKill || globalScriptEngine->interrupted()) { + LOG(2) << "marked for death while leaving callback. isolate: " + << static_cast<const void*>(_isolate) << endl; + v8::V8::TerminateExecution(_isolate); + return false; + } + return true; + } + + void V8Scope::kill() { + mongo::mutex::scoped_lock interruptLock(_interruptLock); + if (!_inNativeExecution) { + // Set the TERMINATE flag on the stack guard for this isolate. + // This won't happen between calls to nativePrologue and nativeEpilogue(). + v8::V8::TerminateExecution(_isolate); + LOG(1) << "killing v8 scope. isolate: " << static_cast<const void*>(_isolate) << endl; + } + LOG(1) << "marking v8 scope for death. isolate: " << static_cast<const void*>(_isolate) + << endl; + _pendingKill = true; + } + + /** check if there is a pending killOp request */ + bool V8Scope::isKillPending() const { + return _pendingKill || _engine->interrupted(); + } + + /** + * Display a list of all known ops (for verbose output) + */ + std::string V8ScriptEngine::printKnownOps_inlock() { + stringstream out; + if (logger::globalLogDomain()->shouldLog(logger::LogSeverity::Debug(2))) { + out << " known ops: " << endl; + for(OpIdToScopeMap::iterator iSc = _opToScopeMap.begin(); + iSc != _opToScopeMap.end(); ++iSc) { + out << " " << iSc->first << endl; + } + } + return out.str(); + } + + + V8Scope::V8Scope(V8ScriptEngine * engine) + : _engine(engine), + _connectState(NOT), + _cpuProfiler(), + _interruptLock("ScopeInterruptLock"), + _inNativeExecution(true), + _pendingKill(false) { + + // create new isolate and enter it via a scope + _isolate.set(v8::Isolate::New()); + v8::Isolate::Scope iscope(_isolate); + + // lock the isolate and enter the context + v8::Locker l(_isolate); + v8::HandleScope handle_scope(_isolate); + v8::Local<v8::Context> context(v8::Context::New(_isolate)); + _context.Set(_isolate, context); + v8::Context::Scope context_scope(context); + + invariant(_isolate->GetNumberOfDataSlots() >= 1U); + uint32_t slot = 0; + _isolate->SetData(slot, this); + + // display heap statistics on MarkAndSweep GC run + v8::V8::AddGCPrologueCallback(gcCallback<GCPrologueState>, v8::kGCTypeMarkSweepCompact); + v8::V8::AddGCEpilogueCallback(gcCallback<GCEpilogueState>, v8::kGCTypeMarkSweepCompact); + + // create a global (rooted) object + _global.Set(_isolate, context->Global()); + + // Grab the RegExp constructor before user code gets a chance to change it. This ensures + // we can always construct proper RegExps from C++. + v8::Local<v8::Value> regexp = getGlobal()->Get(strLitToV8("RegExp")); + verify(regexp->IsFunction()); + _jsRegExpConstructor.Set(_isolate, regexp.As<v8::Function>()); + + // initialize lazy object template + _LazyBsonFT.Set(_isolate, v8::FunctionTemplate::New(_isolate)); + LazyBsonFT()->InstanceTemplate()->SetInternalFieldCount(2); + LazyBsonFT()->InstanceTemplate()->SetNamedPropertyHandler( + namedGet, namedSet, NULL, namedDelete, namedEnumerator); + LazyBsonFT()->InstanceTemplate()->SetIndexedPropertyHandler( + indexedGet, indexedSet, NULL, indexedDelete, namedEnumerator); + LazyBsonFT()->PrototypeTemplate()->Set(strLitToV8("_bson"), + v8::Boolean::New(_isolate, true), + v8::DontEnum); + + _ROBsonFT.Set(_isolate, v8::FunctionTemplate::New(_isolate)); + ROBsonFT()->Inherit(LazyBsonFT()); // This makes LazyBsonFT()->HasInstance() true + ROBsonFT()->InstanceTemplate()->SetInternalFieldCount(2); + ROBsonFT()->InstanceTemplate()->SetNamedPropertyHandler( + namedGetRO, NamedReadOnlySet, NULL, NamedReadOnlyDelete, namedEnumerator); + ROBsonFT()->InstanceTemplate()->SetIndexedPropertyHandler( + indexedGetRO, IndexedReadOnlySet, NULL, IndexedReadOnlyDelete, NULL); + ROBsonFT()->PrototypeTemplate()->Set(strLitToV8("_bson"), + v8::Boolean::New(_isolate, true), + v8::DontEnum); + + injectV8Function("print", Print); + injectV8Function("version", Version); // TODO: remove + injectV8Function("gc", GCV8); + // injectV8Function("startCpuProfiler", startCpuProfiler); + // injectV8Function("stopCpuProfiler", stopCpuProfiler); + // injectV8Function("getCpuProfile", getCpuProfile); + + // install BSON functions in the global object + installBSONTypes(); + + // load JS helpers (dependancy: installBSONTypes) + execSetup(JSFiles::assert); + execSetup(JSFiles::types); + + // install process-specific utilities in the global scope (dependancy: types.js, assert.js) + if (_engine->_scopeInitCallback) + _engine->_scopeInitCallback(*this); + + // install global utility functions + installGlobalUtils(*this); + + // Don't add anything that can throw after this line otherwise we won't be unregistered. + registerOpId(); + } + + V8Scope::~V8Scope() { + unregisterOpId(); + } + + bool V8Scope::hasOutOfMemoryException() { + V8_SIMPLE_HEADER + if (!_context.IsEmpty()) { + return getContext()->HasOutOfMemoryException(); + } + return false; + } + + v8::Local<v8::Value> V8Scope::load(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value> &args) { + v8::Context::Scope context_scope(scope->getContext()); + for (int i = 0; i < args.Length(); ++i) { + std::string filename(toSTLString(args[i])); + if (!scope->execFile(filename, false, true)) { + return v8AssertionException(string("error loading js file: ") + filename); + } + } + return v8::True(scope->getIsolate()); + } + + v8::Local<v8::Value> V8Scope::nativeCallback(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value> &args) { + BSONObj ret; + string exceptionText; + v8::EscapableHandleScope handle_scope(args.GetIsolate()); + try { + v8::Local<v8::External> f = + v8::Local<v8::External>::Cast(args.Callee()->Get( + scope->strLitToV8("_native_function"))); + NativeFunction function = (NativeFunction)(f->Value()); + v8::Local<v8::External> data = + v8::Local<v8::External>::Cast(args.Callee()->Get( + scope->strLitToV8("_native_data"))); + BSONObjBuilder b; + for (int i = 0; i < args.Length(); ++i) + scope->v8ToMongoElement(b, BSONObjBuilder::numStr(i), args[i]); + BSONObj nativeArgs = b.obj(); + ret = function(nativeArgs, data->Value()); + } + catch (const std::exception &e) { + exceptionText = e.what(); + } + catch (...) { + exceptionText = "unknown exception in V8Scope::nativeCallback"; + } + if (!exceptionText.empty()) { + return v8AssertionException(exceptionText); + } + v8::Local<v8::Value> result_handle(scope->mongoToV8Element(ret.firstElement())); + return handle_scope.Escape(result_handle); + } + + void V8Scope::v8Callback(const v8::FunctionCallbackInfo<v8::Value> &args) { + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = args.GetReturnValue(); + result.Set(val); + + v8::HandleScope handle_scope(args.GetIsolate()); + V8Scope* scope = getScope(args.GetIsolate()); + + if (!scope->nativePrologue()) { + // execution terminated + result.Set(v8::Undefined(args.GetIsolate())); + return; + } + + v8::Local<v8::External> f = + v8::Local<v8::External>::Cast(args.Callee()->Get(scope->strLitToV8("_v8_function"))); + v8Function function = (v8Function)(f->Value()); + v8::Local<v8::Value> ret; + string exceptionText; + + try { + // execute the native function + ret = function(scope, args); + } + catch (const std::exception& e) { + exceptionText = e.what(); + } + catch (...) { + exceptionText = "unknown exception in V8Scope::v8Callback"; + } + + if (!scope->nativeEpilogue()) { + // execution terminated + result.Set(v8::Undefined(args.GetIsolate())); + return; + } + + if (!exceptionText.empty()) { + result.Set(v8AssertionException(exceptionText)); + return; + } + result.Set(ret); + } + + void V8Scope::init(const BSONObj * data) { + if (! data) + return; + + BSONObjIterator i(*data); + while (i.more()) { + BSONElement e = i.next(); + setElement(e.fieldName(), e); + } + } + + void V8Scope::setNumber(const char * field, double val) { + V8_SIMPLE_HEADER + v8::Local<v8::Object> global = getGlobal(); + global->ForceSet(v8StringData(field), v8::Number::New(_isolate, val)); + } + + void V8Scope::setString(const char * field, const StringData& val) { + V8_SIMPLE_HEADER + v8::Local<v8::Object> global = getGlobal(); + global->ForceSet(v8StringData(field), v8StringData(val)); + } + + void V8Scope::setBoolean(const char * field, bool val) { + V8_SIMPLE_HEADER + v8::Local<v8::Object> global = getGlobal(); + global->ForceSet(v8StringData(field), v8::Boolean::New(_isolate, val)); + } + + void V8Scope::setElement(const char *field, const BSONElement& e) { + V8_SIMPLE_HEADER + v8::Local<v8::Object> global = getGlobal(); + global->ForceSet(v8StringData(field), mongoToV8Element(e)); + } + + void V8Scope::setObject(const char *field, const BSONObj& obj, bool readOnly) { + V8_SIMPLE_HEADER + v8::Local<v8::Object> global = getGlobal(); + global->ForceSet(v8StringData(field), + mongoToLZV8(obj, readOnly ? v8::ReadOnly : v8::None)); + } + + int V8Scope::type(const char *field) { + V8_SIMPLE_HEADER + v8::Local<v8::Value> v = get(field); + if (v->IsNull()) + return jstNULL; + if (v->IsUndefined()) + return Undefined; + if (v->IsString()) + return String; + if (v->IsFunction()) + return Code; + if (v->IsArray()) + return Array; + if (v->IsBoolean()) + return Bool; + // needs to be explicit NumberInt to use integer +// if (v->IsInt32()) +// return NumberInt; + if (v->IsNumber()) + return NumberDouble; + if (v->IsExternal()) { + uassert(10230, "can't handle external yet", 0); + return -1; + } + if (v->IsDate()) + return Date; + if (v->IsObject()) + return Object; + + uasserted(12509, str::stream() << "unable to get type of field " << field); + } + + v8::Local<v8::Value> V8Scope::get(const char * field) { + return getGlobal()->Get(v8StringData(field)); + } + + double V8Scope::getNumber(const char *field) { + V8_SIMPLE_HEADER + return get(field)->ToNumber()->Value(); + } + + int V8Scope::getNumberInt(const char *field) { + V8_SIMPLE_HEADER + return get(field)->ToInt32()->Value(); + } + + long long V8Scope::getNumberLongLong(const char *field) { + V8_SIMPLE_HEADER + return get(field)->ToInteger()->Value(); + } + + string V8Scope::getString(const char *field) { + V8_SIMPLE_HEADER + return toSTLString(get(field)); + } + + bool V8Scope::getBoolean(const char *field) { + V8_SIMPLE_HEADER + return get(field)->ToBoolean()->Value(); + } + + BSONObj V8Scope::getObject(const char * field) { + V8_SIMPLE_HEADER + v8::Local<v8::Value> v = get(field); + if (v->IsNull() || v->IsUndefined()) + return BSONObj(); + uassert(10231, "not an object", v->IsObject()); + return v8ToMongo(v->ToObject()); + } + + v8::Local<v8::FunctionTemplate> getNumberLongFunctionTemplate(V8Scope* scope) { + v8::Local<v8::FunctionTemplate> numberLong = scope->createV8Function(numberLongInit); + v8::Local<v8::ObjectTemplate> proto = numberLong->PrototypeTemplate(); + scope->injectV8Method("valueOf", numberLongValueOf, proto); + scope->injectV8Method("toNumber", numberLongToNumber, proto); + scope->injectV8Method("toString", numberLongToString, proto); + return numberLong; + } + + v8::Local<v8::FunctionTemplate> getNumberIntFunctionTemplate(V8Scope* scope) { + v8::Local<v8::FunctionTemplate> numberInt = scope->createV8Function(numberIntInit); + v8::Local<v8::ObjectTemplate> proto = numberInt->PrototypeTemplate(); + scope->injectV8Method("valueOf", numberIntValueOf, proto); + scope->injectV8Method("toNumber", numberIntToNumber, proto); + scope->injectV8Method("toString", numberIntToString, proto); + return numberInt; + } + + v8::Local<v8::FunctionTemplate> getBinDataFunctionTemplate(V8Scope* scope) { + v8::Local<v8::FunctionTemplate> binData = scope->createV8Function(binDataInit); + binData->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local<v8::ObjectTemplate> proto = binData->PrototypeTemplate(); + scope->injectV8Method("toString", binDataToString, proto); + scope->injectV8Method("base64", binDataToBase64, proto); + scope->injectV8Method("hex", binDataToHex, proto); + return binData; + } + + v8::Local<v8::FunctionTemplate> getTimestampFunctionTemplate(V8Scope* scope) { + v8::Local<v8::FunctionTemplate> ts = scope->createV8Function(dbTimestampInit); + return ts; + } + + v8::Local<v8::Value> minKeyToJson(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + // MinKey can't just be an object like {$minKey:1} since insert() checks for fields that + // start with $ and raises an error. See DBCollection.prototype._validateForStorage(). + return scope->strLitToV8("{ \"$minKey\" : 1 }"); + } + + void minKeyCall(const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = args.GetReturnValue(); + result.Set(val); + // 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. + V8Scope* scope = getScope(args.GetIsolate()); + + v8::Local<v8::Function> func = scope->MinKeyFT()->GetFunction(); + v8::Local<v8::String> name = scope->strLitToV8("singleton"); + v8::Local<v8::Value> singleton = func->GetHiddenValue(name); + if (!singleton.IsEmpty()) { + result.Set(singleton); + return; + } + + if (!args.IsConstructCall()) { + result.Set(func->NewInstance()); + return; + } + + verify(scope->MinKeyFT()->HasInstance(args.This())); + + func->SetHiddenValue(name, args.This()); + result.Set(v8::Undefined(args.GetIsolate())); + } + + v8::Local<v8::FunctionTemplate> getMinKeyFunctionTemplate(V8Scope* scope) { + v8::Local<v8::FunctionTemplate> myTemplate = + v8::FunctionTemplate::New(scope->getIsolate(), minKeyCall); + myTemplate->InstanceTemplate()->SetCallAsFunctionHandler(minKeyCall); + myTemplate->PrototypeTemplate()->Set(scope->v8StringData("tojson"), + scope->createV8Function(minKeyToJson)->GetFunction()); + myTemplate->SetClassName(scope->strLitToV8("MinKey")); + return myTemplate; + } + + v8::Local<v8::Value> maxKeyToJson(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + return scope->strLitToV8("{ \"$maxKey\" : 1 }"); + } + + void maxKeyCall(const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = args.GetReturnValue(); + result.Set(val); + // See comment in minKeyCall. + V8Scope* scope = getScope(args.GetIsolate()); + + v8::Local<v8::Function> func = scope->MaxKeyFT()->GetFunction(); + v8::Local<v8::String> name = scope->strLitToV8("singleton"); + v8::Local<v8::Value> singleton = func->GetHiddenValue(name); + if (!singleton.IsEmpty()) { + result.Set(singleton); + return; + } + + if (!args.IsConstructCall()) { + result.Set(func->NewInstance()); + return; + } + + verify(scope->MaxKeyFT()->HasInstance(args.This())); + + func->SetHiddenValue(name, args.This()); + result.Set(v8::Undefined(args.GetIsolate())); + } + + v8::Local<v8::FunctionTemplate> getMaxKeyFunctionTemplate(V8Scope* scope) { + v8::Local<v8::FunctionTemplate> myTemplate = + v8::FunctionTemplate::New(scope->getIsolate(), maxKeyCall); + myTemplate->InstanceTemplate()->SetCallAsFunctionHandler(maxKeyCall); + myTemplate->PrototypeTemplate()->Set(scope->v8StringData("tojson"), + scope->createV8Function(maxKeyToJson)->GetFunction()); + myTemplate->SetClassName(scope->strLitToV8("MaxKey")); + return myTemplate; + } + + std::string V8Scope::v8ExceptionToSTLString(const v8::TryCatch* try_catch) { + stringstream ss; + v8::String::Utf8Value exceptionText(try_catch->Exception()); + ss << *exceptionText; + + // get the exception message + v8::Local<v8::Message> message = try_catch->Message(); + if (message.IsEmpty()) + return ss.str(); + + // get the resource (e.g. file or internal call) + v8::String::Utf8Value resourceName(message->GetScriptResourceName()); + if (!*resourceName) + return ss.str(); + + string resourceNameString = *resourceName; + if (resourceNameString.compare("undefined") == 0) + return ss.str(); + if (resourceNameString.find("_funcs") == 0) { + // script loaded from __createFunction + string code; + // find the source script based on the resource name supplied to v8::Script::Compile(). + // this is accomplished by converting the integer after the '_funcs' prefix. + unsigned int funcNum = str::toUnsigned(resourceNameString.substr(6)); + for (map<string, ScriptingFunction>::iterator it = getFunctionCache().begin(); + it != getFunctionCache().end(); + ++it) { + if (it->second == funcNum) { + code = it->first; + break; + } + } + if (!code.empty()) { + // append surrounding code (padded with up to 20 characters on each side) + int startPos = message->GetStartPosition(); + const int kPadding = 20; + if (startPos - kPadding < 0) + // lower bound exceeded + startPos = 0; + else + startPos -= kPadding; + + int displayRange = message->GetEndPosition(); + if (displayRange + kPadding > static_cast<int>(code.length())) + // upper bound exceeded + displayRange -= startPos; + else + // compensate for startPos padding + displayRange = (displayRange - startPos) + kPadding; + + if (startPos > static_cast<int>(code.length()) || + displayRange > static_cast<int>(code.length())) + return ss.str(); + + string codeNear = code.substr(startPos, displayRange); + for (size_t newLine = codeNear.find('\n'); + newLine != string::npos; + newLine = codeNear.find('\n')) { + if (static_cast<int>(newLine) > displayRange - kPadding) { + // truncate at first newline past the reported end position + codeNear = codeNear.substr(0, newLine - 1); + break; + } + // convert newlines to spaces + codeNear.replace(newLine, 1, " "); + } + // trim leading chars + codeNear = str::ltrim(codeNear); + ss << " near '" << codeNear << "' "; + const int linenum = message->GetLineNumber(); + if (linenum != 1) + ss << " (line " << linenum << ")"; + } + } + else if (resourceNameString.find("(shell") == 0) { + // script loaded from shell input -- simply print the error + } + else { + // script loaded from file + ss << " at " << *resourceName; + const int linenum = message->GetLineNumber(); + if (linenum != 1) ss << ":" << linenum; + } + return ss.str(); + } + + // --- functions ----- + + bool hasFunctionIdentifier(const StringData& code) { + if (code.size() < 9 || code.find("function") != 0 ) + return false; + + return code[8] == ' ' || code[8] == '('; + } + + v8::Local<v8::Function> V8Scope::__createFunction(const char* raw, + ScriptingFunction functionNumber) { + v8::EscapableHandleScope handle_scope(_isolate); + v8::TryCatch try_catch; + raw = jsSkipWhiteSpace(raw); + string code = raw; + if (!hasFunctionIdentifier(code)) { + if (code.find('\n') == string::npos && + ! hasJSReturn(code) && + (code.find(';') == string::npos || code.find(';') == code.size() - 1)) { + code = "return " + code; + } + code = "function(){ " + code + "}"; + } + + string fn = str::stream() << "_funcs" << functionNumber; + code = str::stream() << fn << " = " << code; + + v8::Local<v8::Script> script = v8::Script::Compile( + v8StringData(code), + v8StringData(fn)); + + // throw on error + checkV8ErrorState(script, try_catch); + + v8::Local<v8::Value> result = script->Run(); + + // throw on error + checkV8ErrorState(result, try_catch); + + return handle_scope.Escape(v8::Local<v8::Function>::Cast( + getGlobal()->Get(v8StringData(fn)))); + } + + ScriptingFunction V8Scope::_createFunction(const char* raw, ScriptingFunction functionNumber) { + V8_SIMPLE_HEADER + v8::Local<v8::Value> ret = __createFunction(raw, functionNumber); + v8::Eternal<v8::Value> f(_isolate, ret); + uassert(10232, "not a function", ret->IsFunction()); + _funcs.push_back(f); + return functionNumber; + } + + void V8Scope::setFunction(const char* field, const char* code) { + V8_SIMPLE_HEADER + getGlobal()->ForceSet(v8StringData(field), + __createFunction(code, getFunctionCache().size() + 1)); + } + + void V8Scope::rename(const char * from, const char * to) { + V8_SIMPLE_HEADER; + v8::Local<v8::String> f = v8StringData(from); + v8::Local<v8::String> t = v8StringData(to); + v8::Local<v8::Object> global = getGlobal(); + global->ForceSet(t, global->Get(f)); + global->ForceSet(f, v8::Undefined(_isolate)); + } + + int V8Scope::invoke(ScriptingFunction func, const BSONObj* argsObject, const BSONObj* recv, + int timeoutMs, bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv) { + V8_SIMPLE_HEADER + v8::Local<v8::Value> funcValue = _funcs[func-1].Get(_isolate); + v8::TryCatch try_catch; + v8::Local<v8::Value> result; + + // TODO SERVER-8016: properly allocate handles on the stack + static const int MAX_ARGS = 24; + const int nargs = argsObject ? argsObject->nFields() : 0; + uassert(16862, "Too many arguments. Max is 24", + nargs <= MAX_ARGS); + + v8::Local<v8::Value> args[MAX_ARGS]; + if (nargs) { + BSONObjIterator it(*argsObject); + for (int i=0; i<nargs; i++) { + BSONElement next = it.next(); + args[i] = mongoToV8Element(next, readOnlyArgs); + } + } + + v8::Local<v8::Object> v8recv; + if (recv != 0) + v8recv = mongoToLZV8(*recv, readOnlyRecv); + else + v8recv = getGlobal(); + + if (!nativeEpilogue()) { + _error = "JavaScript execution terminated"; + log() << _error << endl; + uasserted(16711, _error); + } + + if (timeoutMs) + // start the deadline timer for this script + _engine->getDeadlineMonitor()->startDeadline(this, timeoutMs); + + result = ((v8::Function*)(*funcValue))->Call(v8recv, nargs, nargs ? args : NULL); + + if (timeoutMs) + // stop the deadline timer for this script + _engine->getDeadlineMonitor()->stopDeadline(this); + + if (!nativePrologue()) { + _error = "JavaScript execution terminated"; + log() << _error << endl; + uasserted(16712, _error); + } + + // throw on error + checkV8ErrorState(result, try_catch); + + if (!ignoreReturn) { + v8::Local<v8::Object> resultObject = result->ToObject(); + // must validate the handle because TerminateExecution may have + // been thrown after the above checks + if (!resultObject.IsEmpty() && resultObject->Has(strLitToV8("_v8_function"))) { + log() << "storing native function as return value" << endl; + _lastRetIsNativeCode = true; + } + else { + _lastRetIsNativeCode = false; + } + v8::Local<v8::Object> global = getGlobal(); + global->ForceSet(strLitToV8("__returnValue"), result); + } + + return 0; + } + + bool V8Scope::exec(const StringData& code, const string& name, bool printResult, + bool reportError, bool assertOnError, int timeoutMs) { + V8_SIMPLE_HEADER + v8::TryCatch try_catch; + + v8::Local<v8::Script> script = v8::Script::Compile(v8StringData(code), + v8StringData(name)); + + if (checkV8ErrorState(script, try_catch, reportError, assertOnError)) + return false; + + if (!nativeEpilogue()) { + _error = "JavaScript execution terminated"; + if (reportError) + log() << _error << endl; + if (assertOnError) + uasserted(13475, _error); + return false; + } + + if (timeoutMs) + // start the deadline timer for this script + _engine->getDeadlineMonitor()->startDeadline(this, timeoutMs); + + v8::Local<v8::Value> result = script->Run(); + + if (timeoutMs) + // stopt the deadline timer for this script + _engine->getDeadlineMonitor()->stopDeadline(this); + + if (!nativePrologue()) { + _error = "JavaScript execution terminated"; + if (reportError) + log() << _error << endl; + if (assertOnError) + uasserted(16721, _error); + return false; + } + + if (checkV8ErrorState(result, try_catch, reportError, assertOnError)) + return false; + + v8::Local<v8::Object> global = getGlobal(); + global->ForceSet(strLitToV8("__lastres__"), result); + + if (printResult && !result->IsUndefined()) { + // appears to only be used by shell + cout << V8String(result) << endl; + } + + return true; + } + + void V8Scope::injectNative(const char *field, NativeFunction func, void* data) { + V8_SIMPLE_HEADER // required due to public access + v8::Local<v8::Object> global = getGlobal(); + injectNative(field, func, global, data); + } + + void V8Scope::injectNative(const char *field, NativeFunction func, v8::Local<v8::Object>& obj, + void* data) { + v8::Local<v8::FunctionTemplate> ft = createV8Function(nativeCallback); + ft->Set(strLitToV8("_native_function"), + v8::External::New(_isolate, (void*)func), + v8::PropertyAttribute(v8::DontEnum | v8::ReadOnly)); + ft->Set(strLitToV8("_native_data"), + v8::External::New(_isolate, data), + v8::PropertyAttribute(v8::DontEnum | v8::ReadOnly)); + injectV8Function(field, ft, obj); + } + + v8::Local<v8::FunctionTemplate> V8Scope::injectV8Function(const char *field, + v8Function func) { + v8::Local<v8::Object> global = getGlobal(); + return injectV8Function(field, func, global); + } + + v8::Local<v8::FunctionTemplate> V8Scope::injectV8Function(const char *field, + v8Function func, + v8::Local<v8::Object> obj) { + return injectV8Function(field, createV8Function(func), obj); + } + + v8::Local<v8::FunctionTemplate> V8Scope::injectV8Function(const char *fieldCStr, + v8::Local<v8::FunctionTemplate> ft, + v8::Local<v8::Object> obj) { + v8::Local<v8::String> field = v8StringData(fieldCStr); + ft->SetClassName(field); + v8::Local<v8::Function> func = ft->GetFunction(); + func->SetName(field); + obj->ForceSet(field, func); + return ft; + } + + v8::Local<v8::FunctionTemplate> V8Scope::injectV8Method( + const char *fieldCStr, + v8Function func, + v8::Local<v8::ObjectTemplate>& proto) { + v8::Local<v8::String> field = v8StringData(fieldCStr); + v8::Local<v8::FunctionTemplate> ft = createV8Function(func); + v8::Local<v8::Function> f = ft->GetFunction(); + f->SetName(field); + proto->Set(field, f); + return ft; + } + + v8::Local<v8::FunctionTemplate> V8Scope::createV8Function(v8Function func) { + v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(_isolate, v8Callback); + ft->Set(strLitToV8("_v8_function"), + v8::External::New(_isolate, reinterpret_cast<void*>(func)), + static_cast<v8::PropertyAttribute>(v8::DontEnum | v8::ReadOnly)); + return ft; + } + + void V8Scope::gc() { + V8_SIMPLE_HEADER + // trigger low memory notification. for more granular control over garbage + // collection cycle, @see v8::V8::IdleNotification. + v8::V8::LowMemoryNotification(); + } + + void V8Scope::localConnect(const char * dbName) { + { + V8_SIMPLE_HEADER + if (_connectState == EXTERNAL) + uasserted(12510, "externalSetup already called, can't call localConnect"); + if (_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 + _MongoFT.Set(_isolate, getMongoFunctionTemplate(this, true)); + v8::Local<v8::Object> global = getGlobal(); + injectV8Function("Mongo", MongoFT(), global); + execCoreFiles(); + exec("_mongo = new Mongo();", "local connect 2", false, true, true, 0); + exec((string)"db = _mongo.getDB(\"" + dbName + "\");", "local connect 3", + false, true, true, 0); + _connectState = LOCAL; + _localDBName = dbName; + } + loadStored(); + } + + void V8Scope::externalSetup() { + V8_SIMPLE_HEADER + if (_connectState == EXTERNAL) + return; + if (_connectState == LOCAL) + uasserted(12512, "localConnect already called, can't call externalSetup"); + + // install db access functions in the global object + installDBAccess(); + + // install thread-related functions (e.g. _threadInject) + installFork(this, getGlobal(), getContext()); + + // install 'load' helper function + injectV8Function("load", load); + + // install the Mongo function object + _MongoFT.Set(_isolate, getMongoFunctionTemplate(this, false)); + injectV8Function("Mongo", MongoFT(), getGlobal()); + execCoreFiles(); + _connectState = EXTERNAL; + } + + void V8Scope::installDBAccess() { + _DBFT.Set(_isolate, createV8Function(dbInit)); + _DBQueryFT.Set(_isolate, createV8Function(dbQueryInit)); + _DBCollectionFT.Set(_isolate, createV8Function(collectionInit)); + + // These must be done before calling injectV8Function + DBFT()->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter, collectionSetter); + DBQueryFT()->InstanceTemplate()->SetIndexedPropertyHandler(dbQueryIndexAccess); + DBCollectionFT()->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter, + collectionSetter); + + v8::Local<v8::Object> global = getGlobal(); + injectV8Function("DB", DBFT(), global); + injectV8Function("DBQuery", DBQueryFT(), global); + injectV8Function("DBCollection", DBCollectionFT(), global); + + // The internal cursor type isn't exposed to the users at all + _InternalCursorFT.Set(_isolate, getInternalCursorFunctionTemplate(this)); + } + + void V8Scope::installBSONTypes() { + _ObjectIdFT.Set(_isolate, injectV8Function("ObjectId", objectIdInit)); + _DBRefFT.Set(_isolate, injectV8Function("DBRef", dbRefInit)); + _DBPointerFT.Set(_isolate, injectV8Function("DBPointer", dbPointerInit)); + + _BinDataFT.Set(_isolate, getBinDataFunctionTemplate(this)); + _NumberLongFT.Set(_isolate, getNumberLongFunctionTemplate(this)); + _NumberIntFT.Set(_isolate, getNumberIntFunctionTemplate(this)); + _TimestampFT.Set(_isolate, getTimestampFunctionTemplate(this)); + _MinKeyFT.Set(_isolate, getMinKeyFunctionTemplate(this)); + _MaxKeyFT.Set(_isolate, getMaxKeyFunctionTemplate(this)); + + v8::Local<v8::Object> global = getGlobal(); + injectV8Function("BinData", BinDataFT(), global); + injectV8Function("NumberLong", NumberLongFT(), global); + injectV8Function("NumberInt", NumberIntFT(), global); + injectV8Function("Timestamp", TimestampFT(), global); + + // These are instances created from the functions, not the functions themselves + global->ForceSet(strLitToV8("MinKey"), MinKeyFT()->GetFunction()->NewInstance()); + global->ForceSet(strLitToV8("MaxKey"), MaxKeyFT()->GetFunction()->NewInstance()); + + // These all create BinData objects so we don't need to hold on to them. + injectV8Function("UUID", uuidInit); + injectV8Function("MD5", md5Init); + injectV8Function("HexData", hexDataInit); + + injectV8Function("bsonWoCompare", bsonWoCompare); + + global->Get(strLitToV8("Object"))->ToObject()->ForceSet( + strLitToV8("bsonsize"), + createV8Function(bsonsize)->GetFunction()); + global->Get(strLitToV8("Object"))->ToObject()->ForceSet( + strLitToV8("invalidForStorage"), + createV8Function(v8ObjectInvalidForStorage)->GetFunction()); + } + + + // ----- internal ----- + + void V8Scope::reset() { + V8_SIMPLE_HEADER + unregisterOpId(); + _error = ""; + _pendingKill = false; + _inNativeExecution = true; + registerOpId(); + } + + v8::Local<v8::Value> V8Scope::newFunction(const StringData& code) { + v8::EscapableHandleScope handle_scope(_isolate); + v8::TryCatch try_catch; + string codeStr = str::stream() << "____MongoToV8_newFunction_temp = " << code; + + v8::Local<v8::Script> compiled = v8::Script::Compile(v8StringData(codeStr)); + + // throw on compile error + checkV8ErrorState(compiled, try_catch); + + v8::Local<v8::Value> ret = compiled->Run(); + + // throw on run/assignment error + checkV8ErrorState(ret, try_catch); + + return handle_scope.Escape(ret); + } + + v8::Local<v8::Value> V8Scope::newId(const OID &id) { + v8::EscapableHandleScope handle_scope(_isolate); + v8::Local<v8::Function> idCons = ObjectIdFT()->GetFunction(); + v8::Local<v8::Value> argv[1]; + const string& idString = id.str(); + argv[0] = v8StringData(idString); + return handle_scope.Escape(idCons->NewInstance(1, argv)); + } + + /** + * converts a BSONObj to a Lazy V8 object + */ + v8::Local<v8::Object> V8Scope::mongoToLZV8(const BSONObj& m, bool readOnly) { + if (m.firstElementType() == String && str::equals(m.firstElementFieldName(), "$ref")) { + BSONObjIterator it(m); + const BSONElement ref = it.next(); + const BSONElement id = it.next(); + if (id.ok() && str::equals(id.fieldName(), "$id")) { + v8::Local<v8::Value> args[] = { + mongoToV8Element(ref, readOnly), + mongoToV8Element(id, readOnly) + }; + v8::Local<v8::Object> dbRef = DBRefFT()->GetFunction()->NewInstance(2, args); + while (it.more()) { + BSONElement elem = it.next(); + dbRef->Set(v8StringData(elem.fieldName()), mongoToV8Element(elem, readOnly)); + } + return dbRef; + } + } + + v8::Local<v8::FunctionTemplate> templ = readOnly ? ROBsonFT() : LazyBsonFT(); + v8::Local<v8::Object> o = templ->GetFunction()->NewInstance(); + massert(16496, str::stream() << "V8: NULL Object template instantiated. " + << (v8::V8::IsExecutionTerminating() ? + "v8 execution is terminating." : + "v8 still executing."), + *o != NULL); + + wrapBSONObject(o, m, readOnly); + return o; + } + + v8::Local<v8::Value> V8Scope::mongoToV8Element(const BSONElement &elem, bool readOnly) { + v8::Local<v8::Value> argv[3]; // arguments for v8 instance constructors + v8::Local<v8::Object> instance; // instance of v8 type + uint64_t nativeUnsignedLong; // native representation of NumberLong + + switch (elem.type()) { + case mongo::Code: + return newFunction(StringData(elem.valuestr(), elem.valuestrsize() - 1)); + case CodeWScope: + if (!elem.codeWScopeObject().isEmpty()) + log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; + return newFunction(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen() - 1)); + case mongo::Symbol: + case mongo::String: { + return v8StringData(StringData(elem.valuestr(), elem.valuestrsize() - 1)); + } + case mongo::jstOID: + return newId(elem.__oid()); + case mongo::NumberDouble: + case mongo::NumberInt: + return v8::Number::New(_isolate, elem.number()); + case mongo::Array: { + // NB: This comment may no longer be accurate. + // for arrays it's better to use non lazy object because: + // - the lazy array is not a true v8 array and requires some v8 src change + // for all methods to work + // - it made several tests about 1.5x slower + // - most times when an array is accessed, all its values will be used + + // It is faster to allow the v8::Array to grow than call nFields() on the array + v8::Local<v8::Array> array = v8::Array::New(_isolate); + int i = 0; + BSONForEach(subElem, elem.embeddedObject()) { + array->Set(i++, mongoToV8Element(subElem, readOnly)); + } + return array; + } + case mongo::Object: + return mongoToLZV8(elem.embeddedObject(), readOnly); + case mongo::Date: + return v8::Date::New(_isolate, (double) ((long long)elem.date().millis)); + case mongo::Bool: + return v8::Boolean::New(_isolate, elem.boolean()); + case mongo::EOO: + case mongo::jstNULL: + case mongo::Undefined: // duplicate sm behavior + return v8::Null(_isolate); + case mongo::RegEx: { + // TODO parse into a custom type that can support any patterns and flags SERVER-9803 + v8::TryCatch tryCatch; + + v8::Local<v8::Value> args[] = { + v8StringData(elem.regex()), + v8StringData(elem.regexFlags()) + }; + + v8::Local<v8::Value> ret = _jsRegExpConstructor.Get(_isolate)->NewInstance(2, args); + uassert(16863, str::stream() << "Error converting " << elem.toString(false) + << " in field " << elem.fieldName() + << " to a JS RegExp object: " + << toSTLString(tryCatch.Exception()), + !tryCatch.HasCaught()); + + return ret; + } + case mongo::BinData: { + int len; + const char *data = elem.binData(len); + stringstream ss; + base64::encode(ss, data, len); + argv[0] = v8::Number::New(_isolate, elem.binDataType()); + argv[1] = v8StringData(ss.str()); + return BinDataFT()->GetFunction()->NewInstance(2, argv); + } + case mongo::Timestamp: { + v8::TryCatch tryCatch; + + argv[0] = v8::Number::New(_isolate, elem.timestampTime() / 1000); + argv[1] = v8::Number::New(_isolate, elem.timestampInc()); + + v8::Local<v8::Value> ret = TimestampFT()->GetFunction()->NewInstance(2,argv); + uassert(17355, str::stream() << "Error converting " << elem.toString(false) + << " in field " << elem.fieldName() + << " to a JS Timestamp object: " + << toSTLString(tryCatch.Exception()), + !tryCatch.HasCaught()); + + return ret; + } + case mongo::NumberLong: + nativeUnsignedLong = elem.numberLong(); + // values above 2^53 are not accurately represented in JS + if ((long long)nativeUnsignedLong == + (long long)(double)(long long)(nativeUnsignedLong) && + nativeUnsignedLong < 9007199254740992ULL) { + argv[0] = v8::Number::New(_isolate, (double)(long long)(nativeUnsignedLong)); + return NumberLongFT()->GetFunction()->NewInstance(1, argv); + } + else { + argv[0] = v8::Number::New(_isolate, (double)(long long)(nativeUnsignedLong)); + argv[1] = v8::Integer::New(_isolate, nativeUnsignedLong >> 32); + argv[2] = v8::Integer::New(_isolate, (unsigned long) + (nativeUnsignedLong & 0x00000000ffffffff)); + return NumberLongFT()->GetFunction()->NewInstance(3, argv); + } + case mongo::MinKey: + return MinKeyFT()->GetFunction()->NewInstance(); + case mongo::MaxKey: + return MaxKeyFT()->GetFunction()->NewInstance(); + case mongo::DBRef: + argv[0] = v8StringData(elem.dbrefNS()); + argv[1] = newId(elem.dbrefOID()); + return DBPointerFT()->GetFunction()->NewInstance(2, argv); + default: + massert(16661, str::stream() << "can't handle type: " << elem.type() + << " " << elem.toString(), false); + break; + } + return v8::Undefined(_isolate); + } + + void V8Scope::v8ToMongoNumber(BSONObjBuilder& b, + const StringData& elementName, + v8::Local<v8::Number> value, + BSONObj* originalParent) { + double val = value->Value(); + // 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(elementName); + if (elmt.type() == mongo::NumberInt) { + b.append(elementName, intval); + return; + } + } + b.append(elementName, val); + } + + void V8Scope::v8ToMongoRegex(BSONObjBuilder& b, + const StringData& elementName, + v8::Local<v8::RegExp> v8Regex) { + V8String v8RegexString (v8Regex); + StringData regex = v8RegexString; + regex = regex.substr(1); + StringData r = regex.substr(0 ,regex.rfind('/')); + StringData o = regex.substr(regex.rfind('/') + 1); + b.appendRegex(elementName, r, o); + } + + void V8Scope::v8ToMongoDBRef(BSONObjBuilder& b, + const StringData& elementName, + v8::Local<v8::Object> obj) { + verify(DBPointerFT()->HasInstance(obj)); + v8::Local<v8::Value> theid = obj->Get(strLitToV8("id")); + OID oid = v8ToMongoObjectID(theid->ToObject()); + string ns = toSTLString(obj->Get(strLitToV8("ns"))); + b.appendDBRef(elementName, ns, oid); + } + + void V8Scope::v8ToMongoBinData(BSONObjBuilder& b, + const StringData& elementName, + v8::Local<v8::Object> obj) { + + verify(BinDataFT()->HasInstance(obj)); + verify(obj->InternalFieldCount() == 1); + int len = obj->Get(strLitToV8("len"))->ToInt32()->Value(); + b.appendBinData(elementName, + len, + mongo::BinDataType(obj->Get(strLitToV8("type"))->ToInt32()->Value()), + base64::decode(toSTLString(obj->GetInternalField(0))).c_str()); + } + + OID V8Scope::v8ToMongoObjectID(v8::Local<v8::Object> obj) { + verify(ObjectIdFT()->HasInstance(obj)); + const string hexStr = toSTLString(obj->Get(strLitToV8("str"))); + + // OID parser doesn't have user-friendly error messages + uassert(16864, "ObjectID.str must be exactly 24 chars long", + hexStr.size() == 24); + uassert(16865, "ObjectID.str must only have hex characters [0-1a-fA-F]", + count_if(hexStr.begin(), hexStr.end(), ::isxdigit) == 24); + + return OID(hexStr); + } + + void V8Scope::v8ToMongoObject(BSONObjBuilder& b, + const StringData& elementName, + v8::Local<v8::Value> value, + int depth, + BSONObj* originalParent) { + verify(value->IsObject()); + v8::Local<v8::Object> obj = value.As<v8::Object>(); + + if (value->IsRegExp()) { + v8ToMongoRegex(b, elementName, obj.As<v8::RegExp>()); + } else if (ObjectIdFT()->HasInstance(value)) { + b.append(elementName, v8ToMongoObjectID(obj)); + } else if (NumberLongFT()->HasInstance(value)) { + b.append(elementName, numberLongVal(this, obj)); + } else if (NumberIntFT()->HasInstance(value)) { + b.append(elementName, numberIntVal(this, obj)); + } else if (DBPointerFT()->HasInstance(value)) { + v8ToMongoDBRef(b, elementName, obj); + } else if (BinDataFT()->HasInstance(value)) { + v8ToMongoBinData(b, elementName, obj); + } else if (TimestampFT()->HasInstance(value)) { + OpTime ot (obj->Get(strLitToV8("t"))->Uint32Value(), + obj->Get(strLitToV8("i"))->Uint32Value()); + b.append(elementName, ot); + } else if (MinKeyFT()->HasInstance(value)) { + b.appendMinKey(elementName); + } else if (MaxKeyFT()->HasInstance(value)) { + b.appendMaxKey(elementName); + } else { + // nested object or array + BSONObj sub = v8ToMongo(obj, depth); + b.append(elementName, sub); + } + } + + void V8Scope::v8ToMongoElement(BSONObjBuilder & b, const StringData& sname, + v8::Local<v8::Value> value, int depth, + BSONObj* originalParent) { + uassert(17279, + str::stream() << "Exceeded depth limit of " << objectDepthLimit + << " when converting js object to BSON. Do you have a cycle?", + depth < objectDepthLimit); + + // 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->jsonString(), + (string::npos == sname.find('\0')) ); + + if (value->IsString()) { + b.append(sname, V8String(value)); + return; + } + if (value->IsFunction()) { + uassert(16716, "cannot convert native function to BSON", + !value->ToObject()->Has(strLitToV8("_v8_function"))); + b.appendCode(sname, V8String(value)); + return; + } + if (value->IsNumber()) { + v8ToMongoNumber(b, sname, value.As<v8::Number>(), originalParent); + return; + } + if (value->IsArray()) { + // Note: can't use BSONArrayBuilder because need to call recursively + BSONObjBuilder arrBuilder(b.subarrayStart(sname)); + v8::Local<v8::Array> array = value.As<v8::Array>(); + const int len = array->Length(); + for (int i=0; i < len; i++) { + const string name = BSONObjBuilder::numStr(i); + v8ToMongoElement(arrBuilder, name, array->Get(i), depth+1, originalParent); + } + return; + } + if (value->IsDate()) { + long long dateval = (long long)(v8::Date::Cast(*value)->NumberValue()); + b.appendDate(sname, Date_t((unsigned long long) dateval)); + return; + } + if (value->IsExternal()) + return; + if (value->IsObject()) { + v8ToMongoObject(b, sname, value, depth, originalParent); + return; + } + + if (value->IsBoolean()) { + b.appendBool(sname, value->ToBoolean()->Value()); + return; + } + else if (value->IsUndefined()) { + b.appendUndefined(sname); + return; + } + else if (value->IsNull()) { + b.appendNull(sname); + return; + } + uasserted(16662, str::stream() << "unable to convert JavaScript property to mongo element " + << sname); + } + + BSONObj V8Scope::v8ToMongo(v8::Local<v8::Object> o, int depth) { + BSONObj originalBSON; + if (LazyBsonFT()->HasInstance(o)) { + originalBSON = unwrapBSONObj(this, o); + BSONHolder* holder = unwrapHolder(this, o); + if (holder && !holder->_modified) { + // object was not modified, use bson as is + return originalBSON; + } + } + + BSONObjBuilder b; + + // We special case the _id field in top-level objects and move it to the front. + // This matches other drivers behavior and makes finding the _id field quicker in BSON. + if (depth == 0) { + if (o->HasOwnProperty(strLitToV8("_id"))) { + v8ToMongoElement(b, "_id", o->Get(strLitToV8("_id")), 0, &originalBSON); + } + } + + v8::Local<v8::Array> names = o->GetOwnPropertyNames(); + for (unsigned int i=0; i<names->Length(); i++) { + v8::Local<v8::String> name = names->Get(i)->ToString(); + + if (depth == 0 && name->StrictEquals(strLitToV8("_id"))) + continue; // already handled above + + V8String sname(name); + v8::Local<v8::Value> value = o->Get(name); + v8ToMongoElement(b, sname, value, depth + 1, &originalBSON); + } + + const int sizeWithEOO = b.len() + 1/*EOO*/ - 4/*BSONObj::Holder ref count*/; + uassert(17260, str::stream() << "Converting from JavaScript to BSON failed: " + << "Object size " << sizeWithEOO << " exceeds limit of " + << BSONObjMaxInternalSize << " bytes.", + sizeWithEOO <= BSONObjMaxInternalSize); + + return b.obj(); // Would give an uglier error than above for oversized objects. + } + + // --- random utils ---- + + static logger::MessageLogDomain* jsPrintLogDomain; + v8::Local<v8::Value> V8Scope::Print(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + LogstreamBuilder builder(jsPrintLogDomain, getThreadName(), logger::LogSeverity::Log()); + std::ostream& ss = builder.stream(); + v8::EscapableHandleScope handle_scope(args.GetIsolate()); + bool first = true; + for (int i = 0; i < args.Length(); i++) { + if (first) + first = false; + else + ss << " "; + + if (args[i].IsEmpty()) { + // failed to get object to convert + ss << "[unknown type]"; + continue; + } + if (args[i]->IsExternal()) { + // object is External + ss << "[mongo internal]"; + continue; + } + + v8::String::Utf8Value str(args[i]); + ss << *str; + } + ss << "\n"; + return handle_scope.Escape(v8::Local<v8::Value>(v8::Undefined(scope->getIsolate()))); + } + + v8::Local<v8::Value> V8Scope::Version(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::EscapableHandleScope handle_scope(args.GetIsolate()); + return handle_scope.Escape(scope->v8StringData(v8::V8::GetVersion())); + } + + v8::Local<v8::Value> V8Scope::GCV8(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + // trigger low memory notification. for more granular control over garbage + // collection cycle, @see v8::V8::IdleNotification. + v8::V8::LowMemoryNotification(); + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> V8Scope::startCpuProfiler(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (args.Length() != 1 || !args[0]->IsString()) { + return v8AssertionException("startCpuProfiler takes a string argument"); + } + scope->_cpuProfiler.start(scope->_isolate, *v8::String::Utf8Value(args[0]->ToString())); + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> V8Scope::stopCpuProfiler(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (args.Length() != 1 || !args[0]->IsString()) { + return v8AssertionException("stopCpuProfiler takes a string argument"); + } + scope->_cpuProfiler.stop(scope->_isolate, *v8::String::Utf8Value(args[0]->ToString())); + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> V8Scope::getCpuProfile(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (args.Length() != 1 || !args[0]->IsString()) { + return v8AssertionException("getCpuProfile takes a string argument"); + } + return scope->mongoToLZV8(scope->_cpuProfiler.fetch( + *v8::String::Utf8Value(args[0]->ToString()))); + } + + MONGO_INITIALIZER(JavascriptPrintDomain)(InitializerContext*) { + jsPrintLogDomain = logger::globalLogManager()->getNamedDomain("javascriptOutput"); + return Status::OK(); + } + +} // namespace mongo diff --git a/src/mongo/scripting/engine_v8-3.25.h b/src/mongo/scripting/engine_v8-3.25.h new file mode 100644 index 00000000000..01296c53cc0 --- /dev/null +++ b/src/mongo/scripting/engine_v8-3.25.h @@ -0,0 +1,615 @@ +//engine_v8.h + +/* Copyright 2014 MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <v8.h> + +#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-3.25_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(_isolate); /* make the current scope own local */ \ + /* handles */ \ + v8::Context::Scope context_scope(getContext()); /* enter the context; exit when */ \ + /* out of scope */ + +namespace mongo { + + class V8ScriptEngine; + class V8Scope; + class BSONHolder; + + typedef v8::Local<v8::Value> (*v8Function)(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& 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. + * @param instanceHandle persistent handle to the weakly referenced object + * @param rawData pointer to the object instance + */ + void track(v8::Isolate* isolate, v8::Local<v8::Value> instanceHandle, _ObjType* instance) { + TrackedPtr* collectionHandle = new TrackedPtr(isolate, instanceHandle, instance, this); + _container.insert(collectionHandle); + collectionHandle->_instanceHandle.SetWeak(collectionHandle, deleteOnCollect); + } + /** + * Free any remaining objects and their TrackedPtrs. Invoked when the + * V8Scope is destructed. + */ + ~ObjTracker() { + if (!_container.empty()) { + LOG(1) << "freeing " << _container.size() << " uncollected " + << typeid(_ObjType).name() << " objects" << endl; + } + typename 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(v8::Isolate* isolate, v8::Local<v8::Value>& instanceHandle, + _ObjType* instance, ObjTracker<_ObjType>* tracker) : + _instanceHandle(isolate, instanceHandle), + _objPtr(instance), + _tracker(tracker) { } + v8::Persistent<v8::Value> _instanceHandle; + scoped_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 data Weak callback data. Contains pointer to the TrackedPtr instance. + */ + static void deleteOnCollect(const v8::WeakCallbackData<v8::Value, TrackedPtr>& data) { + boost::scoped_ptr<TrackedPtr> trackedPtr(data.GetParameter()); + invariant(trackedPtr.get()); + + trackedPtr->_tracker->_container.erase(trackedPtr.get()); + trackedPtr->_instanceHandle.Reset(); + } + + // container for all TrackedPtrs created by this ObjTracker instance + set<TrackedPtr*> _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; + + /** + * Connect to a local database, create a Mongo object instance, and load any + * server-side js into the global object + */ + virtual void localConnect(const char* dbName); + + virtual void externalSetup(); + + virtual void installDBAccess(); + + virtual void installBSONTypes(); + + virtual 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::Local<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 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, const 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(const StringData& code, const 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::Local<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::Local<v8::FunctionTemplate> injectV8Function(const char* name, v8Function func); + v8::Local<v8::FunctionTemplate> injectV8Function(const char* name, + v8Function func, + v8::Local<v8::Object> obj); + v8::Local<v8::FunctionTemplate> injectV8Function(const char* name, + v8::Local<v8::FunctionTemplate> ft, + v8::Local<v8::Object> obj); + + // Injects a method into the provided prototype + v8::Local<v8::FunctionTemplate> injectV8Method(const char* name, + v8Function func, + v8::Local<v8::ObjectTemplate>& proto); + v8::Local<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::Local<v8::Object> mongoToLZV8(const mongo::BSONObj& m, bool readOnly = false); + v8::Local<v8::Value> mongoToV8Element(const BSONElement& f, bool readOnly = false); + + /** + * Convert v8 Javascript types to BSON types + */ + mongo::BSONObj v8ToMongo(v8::Local<v8::Object> obj, int depth = 0); + void v8ToMongoElement(BSONObjBuilder& b, + const StringData& sname, + v8::Local<v8::Value> value, + int depth = 0, + BSONObj* originalParent = 0); + void v8ToMongoObject(BSONObjBuilder& b, + const StringData& sname, + v8::Local<v8::Value> value, + int depth, + BSONObj* originalParent); + void v8ToMongoNumber(BSONObjBuilder& b, + const StringData& elementName, + v8::Local<v8::Number> value, + BSONObj* originalParent); + void v8ToMongoRegex(BSONObjBuilder& b, + const StringData& elementName, + v8::Local<v8::RegExp> v8Regex); + void v8ToMongoDBRef(BSONObjBuilder& b, + const StringData& elementName, + v8::Local<v8::Object> obj); + void v8ToMongoBinData(BSONObjBuilder& b, + const StringData& elementName, + v8::Local<v8::Object> obj); + OID v8ToMongoObjectID(v8::Local<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 string with a local handle + */ + inline v8::Local<v8::String> v8StringData(const StringData& str) { + return v8::String::NewFromUtf8(_isolate, str.rawData(), v8::String::kNormalString, + 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() const { return _isolate; } + + /** + * Get the JS context this scope executes within. + */ + v8::Local<v8::Context> getContext() { return _context.Get(_isolate); } + + /** + * Get the global JS object + */ + v8::Local<v8::Object> getGlobal() { return _global.Get(_isolate); } + + ObjTracker<BSONHolder> bsonHolderTracker; + ObjTracker<DBClientWithCommands> dbClientWithCommandsTracker; + ObjTracker<DBClientBase> dbClientBaseTracker; + ObjTracker<DBClientCursor> dbClientCursorTracker; + + // These are all named after the JS constructor name + FT + v8::Local<v8::FunctionTemplate> ObjectIdFT() { return _ObjectIdFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> DBRefFT() { return _DBRefFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> DBPointerFT() { return _DBPointerFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> BinDataFT() { return _BinDataFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> NumberLongFT() { return _NumberLongFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> NumberIntFT() { return _NumberIntFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> TimestampFT() { return _TimestampFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> MinKeyFT() { return _MinKeyFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> MaxKeyFT() { return _MaxKeyFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> MongoFT() { return _MongoFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> DBFT() { return _DBFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> DBCollectionFT() { return _DBCollectionFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> DBQueryFT() { return _DBQueryFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> InternalCursorFT() { + return _InternalCursorFT.Get(_isolate); + } + v8::Local<v8::FunctionTemplate> LazyBsonFT() { return _LazyBsonFT.Get(_isolate); } + v8::Local<v8::FunctionTemplate> ROBsonFT() { return _ROBsonFT.Get(_isolate); } + + template <size_t N> + v8::Local<v8::String> strLitToV8(const char (&str)[N]) { + // Note that _strLitMap is keyed on string pointer not string + // value. This is OK because each string literal has a constant + // pointer for the program's lifetime. This works best if (but does + // not require) the linker interns all 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.Get(_isolate); + + StringData sd (str, StringData::LiteralTag()); + v8::Local<v8::String> v8Str = v8StringData(sd); + + // Eternal should last as long as V8Scope exists. + _strLitMap[str].Set(_isolate, 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::Local<v8::Object> obj, BSONObj data, bool readOnly); + + /** + * Trampoline to call a c++ function with a specific signature (V8Scope*, + * v8::FunctionCallbackInfo<v8::Value>&). + * Handles interruption, exceptions, etc. + */ + static void v8Callback(const v8::FunctionCallbackInfo<v8::Value>& args); + + /** + * Interpreter agnostic 'Native Callback' trampoline. Note this is only called + * from v8Callback(). + */ + static v8::Local<v8::Value> nativeCallback(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + /** + * v8-specific implementations of basic global functions + */ + static v8::Local<v8::Value> load(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + static v8::Local<v8::Value> Print(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + static v8::Local<v8::Value> Version(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + static v8::Local<v8::Value> GCV8(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + static v8::Local<v8::Value> startCpuProfiler(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + static v8::Local<v8::Value> stopCpuProfiler(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + static v8::Local<v8::Value> getCpuProfile(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& 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(); + + /** + * 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. + */ + void registerOpId(); + + /** + * Unregister this scope with the mongo op id. + */ + void unregisterOpId(); + + /** + * Create a new function; primarily used for BSON/V8 conversion. + */ + v8::Local<v8::Value> newFunction(const StringData& code); + + template <typename _HandleType> + bool checkV8ErrorState(const _HandleType& resultHandle, + const v8::TryCatch& try_catch, + bool reportError = true, + bool assertOnError = true); + + V8ScriptEngine* _engine; + + v8::Eternal<v8::Context> _context; + v8::Eternal<v8::Object> _global; + string _error; + std::vector<v8::Eternal<v8::Value> > _funcs; + + enum ConnectState { NOT, LOCAL, EXTERNAL }; + ConnectState _connectState; + + // These are all named after the JS constructor name + FT + v8::Eternal<v8::FunctionTemplate> _ObjectIdFT; + v8::Eternal<v8::FunctionTemplate> _DBRefFT; + v8::Eternal<v8::FunctionTemplate> _DBPointerFT; + v8::Eternal<v8::FunctionTemplate> _BinDataFT; + v8::Eternal<v8::FunctionTemplate> _NumberLongFT; + v8::Eternal<v8::FunctionTemplate> _NumberIntFT; + v8::Eternal<v8::FunctionTemplate> _TimestampFT; + v8::Eternal<v8::FunctionTemplate> _MinKeyFT; + v8::Eternal<v8::FunctionTemplate> _MaxKeyFT; + v8::Eternal<v8::FunctionTemplate> _MongoFT; + v8::Eternal<v8::FunctionTemplate> _DBFT; + v8::Eternal<v8::FunctionTemplate> _DBCollectionFT; + v8::Eternal<v8::FunctionTemplate> _DBQueryFT; + v8::Eternal<v8::FunctionTemplate> _InternalCursorFT; + v8::Eternal<v8::FunctionTemplate> _LazyBsonFT; + v8::Eternal<v8::FunctionTemplate> _ROBsonFT; + + v8::Eternal<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; + } + } + + 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<const char*, v8::Eternal<v8::String> > StrLitMap; + StrLitMap _strLitMap; + + mongo::mutex _interruptLock; // protects interruption-related flags + bool _inNativeExecution; // protected by _interruptLock + bool _pendingKill; // protected by _interruptLock + int _opId; // op id for this scope + }; + + /// Helper to extract V8Scope for an Isolate + inline V8Scope* getScope(v8::Isolate* isolate) { + invariant(isolate); + invariant(isolate->GetNumberOfDataSlots() >= 1U); + uint32_t slot = 0; + return static_cast<V8Scope*>(isolate->GetData(slot)); + } + + 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 map<unsigned, V8Scope*> OpIdToScopeMap; + mongo::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: + BSONHolder(V8Scope* scope, BSONObj obj, bool readOnly) : + _scope(scope), + _obj(obj.getOwned()), + _modified(false), + _readOnly(readOnly) { + invariant(scope); + if (_scope->getIsolate()) { + // give hint v8's GC + _scope->getIsolate()->AdjustAmountOfExternalAllocatedMemory(_obj.objsize()); + } + } + ~BSONHolder() { + if (_scope->getIsolate()) { + // if v8 is still up, send hint to GC + _scope->getIsolate()->AdjustAmountOfExternalAllocatedMemory(-_obj.objsize()); + } + } + const V8Scope* _scope; + const BSONObj _obj; + bool _modified; + const bool _readOnly; + set<string> _removed; + }; + + /** + * Check for an error condition (e.g. empty handle, JS exception, OOM) after executing + * a v8 operation. + * @resultHandle handle storing the result of the preceeding v8 operation + * @try_catch the active v8::TryCatch exception handler + * @param reportError if true, log an error message + * @param assertOnError if true, throw an exception if an error is detected + * if false, return value indicates error state + * @return true if an error was detected and assertOnError is set to false + * false if no error was detected + */ + template <typename _HandleType> + bool V8Scope::checkV8ErrorState(const _HandleType& resultHandle, + const v8::TryCatch& try_catch, + bool reportError, + bool assertOnError) { + bool haveError = false; + + if (try_catch.HasCaught() && try_catch.CanContinue()) { + // normal JS exception + _error = v8ExceptionToSTLString(&try_catch); + haveError = true; + } + else if (hasOutOfMemoryException()) { + // out of memory exception (treated as terminal) + _error = "JavaScript execution failed -- v8 is out of memory"; + haveError = true; + } + else if (resultHandle.IsEmpty() || try_catch.HasCaught()) { + // terminal exception (due to empty handle, termination, etc.) + _error = "JavaScript execution failed"; + haveError = true; + } + + if (haveError) { + if (reportError) + log() << _error << endl; + if (assertOnError) + uasserted(16722, _error); + return true; + } + + return false; + } + + extern ScriptEngine* globalScriptEngine; + +} diff --git a/src/mongo/scripting/v8-3.25_db.cpp b/src/mongo/scripting/v8-3.25_db.cpp new file mode 100644 index 00000000000..9619e25d2f0 --- /dev/null +++ b/src/mongo/scripting/v8-3.25_db.cpp @@ -0,0 +1,1117 @@ +// v8_db.cpp + +/* Copyright 2014 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/scripting/v8-3.25_db.h" + +#include <iostream> +#include <iomanip> +#include <boost/scoped_array.hpp> + +#include "mongo/base/init.h" +#include "mongo/client/sasl_client_authenticate.h" +#include "mongo/client/syncclusterconnection.h" +#include "mongo/db/namespace_string.h" +#include "mongo/s/d_logic.h" +#include "mongo/scripting/engine_v8-3.25.h" +#include "mongo/scripting/v8-3.25_utils.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/base64.h" +#include "mongo/util/text.h" + +using namespace std; + +namespace mongo { + + namespace { + std::vector<V8FunctionPrototypeManipulatorFn> _mongoPrototypeManipulators; + bool _mongoPrototypeManipulatorsFrozen = false; + + MONGO_INITIALIZER(V8MongoPrototypeManipulatorRegistry)(InitializerContext* context) { + return Status::OK(); + } + + MONGO_INITIALIZER_WITH_PREREQUISITES(V8MongoPrototypeManipulatorRegistrationDone, + ("V8MongoPrototypeManipulatorRegistry")) + (InitializerContext* context) { + + _mongoPrototypeManipulatorsFrozen = true; + return Status::OK(); + } + + } // namespace + + void v8RegisterMongoPrototypeManipulator(const V8FunctionPrototypeManipulatorFn& manipulator) { + fassert(16467, !_mongoPrototypeManipulatorsFrozen); + _mongoPrototypeManipulators.push_back(manipulator); + } + + static v8::Local<v8::Value> newInstance(v8::Local<v8::Function> f, + const v8::FunctionCallbackInfo<v8::Value>& args) { + // need to translate arguments into an array + v8::EscapableHandleScope handle_scope(args.GetIsolate()); + const int argc = args.Length(); + static const int MAX_ARGC = 24; + uassert(16858, "Too many arguments. Max is 24", + argc <= MAX_ARGC); + + // TODO SERVER-8016: properly allocate handles on the stack + v8::Local<v8::Value> argv[MAX_ARGC]; + for (int i = 0; i < argc; ++i) { + argv[i] = args[i]; + } + return handle_scope.Escape(f->NewInstance(argc, argv)); + } + + v8::Local<v8::FunctionTemplate> getInternalCursorFunctionTemplate(V8Scope* scope) { + v8::Local<v8::FunctionTemplate> ic = scope->createV8Function(internalCursorCons); + ic->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local<v8::ObjectTemplate> icproto = ic->PrototypeTemplate(); + scope->injectV8Method("next", internalCursorNext, icproto); + scope->injectV8Method("hasNext", internalCursorHasNext, icproto); + scope->injectV8Method("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto); + scope->injectV8Method("readOnly", internalCursorReadOnly, icproto); + return ic; + } + + v8::Local<v8::FunctionTemplate> getMongoFunctionTemplate(V8Scope* scope, bool local) { + v8::Local<v8::FunctionTemplate> mongo; + if (local) + mongo = scope->createV8Function(mongoConsLocal); + else + mongo = scope->createV8Function(mongoConsExternal); + mongo->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local<v8::ObjectTemplate> proto = mongo->PrototypeTemplate(); + scope->injectV8Method("find", mongoFind, proto); + scope->injectV8Method("insert", mongoInsert, proto); + scope->injectV8Method("remove", mongoRemove, proto); + scope->injectV8Method("update", mongoUpdate, proto); + scope->injectV8Method("auth", mongoAuth, proto); + scope->injectV8Method("logout", mongoLogout, proto); + scope->injectV8Method("cursorFromId", mongoCursorFromId, proto); + + fassert(16468, _mongoPrototypeManipulatorsFrozen); + for (size_t i = 0; i < _mongoPrototypeManipulators.size(); ++i) + _mongoPrototypeManipulators[i](scope, mongo); + + return mongo; + } + + + v8::Local<v8::Value> mongoConsExternal(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + char host[255]; + if (args.Length() > 0 && args[0]->IsString()) { + uassert(16666, "string argument too long", args[0]->ToString()->Utf8Length() < 250); + args[0]->ToString()->WriteUtf8(host); + } + else { + strcpy(host, "127.0.0.1"); + } + + // only allow function template to be used by a constructor + uassert(16859, "Mongo function is only usable as a constructor", + args.IsConstructCall()); + verify(scope->MongoFT()->HasInstance(args.This())); + + string errmsg; + ConnectionString cs = ConnectionString::parse(host, errmsg); + if (!cs.isValid()) { + return v8AssertionException(errmsg); + } + + DBClientWithCommands* conn; + conn = cs.connect(errmsg); + if (!conn) { + return v8AssertionException(errmsg); + } + + scope->dbClientWithCommandsTracker.track(scope->getIsolate(), args.This(), conn); + + ScriptEngine::runConnectCallback(*conn); + + args.This()->SetInternalField(0, v8::External::New(scope->getIsolate(), conn)); + args.This()->ForceSet(scope->v8StringData("slaveOk"), + v8::Boolean::New(scope->getIsolate(), false)); + args.This()->ForceSet(scope->v8StringData("host"), scope->v8StringData(host)); + + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> mongoConsLocal(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 0, "local Mongo constructor takes no args") + + // only allow function template to be used by a constructor + uassert(16860, "Mongo function is only usable as a constructor", + args.IsConstructCall()); + verify(scope->MongoFT()->HasInstance(args.This())); + + DBClientBase* conn = createDirectClient(); + scope->dbClientBaseTracker.track(scope->getIsolate(), args.This(), conn); + + args.This()->SetInternalField(0, v8::External::New(scope->getIsolate(), conn)); + args.This()->ForceSet(scope->v8StringData("slaveOk"), + v8::Boolean::New(scope->getIsolate(), false)); + args.This()->ForceSet(scope->v8StringData("host"), scope->v8StringData("EMBEDDED")); + + return v8::Undefined(scope->getIsolate()); + } + + DBClientBase* getConnection(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + verify(scope->MongoFT()->HasInstance(args.This())); + verify(args.This()->InternalFieldCount() == 1); + v8::Local<v8::External> c = + v8::Local<v8::External>::Cast(args.This()->GetInternalField(0)); + DBClientBase* conn = (DBClientBase*)(c->Value()); + massert(16667, "Unable to get db client connection", conn); + return conn; + } + + /** + * JavaScript binding for Mongo.prototype.find(namespace, query, fields, limit, skip) + */ + v8::Local<v8::Value> mongoFind(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 7, "find needs 7 args") + argumentCheck(args[1]->IsObject(), "needs to be an object") + DBClientBase * conn = getConnection(scope, args); + const string ns = toSTLString(args[0]); + BSONObj fields; + BSONObj q = scope->v8ToMongo(args[1]->ToObject()); + bool haveFields = args[2]->IsObject() && + args[2]->ToObject()->GetPropertyNames()->Length() > 0; + if (haveFields) + fields = scope->v8ToMongo(args[2]->ToObject()); + + auto_ptr<mongo::DBClientCursor> cursor; + int nToReturn = args[3]->Int32Value(); + int nToSkip = args[4]->Int32Value(); + int batchSize = args[5]->Int32Value(); + int options = args[6]->Int32Value(); + cursor = conn->query(ns, q, nToReturn, nToSkip, haveFields ? &fields : NULL, + options, batchSize); + if (!cursor.get()) { + return v8AssertionException("error doing query: failed"); + } + + v8::Local<v8::Function> cons = scope->InternalCursorFT()->GetFunction(); + v8::Local<v8::Object> c = v8::Local<v8::Object>::Cast(cons->NewInstance()); + c->SetInternalField(0, v8::External::New(scope->getIsolate(), cursor.get())); + scope->dbClientCursorTracker.track(scope->getIsolate(), c, cursor.release()); + return c; + } + + v8::Local<v8::Value> mongoCursorFromId(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 2 || args.Length() == 3, "cursorFromId needs 2 or 3 args") + argumentCheck(scope->NumberLongFT()->HasInstance(args[1]), "2nd arg must be a NumberLong") + argumentCheck(args[2]->IsUndefined() || args[2]->IsNumber(), "3rd arg must be a js Number") + + DBClientBase* conn = getConnection(scope, args); + const string ns = toSTLString(args[0]); + long long cursorId = numberLongVal(scope, args[1]->ToObject()); + + auto_ptr<mongo::DBClientCursor> cursor(new DBClientCursor(conn, ns, cursorId, 0, 0)); + + if (!args[2]->IsUndefined()) + cursor->setBatchSize(args[2]->Int32Value()); + + v8::Local<v8::Function> cons = scope->InternalCursorFT()->GetFunction(); + v8::Local<v8::Object> c = v8::Local<v8::Object>::Cast(cons->NewInstance()); + c->SetInternalField(0, v8::External::New(scope->getIsolate(), cursor.get())); + scope->dbClientCursorTracker.track(scope->getIsolate(), c, cursor.release()); + return c; + } + + v8::Local<v8::Value> mongoInsert(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 3 ,"insert needs 3 args") + argumentCheck(args[1]->IsObject() ,"attempted to insert a non-object") + + verify(scope->MongoFT()->HasInstance(args.This())); + + if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) { + return v8AssertionException("js db in read only mode"); + } + + DBClientBase * conn = getConnection(scope, args); + const string ns = toSTLString(args[0]); + + v8::Local<v8::Integer> flags = args[2]->ToInteger(); + + if(args[1]->IsArray()){ + v8::Local<v8::Array> arr = v8::Local<v8::Array>::Cast(args[1]); + vector<BSONObj> bos; + uint32_t len = arr->Length(); + argumentCheck(len > 0, "attempted to insert an empty array") + + for(uint32_t i = 0; i < len; i++){ + v8::Local<v8::Object> el = arr->CloneElementAt(i); + argumentCheck(!el.IsEmpty(), "attempted to insert an array of non-object types") + + // Set ID on the element if necessary + if (!el->Has(scope->v8StringData("_id"))) { + v8::Local<v8::Value> argv[1]; + el->ForceSet(scope->v8StringData("_id"), + scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv)); + } + bos.push_back(scope->v8ToMongo(el)); + } + conn->insert(ns, bos, flags->Int32Value()); + } + else { + v8::Local<v8::Object> in = args[1]->ToObject(); + if (!in->Has(scope->v8StringData("_id"))) { + v8::Local<v8::Value> argv[1]; + in->ForceSet(scope->v8StringData("_id"), + scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv)); + } + BSONObj o = scope->v8ToMongo(in); + conn->insert(ns, o); + } + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> mongoRemove(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 2 || args.Length() == 3, "remove needs 2 or 3 args") + argumentCheck(args[1]->IsObject(), "attempted to remove a non-object") + + verify(scope->MongoFT()->HasInstance(args.This())); + + if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) { + return v8AssertionException("js db in read only mode"); + } + + DBClientBase * conn = getConnection(scope, args); + const string ns = toSTLString(args[0]); + + v8::Local<v8::Object> in = args[1]->ToObject(); + BSONObj o = scope->v8ToMongo(in); + + bool justOne = false; + if (args.Length() > 2) { + justOne = args[2]->BooleanValue(); + } + + conn->remove(ns, o, justOne); + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> mongoUpdate(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() >= 3, "update needs at least 3 args") + argumentCheck(args[1]->IsObject(), "1st param to update has to be an object") + argumentCheck(args[2]->IsObject(), "2nd param to update has to be an object") + + verify(scope->MongoFT()->HasInstance(args.This())); + + if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) { + return v8AssertionException("js db in read only mode"); + } + + DBClientBase * conn = getConnection(scope, args); + const string ns = toSTLString(args[0]); + + v8::Local<v8::Object> q = args[1]->ToObject(); + v8::Local<v8::Object> o = args[2]->ToObject(); + + bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value(); + bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value(); + + BSONObj q1 = scope->v8ToMongo(q); + BSONObj o1 = scope->v8ToMongo(o); + conn->update(ns, q1, o1, upsert, multi); + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> mongoAuth(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + DBClientWithCommands* conn = getConnection(scope, args); + if (NULL == conn) + return v8AssertionException("no connection"); + + BSONObj params; + switch (args.Length()) { + case 1: + params = scope->v8ToMongo(args[0]->ToObject()); + break; + case 3: + params = BSON(saslCommandMechanismFieldName << "MONGODB-CR" << + saslCommandUserDBFieldName << toSTLString(args[0]) << + saslCommandUserFieldName << toSTLString(args[1]) << + saslCommandPasswordFieldName << toSTLString(args[2])); + break; + default: + return v8AssertionException("mongoAuth takes 1 object or 3 string arguments"); + } + try { + conn->auth(params); + } + catch (const DBException& ex) { + return v8AssertionException(ex.toString()); + } + return v8::Boolean::New(scope->getIsolate(), true); + } + + v8::Local<v8::Value> mongoLogout(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 1, "logout needs 1 arg") + DBClientBase* conn = getConnection(scope, args); + const string db = toSTLString(args[0]); + BSONObj ret; + conn->logout(db, ret); + return scope->mongoToLZV8(ret, false); + } + + /** + * get cursor from v8 argument + */ + mongo::DBClientCursor* getCursor(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + verify(scope->InternalCursorFT()->HasInstance(args.This())); + verify(args.This()->InternalFieldCount() == 1); + v8::Local<v8::External> c = + v8::Local<v8::External>::Cast(args.This()->GetInternalField(0)); + mongo::DBClientCursor* cursor = static_cast<mongo::DBClientCursor*>(c->Value()); + return cursor; + } + + v8::Local<v8::Value> internalCursorCons(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + return v8::Undefined(scope->getIsolate()); + } + + /** + * cursor.next() + */ + v8::Local<v8::Value> internalCursorNext(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + mongo::DBClientCursor* cursor = getCursor(scope, args); + if (! cursor) + return v8::Undefined(scope->getIsolate()); + BSONObj o = cursor->next(); + bool ro = false; + if (args.This()->Has(v8::String::NewFromUtf8(scope->getIsolate(), "_ro"))) + ro = args.This()->Get(v8::String::NewFromUtf8(scope->getIsolate(), + "_ro"))->BooleanValue(); + return scope->mongoToLZV8(o, ro); + } + + /** + * cursor.hasNext() + */ + v8::Local<v8::Value> internalCursorHasNext(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + mongo::DBClientCursor* cursor = getCursor(scope, args); + if (! cursor) + return v8::Boolean::New(scope->getIsolate(), false); + return v8::Boolean::New(scope->getIsolate(), cursor->more()); + } + + /** + * cursor.objsLeftInBatch() + */ + v8::Local<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + mongo::DBClientCursor* cursor = getCursor(scope, args); + if (! cursor) + return v8::Number::New(scope->getIsolate(), 0.0); + return v8::Number::New(scope->getIsolate(), + static_cast<double>(cursor->objsLeftInBatch())); + } + + /** + * cursor.readOnly() + */ + v8::Local<v8::Value> internalCursorReadOnly(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + verify(scope->InternalCursorFT()->HasInstance(args.This())); + + v8::Local<v8::Object> cursor = args.This(); + cursor->ForceSet(v8::String::NewFromUtf8(scope->getIsolate(), "_ro"), + v8::Boolean::New(scope->getIsolate(), true)); + return cursor; + } + + v8::Local<v8::Value> dbInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->DBFT()->GetFunction(); + return newInstance(f, args); + } + + verify(scope->DBFT()->HasInstance(args.This())); + + argumentCheck(args.Length() == 2, "db constructor requires 2 arguments") + + args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]); + args.This()->ForceSet(scope->v8StringData("_name"), args[1]); + + for (int i = 0; i < args.Length(); i++) { + argumentCheck(!args[i]->IsUndefined(), "db initializer called with undefined argument") + } + + string dbName = toSTLString(args[1]); + if (!NamespaceString::validDBName(dbName)) { + return v8AssertionException(str::stream() << "[" << dbName + << "] is not a valid database name"); + } + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> collectionInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->DBCollectionFT()->GetFunction(); + return newInstance(f, args); + } + + verify(scope->DBCollectionFT()->HasInstance(args.This())); + + argumentCheck(args.Length() == 4, "collection constructor requires 4 arguments") + + for (int i = 0; i < args.Length(); i++) { + argumentCheck(!args[i]->IsUndefined(), + "collection constructor called with undefined argument") + } + + args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]); + args.This()->ForceSet(scope->v8StringData("_db"), args[1]); + args.This()->ForceSet(scope->v8StringData("_shortName"), args[2]); + args.This()->ForceSet(v8::String::NewFromUtf8(scope->getIsolate(), "_fullName"), args[3]); + + if (haveLocalShardingInfo(toSTLString(args[3]))) { + return v8AssertionException("can't use sharded collection from db.eval"); + } + + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> dbQueryInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->DBQueryFT()->GetFunction(); + return newInstance(f, args); + } + + verify(scope->DBQueryFT()->HasInstance(args.This())); + + argumentCheck(args.Length() >= 4, "dbQuery constructor requires at least 4 arguments") + + v8::Local<v8::Object> t = args.This(); + t->ForceSet(scope->v8StringData("_mongo"), args[0]); + t->ForceSet(scope->v8StringData("_db"), args[1]); + t->ForceSet(scope->v8StringData("_collection"), args[2]); + t->ForceSet(scope->v8StringData("_ns"), args[3]); + + if (args.Length() > 4 && args[4]->IsObject()) + t->ForceSet(scope->v8StringData("_query"), args[4]); + else + t->ForceSet(scope->v8StringData("_query"), v8::Object::New(scope->getIsolate())); + + if (args.Length() > 5 && args[5]->IsObject()) + t->ForceSet(scope->v8StringData("_fields"), args[5]); + else + t->ForceSet(scope->v8StringData("_fields"), v8::Null(scope->getIsolate())); + + if (args.Length() > 6 && args[6]->IsNumber()) + t->ForceSet(scope->v8StringData("_limit"), args[6]); + else + t->ForceSet(scope->v8StringData("_limit"), v8::Number::New(scope->getIsolate(), 0)); + + if (args.Length() > 7 && args[7]->IsNumber()) + t->ForceSet(scope->v8StringData("_skip"), args[7]); + else + t->ForceSet(scope->v8StringData("_skip"), v8::Number::New(scope->getIsolate(), 0)); + + if (args.Length() > 8 && args[8]->IsNumber()) + t->ForceSet(scope->v8StringData("_batchSize"), args[8]); + else + t->ForceSet(scope->v8StringData("_batchSize"), v8::Number::New(scope->getIsolate(), + 0)); + + if (args.Length() > 9 && args[9]->IsNumber()) + t->ForceSet(scope->v8StringData("_options"), args[9]); + else + t->ForceSet(scope->v8StringData("_options"), v8::Number::New(scope->getIsolate(), 0)); + + t->ForceSet(scope->v8StringData("_cursor"), v8::Null(scope->getIsolate())); + t->ForceSet(scope->v8StringData("_numReturned"), v8::Number::New(scope->getIsolate(), 0)); + t->ForceSet(scope->v8StringData("_special"), v8::Boolean::New(scope->getIsolate(), false)); + + return v8::Undefined(scope->getIsolate()); + } + + void collectionSetter(v8::Local<v8::String> name, + v8::Local<v8::Value> value, + const v8::PropertyCallbackInfo<v8::Value>& info) { + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = info.GetReturnValue(); + result.Set(val); + try { + V8Scope* scope = getScope(info.GetIsolate()); + + // Both DB and Collection objects use this setter + verify(scope->DBCollectionFT()->HasInstance(info.This()) + || scope->DBFT()->HasInstance(info.This())); + + // a collection name cannot be overwritten by a variable + string sname = toSTLString(name); + if (sname.length() == 0 || sname[0] == '_') { + // if starts with '_' we allow overwrite + return; + } + // dont set + result.Set(value); + } + catch (const DBException& dbEx) { + result.Set(v8AssertionException(dbEx.toString())); + } + catch (...) { + result.Set(v8AssertionException("unknown error in collationSetter")); + } + } + + void collectionGetter(v8::Local<v8::String> name, + const v8::PropertyCallbackInfo<v8::Value>& info) { + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = info.GetReturnValue(); + result.Set(val); + try { + V8Scope* scope = getScope(info.GetIsolate()); + + // Both DB and Collection objects use this getter + verify(scope->DBCollectionFT()->HasInstance(info.This()) + || scope->DBFT()->HasInstance(info.This())); + + v8::TryCatch tryCatch; + + // first look in prototype, may be a function + v8::Local<v8::Value> real = info.This()->GetPrototype()->ToObject()->Get(name); + if (!real->IsUndefined()) { + result.Set(real); + return; + } + + // 2nd look into real values, may be cached collection object + string sname = toSTLString(name); + if (info.This()->HasRealNamedProperty(name)) { + v8::Local<v8::Value> prop = info.This()->GetRealNamedProperty(name); + if (prop->IsObject() && + prop->ToObject()->HasRealNamedProperty( + v8::String::NewFromUtf8(scope->getIsolate(), "_fullName"))) { + // need to check every time that the collection did not get sharded + if (haveLocalShardingInfo(toSTLString( + prop->ToObject()->GetRealNamedProperty( + v8::String::NewFromUtf8(scope->getIsolate(), "_fullName"))))) { + result.Set( + v8AssertionException("can't use sharded collection from db.eval")); + return; + } + } + result.Set(prop); + 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 + v8::Local<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get( + v8::String::NewFromUtf8(scope->getIsolate(), "getCollection")); + if (! getCollection->IsFunction()) { + result.Set(v8AssertionException("getCollection is not a function")); + return; + } + + v8::Local<v8::Function> f = getCollection.As<v8::Function>(); + v8::Local<v8::Value> argv[1]; + argv[0] = name; + v8::Local<v8::Value> coll = f->Call(info.This(), 1, argv); + if (coll.IsEmpty()) { + result.Set(tryCatch.ReThrow()); + return; + } + + uassert(16861, "getCollection returned something other than a collection", + scope->DBCollectionFT()->HasInstance(coll)); + + // cache collection for reuse, don't enumerate + info.This()->ForceSet(name, coll, v8::DontEnum); + result.Set(coll); + } + catch (const DBException& dbEx) { + result.Set(v8AssertionException(dbEx.toString())); + } + catch (...) { + result.Set(v8AssertionException("unknown error in collectionGetter")); + } + } + + void dbQueryIndexAccess(unsigned int index, const v8::PropertyCallbackInfo<v8::Value>& info) { + v8::Local<v8::Value> val; + v8::ReturnValue<v8::Value> result = info.GetReturnValue(); + result.Set(val); + try { + V8Scope* scope = getScope(info.GetIsolate()); + verify(scope->DBQueryFT()->HasInstance(info.This())); + + v8::Local<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get( + v8::String::NewFromUtf8(scope->getIsolate(), "arrayAccess")); + massert(16660, "arrayAccess is not a function", arrayAccess->IsFunction()); + + v8::Local<v8::Function> f = arrayAccess.As<v8::Function>(); + v8::Local<v8::Value> argv[1]; + argv[0] = v8::Number::New(scope->getIsolate(), index); + + result.Set(f->Call(info.This(), 1, argv)); + } + catch (const DBException& dbEx) { + result.Set(v8AssertionException(dbEx.toString())); + } + catch (...) { + result.Set(v8AssertionException("unknown error in dbQueryIndexAccess")); + } + } + + v8::Local<v8::Value> objectIdInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->ObjectIdFT()->GetFunction(); + return newInstance(f, args); + } + + v8::Local<v8::Object> it = args.This(); + verify(scope->ObjectIdFT()->HasInstance(it)); + + OID oid; + if (args.Length() == 0) { + oid.init(); + } + else { + string s = toSTLString(args[0]); + try { + Scope::validateObjectIdString(s); + } + catch (const MsgAssertionException& m) { + return v8AssertionException(m.toString()); + } + oid.init(s); + } + + it->ForceSet(scope->v8StringData("str"), + v8::String::NewFromUtf8(scope->getIsolate(), oid.str().c_str())); + return it; + } + + v8::Local<v8::Value> dbRefInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->DBRefFT()->GetFunction(); + return newInstance(f, args); + } + + v8::Local<v8::Object> it = args.This(); + verify(scope->DBRefFT()->HasInstance(it)); + + argumentCheck(args.Length() == 2, "DBRef needs 2 arguments") + argumentCheck(args[0]->IsString(), "DBRef 1st parameter must be a string") + it->ForceSet(scope->v8StringData("$ref"), args[0]); + it->ForceSet(scope->v8StringData("$id"), args[1]); + return it; + } + + v8::Local<v8::Value> dbPointerInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->DBPointerFT()->GetFunction(); + return newInstance(f, args); + } + + v8::Local<v8::Object> it = args.This(); + verify(scope->DBPointerFT()->HasInstance(it)); + + argumentCheck(args.Length() == 2, "DBPointer needs 2 arguments") + argumentCheck(args[0]->IsString(), "DBPointer 1st parameter must be a string") + argumentCheck(scope->ObjectIdFT()->HasInstance(args[1]), + "DBPointer 2nd parameter must be an ObjectId") + + it->ForceSet(scope->v8StringData("ns"), args[0]); + it->ForceSet(scope->v8StringData("id"), args[1]); + return it; + } + + v8::Local<v8::Value> dbTimestampInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->TimestampFT()->GetFunction(); + return newInstance(f, args); + } + + v8::Local<v8::Object> it = args.This(); + verify(scope->TimestampFT()->HasInstance(it)); + + if (args.Length() == 0) { + it->ForceSet(scope->v8StringData("t"), v8::Number::New(scope->getIsolate(), 0)); + it->ForceSet(scope->v8StringData("i"), v8::Number::New(scope->getIsolate(), 0)); + } + else if (args.Length() == 2) { + if (!args[0]->IsNumber()) { + return v8AssertionException("Timestamp time must be a number"); + } + if (!args[1]->IsNumber()) { + return v8AssertionException("Timestamp increment must be a number"); + } + int64_t t = args[0]->IntegerValue(); + int64_t largestVal = int64_t(OpTime::max().getSecs()); + if( t > largestVal ) + return v8AssertionException( str::stream() + << "The first argument must be in seconds; " + << t << " is too large (max " << largestVal << ")"); + it->ForceSet(scope->v8StringData("t"), args[0]); + it->ForceSet(scope->v8StringData("i"), args[1]); + } + else { + return v8AssertionException("Timestamp needs 0 or 2 arguments"); + } + + return it; + } + + v8::Local<v8::Value> binDataInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->BinDataFT()->GetFunction(); + return newInstance(f, args); + } + + v8::Local<v8::Object> it = args.This(); + verify(scope->BinDataFT()->HasInstance(it)); + + argumentCheck(args.Length() == 2, "BinData takes 2 arguments -- BinData(subtype,data)"); + + // 2 args: type, base64 string + v8::Local<v8::Value> type = args[0]; + if (!type->IsNumber() || type->Int32Value() < 0 || type->Int32Value() > 255) { + return v8AssertionException( + "BinData subtype must be a Number between 0 and 255 inclusive)"); + } + v8::String::Utf8Value utf(args[1]); + // uassert if invalid base64 string + string tmpBase64 = base64::decode(*utf); + // length property stores the decoded length + it->ForceSet(scope->v8StringData("len"), + v8::Number::New(scope->getIsolate(), tmpBase64.length())); + it->ForceSet(scope->v8StringData("type"), type); + it->SetInternalField(0, args[1]); + + return it; + } + + v8::Local<v8::Value> binDataToString(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Object> it = args.This(); + verify(scope->BinDataFT()->HasInstance(it)); + int type = it->Get(v8::String::NewFromUtf8(scope->getIsolate(), "type"))->Int32Value(); + + stringstream ss; + verify(it->InternalFieldCount() == 1); + ss << "BinData(" << type << ",\"" << toSTLString(it->GetInternalField(0)) << "\")"; + return v8::String::NewFromUtf8(scope->getIsolate(), ss.str().c_str()); + } + + v8::Local<v8::Value> binDataToBase64(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Object> it = args.This(); + verify(scope->BinDataFT()->HasInstance(it)); + verify(it->InternalFieldCount() == 1); + return it->GetInternalField(0); + } + + v8::Local<v8::Value> binDataToHex(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Object> it = args.This(); + verify(scope->BinDataFT()->HasInstance(it)); + int len = v8::Local<v8::Number>::Cast(it->Get( + v8::String::NewFromUtf8(scope->getIsolate(), "len")))->Int32Value(); + verify(it->InternalFieldCount() == 1); + string data = base64::decode(toSTLString(it->GetInternalField(0))); + stringstream ss; + ss.setf (ios_base::hex, ios_base::basefield); + ss.fill ('0'); + ss.setf (ios_base::right, ios_base::adjustfield); + for(int i = 0; i < len; i++) { + unsigned v = (unsigned char) data[i]; + ss << setw(2) << v; + } + return v8::String::NewFromUtf8(scope->getIsolate(), ss.str().c_str()); + } + + static v8::Local<v8::Value> hexToBinData(V8Scope* scope, int type, string hexstr) { + // 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.length() / 2; + scoped_array<char> data(new char[len]); + const char* src = hexstr.c_str(); + for(int i = 0; i < len; i++) { + data[i] = fromHex(src + i * 2); + } + + string encoded = base64::encode(data.get(), len); + v8::Local<v8::Value> argv[2]; + argv[0] = v8::Number::New(scope->getIsolate(), type); + argv[1] = v8::String::NewFromUtf8(scope->getIsolate(), encoded.c_str()); + return scope->BinDataFT()->GetFunction()->NewInstance(2, argv); + } + + v8::Local<v8::Value> uuidInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 1, "UUID needs 1 argument") + v8::String::Utf8Value utf(args[0]); + argumentCheck(utf.length() == 32, "UUID string must have 32 characters") + return hexToBinData(scope, bdtUUID, *utf); + } + + v8::Local<v8::Value> md5Init(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 1, "MD5 needs 1 argument") + v8::String::Utf8Value utf(args[0]); + argumentCheck(utf.length() == 32, "MD5 string must have 32 characters") + return hexToBinData(scope, MD5Type, *utf); + } + + v8::Local<v8::Value> hexDataInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 2, "HexData needs 2 arguments") + v8::Local<v8::Value> type = args[0]; + if (!type->IsNumber() || type->Int32Value() < 0 || type->Int32Value() > 255) { + return v8AssertionException( + "HexData subtype must be a Number between 0 and 255 inclusive"); + } + v8::String::Utf8Value utf(args[1]); + return hexToBinData(scope, type->Int32Value(), *utf); + } + + v8::Local<v8::Value> numberLongInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->NumberLongFT()->GetFunction(); + return newInstance(f, args); + } + + argumentCheck(args.Length() == 0 || args.Length() == 1 || args.Length() == 3, + "NumberLong needs 0, 1 or 3 arguments") + + v8::Local<v8::Object> it = args.This(); + verify(scope->NumberLongFT()->HasInstance(it)); + + if (args.Length() == 0) { + it->ForceSet(scope->v8StringData("floatApprox"), + v8::Number::New(scope->getIsolate(), 0)); + } + else if (args.Length() == 1) { + if (args[0]->IsNumber()) { + it->ForceSet(scope->v8StringData("floatApprox"), args[0]); + } + else { + v8::String::Utf8Value data(args[0]); + string num = *data; + const char *numStr = num.c_str(); + long long n; + try { + n = parseLL(numStr); + } + catch (const AssertionException&) { + return v8AssertionException(string("could not convert \"") + + num + + "\" to NumberLong"); + } + unsigned long long val = n; + // values above 2^53 are not accurately represented in JS + if ((long long)val == + (long long)(double)(long long)(val) && val < 9007199254740992ULL) { + it->ForceSet(scope->v8StringData("floatApprox"), + v8::Number::New(scope->getIsolate(), (double)(long long)(val))); + } + else { + it->ForceSet(scope->v8StringData("floatApprox"), + v8::Number::New(scope->getIsolate(), (double)(long long)(val))); + it->ForceSet(scope->v8StringData("top"), + v8::Integer::New(scope->getIsolate(), val >> 32)); + it->ForceSet(scope->v8StringData("bottom"), + v8::Integer::New(scope->getIsolate(), + (unsigned long)(val & 0x00000000ffffffff))); + } + } + } + else { + it->ForceSet(scope->v8StringData("floatApprox"), args[0]->ToNumber()); + it->ForceSet(scope->v8StringData("top"), args[1]->ToUint32()); + it->ForceSet(scope->v8StringData("bottom"), args[2]->ToUint32()); + } + return it; + } + + long long numberLongVal(V8Scope* scope, const v8::Local<v8::Object>& it) { + verify(scope->NumberLongFT()->HasInstance(it)); + if (!it->Has(v8::String::NewFromUtf8(scope->getIsolate(), "top"))) + return (long long)( + it->Get(v8::String::NewFromUtf8(scope->getIsolate(), + "floatApprox"))->NumberValue()); + return + (long long) + ((unsigned long long)(it->Get( + v8::String::NewFromUtf8(scope->getIsolate(),"top"))->ToInt32()->Value()) << 32) + + (unsigned)(it->Get( + v8::String::NewFromUtf8(scope->getIsolate(), "bottom"))->ToInt32()->Value()); + } + + v8::Local<v8::Value> numberLongValueOf(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Object> it = args.This(); + long long val = numberLongVal(scope, it); + return v8::Number::New(scope->getIsolate(), double(val)); + } + + v8::Local<v8::Value> numberLongToNumber(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + return numberLongValueOf(scope, args); + } + + v8::Local<v8::Value> numberLongToString(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Object> it = args.This(); + + stringstream ss; + long long val = numberLongVal(scope, it); + const long long limit = 2LL << 30; + + if (val <= -limit || limit <= val) + ss << "NumberLong(\"" << val << "\")"; + else + ss << "NumberLong(" << val << ")"; + + string ret = ss.str(); + return v8::String::NewFromUtf8(scope->getIsolate(), ret.c_str()); + } + + v8::Local<v8::Value> numberIntInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (!args.IsConstructCall()) { + v8::Local<v8::Function> f = scope->NumberIntFT()->GetFunction(); + return newInstance(f, args); + } + + v8::Local<v8::Object> it = args.This(); + verify(scope->NumberIntFT()->HasInstance(it)); + + argumentCheck(args.Length() == 0 || args.Length() == 1, "NumberInt needs 0 or 1 arguments") + if (args.Length() == 0) { + it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "__NumberInt"), + v8::Number::New(scope->getIsolate(), 0)); + } + else if (args.Length() == 1) { + it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "__NumberInt"), + args[0]->ToInt32()); + } + return it; + } + + int numberIntVal(V8Scope* scope, const v8::Local<v8::Object>& it) { + verify(scope->NumberIntFT()->HasInstance(it)); + v8::Local<v8::Value> value = + it->GetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "__NumberInt")); + verify(!value.IsEmpty()); + return value->Int32Value(); + } + + v8::Local<v8::Value> numberIntValueOf(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Object> it = args.This(); + return v8::Integer::New(scope->getIsolate(), numberIntVal(scope, it)); + } + + v8::Local<v8::Value> numberIntToNumber(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + return numberIntValueOf(scope, args); + } + + v8::Local<v8::Value> numberIntToString(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Object> it = args.This(); + int val = numberIntVal(scope, it); + string ret = str::stream() << "NumberInt(" << val << ")"; + return v8::String::NewFromUtf8(scope->getIsolate(), ret.c_str()); + } + + v8::Local<v8::Value> v8ObjectInvalidForStorage(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 1, "invalidForStorage needs 1 argument") + if (args[0]->IsNull()) { + return v8::Null(scope->getIsolate()); + } + argumentCheck(args[0]->IsObject(), "argument to invalidForStorage has to be an object") + Status validForStorage = scope->v8ToMongo(args[0]->ToObject()).storageValid(true); + if (validForStorage.isOK()) { + return v8::Null(scope->getIsolate()); + } + + std::string errmsg = str::stream() << validForStorage.codeString() + << ": "<< validForStorage.reason(); + return v8::String::NewFromUtf8(scope->getIsolate(), errmsg.c_str()); + } + + v8::Local<v8::Value> bsonsize(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 1, "bsonsize needs 1 argument") + if (args[0]->IsNull()) { + return v8::Number::New(scope->getIsolate(), 0); + } + argumentCheck(args[0]->IsObject(), "argument to bsonsize has to be an object") + return v8::Number::New(scope->getIsolate(), + scope->v8ToMongo(args[0]->ToObject()).objsize()); + } + + v8::Local<v8::Value> bsonWoCompare(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + argumentCheck(args.Length() == 2, "bsonWoCompare needs 2 argument"); + + argumentCheck(args[0]->IsObject(), "first argument to bsonWoCompare has to be an object"); + argumentCheck(args[1]->IsObject(), "second argument to bsonWoCompare has to be an object"); + + BSONObj firstObject(scope->v8ToMongo(args[0]->ToObject())); + BSONObj secondObject(scope->v8ToMongo(args[1]->ToObject())); + + return v8::Number::New(scope->getIsolate(), firstObject.woCompare(secondObject)); + } + +} diff --git a/src/mongo/scripting/v8-3.25_db.h b/src/mongo/scripting/v8-3.25_db.h new file mode 100644 index 00000000000..1950a7230d6 --- /dev/null +++ b/src/mongo/scripting/v8-3.25_db.h @@ -0,0 +1,167 @@ +// v8_db.h + +/* Copyright 2014 MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <boost/function.hpp> +#include <v8.h> + +#include "mongo/scripting/engine_v8-3.25.h" + +namespace mongo { + + class DBClientBase; + + /** + * get the DBClientBase connection from JS args + */ + mongo::DBClientBase* getConnection(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // Internal Cursor + v8::Local<v8::FunctionTemplate> getInternalCursorFunctionTemplate(V8Scope* scope); + + // Mongo constructors + v8::Local<v8::Value> mongoConsLocal(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> mongoConsExternal(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::FunctionTemplate> getMongoFunctionTemplate(V8Scope* scope, bool local); + + // Mongo member functions + v8::Local<v8::Value> mongoFind(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> mongoInsert(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> mongoRemove(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> mongoUpdate(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> mongoAuth(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> mongoLogout(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> mongoCursorFromId(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // Cursor object + v8::Local<v8::Value> internalCursorCons(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> internalCursorNext(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> internalCursorHasNext(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> internalCursorReadOnly(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // BinData object + v8::Local<v8::Value> binDataInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> binDataToString(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> binDataToBase64(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> binDataToHex(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // NumberLong object + long long numberLongVal(V8Scope* scope, const v8::Local<v8::Object>& it); + v8::Local<v8::Value> numberLongInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> numberLongToNumber(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> numberLongValueOf(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> numberLongToString(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // NumberInt object + int numberIntVal(V8Scope* scope, const v8::Local<v8::Object>& it); + v8::Local<v8::Value> numberIntInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> numberIntToNumber(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> numberIntValueOf(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + v8::Local<v8::Value> numberIntToString(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // DBQuery object + v8::Local<v8::Value> dbQueryInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + void dbQueryIndexAccess(::uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info); + + // db constructor + v8::Local<v8::Value> dbInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); + + // collection constructor + v8::Local<v8::Value> collectionInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // ObjectId constructor + v8::Local<v8::Value> objectIdInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // DBRef constructor + v8::Local<v8::Value> dbRefInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // DBPointer constructor + v8::Local<v8::Value> dbPointerInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // Timestamp constructor + v8::Local<v8::Value> dbTimestampInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // UUID constructor + v8::Local<v8::Value> uuidInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); + + // MD5 constructor + v8::Local<v8::Value> md5Init(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); + + // HexData constructor + v8::Local<v8::Value> hexDataInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // Object.invalidForStorage() + v8::Local<v8::Value> v8ObjectInvalidForStorage(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // Object.bsonsize() + v8::Local<v8::Value> bsonsize(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); + + // global method + // Accepts 2 objects, converts them to BSONObj and calls woCompare on the first against the + // second. + v8::Local<v8::Value> bsonWoCompare(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args); + + // 'db.collection' property handlers + void collectionGetter(v8::Local<v8::String> name, + const v8::PropertyCallbackInfo<v8::Value>& info); + void collectionSetter(v8::Local<v8::String> name, v8::Local<v8::Value> value, + const v8::PropertyCallbackInfo<v8::Value>& info); + + typedef boost::function<void (V8Scope*, const v8::Local<v8::FunctionTemplate>&)> + V8FunctionPrototypeManipulatorFn; + + void v8RegisterMongoPrototypeManipulator(const V8FunctionPrototypeManipulatorFn& manipulator); +} + diff --git a/src/mongo/scripting/v8-3.25_profiler.cpp b/src/mongo/scripting/v8-3.25_profiler.cpp new file mode 100644 index 00000000000..d7e320a21e2 --- /dev/null +++ b/src/mongo/scripting/v8-3.25_profiler.cpp @@ -0,0 +1,77 @@ +/* Copyright 2013 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 <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/scripting/v8-3.25_profiler.h" + +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + void V8CpuProfiler::start(v8::Isolate* isolate, const StringData name) { + isolate->GetCpuProfiler()->StartCpuProfiling( + v8::String::NewFromUtf8(isolate, name.toString().c_str())); + } + + void V8CpuProfiler::stop(v8::Isolate* isolate, const StringData name) { + _cpuProfiles.insert(make_pair(name.toString(), + isolate->GetCpuProfiler()->StopCpuProfiling( + v8::String::NewFromUtf8(isolate, name.toString().c_str(), + v8::String::kNormalString, name.size())))); + } + + void V8CpuProfiler::traverseDepthFirst(const v8::CpuProfileNode* cpuProfileNode, + BSONArrayBuilder& arrayBuilder) { + if (cpuProfileNode == NULL) + return; + BSONObjBuilder frameObjBuilder; + frameObjBuilder.append("Function", + *v8::String::Utf8Value(cpuProfileNode->GetFunctionName())); + frameObjBuilder.append("Source", + *v8::String::Utf8Value(cpuProfileNode->GetScriptResourceName())); + frameObjBuilder.appendNumber("Line", cpuProfileNode->GetLineNumber()); + if (cpuProfileNode->GetChildrenCount()) { + BSONArrayBuilder subArrayBuilder(frameObjBuilder.subarrayStart("Children")); + for (int i = 0; i < cpuProfileNode->GetChildrenCount(); ++i) { + traverseDepthFirst(cpuProfileNode->GetChild(i), subArrayBuilder); + } + subArrayBuilder.done(); + } + arrayBuilder << frameObjBuilder.obj(); + } + + const BSONArray V8CpuProfiler::fetch(const StringData name) { + BSONArrayBuilder arrayBuilder; + CpuProfileMap::const_iterator iProf = _cpuProfiles.find(name.toString()); + if (iProf == _cpuProfiles.end()) + return arrayBuilder.arr(); + const v8::CpuProfile* cpuProfile = iProf->second; + if (cpuProfile == NULL) + return arrayBuilder.arr(); + traverseDepthFirst(cpuProfile->GetTopDownRoot(), arrayBuilder); + return arrayBuilder.arr(); + } +} diff --git a/src/mongo/scripting/v8-3.25_profiler.h b/src/mongo/scripting/v8-3.25_profiler.h new file mode 100644 index 00000000000..b602a3ca14e --- /dev/null +++ b/src/mongo/scripting/v8-3.25_profiler.h @@ -0,0 +1,46 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <v8.h> +#include <v8-profiler.h> +#include <map> + +#include "mongo/base/string_data.h" +#include "mongo/db/jsobj.h" + +namespace mongo { + + /** Collect CPU Profiling data from v8. */ + class V8CpuProfiler { + public: + /** Start the CPU profiler */ + void start(v8::Isolate* isolate, const StringData name); + + /** Stop the CPU profiler */ + void stop(v8::Isolate* isolate, const StringData name); + + /** Get the current cpu profile */ + const BSONArray fetch(const StringData name); + private: + void traverseDepthFirst(const v8::CpuProfileNode* cpuProfileNode, + BSONArrayBuilder& arrayBuilder); + + typedef std::map<std::string, const v8::CpuProfile*> CpuProfileMap; + CpuProfileMap _cpuProfiles; + }; + +} diff --git a/src/mongo/scripting/v8-3.25_utils.cpp b/src/mongo/scripting/v8-3.25_utils.cpp new file mode 100644 index 00000000000..9c2043431ea --- /dev/null +++ b/src/mongo/scripting/v8-3.25_utils.cpp @@ -0,0 +1,299 @@ +// v8_utils.cpp + +/* Copyright 2014 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/scripting/v8-3.25_utils.h" + +#include <boost/smart_ptr.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/xtime.hpp> +#include <iostream> +#include <map> +#include <sstream> +#include <vector> + +#include "mongo/platform/cstdint.h" +#include "mongo/scripting/engine_v8-3.25.h" +#include "mongo/scripting/v8-3.25_db.h" +#include "mongo/util/mongoutils/str.h" + +using namespace std; + +namespace mongo { + + std::string toSTLString(const v8::Local<v8::Value>& o) { + return StringData(V8String(o)).toString(); + } + + /** Get the properties of an object (and its prototype) as a comma-delimited string */ + std::string v8ObjectToString(const v8::Local<v8::Object>& o) { + v8::Local<v8::Array> properties = o->GetPropertyNames(); + v8::String::Utf8Value str(properties); + massert(16696 , "error converting js type to Utf8Value", *str); + return std::string(*str, str.length()); + } + + std::ostream& operator<<(std::ostream& s, const v8::Local<v8::Value>& o) { + v8::String::Utf8Value str(o); + s << *str; + return s; + } + + std::ostream& operator<<(std::ostream& s, const v8::TryCatch* try_catch) { + v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); + v8::String::Utf8Value exceptionText(try_catch->Exception()); + v8::Local<v8::Message> message = try_catch->Message(); + + if (message.IsEmpty()) { + s << *exceptionText << endl; + } + else { + v8::String::Utf8Value filename(message->GetScriptResourceName()); + int linenum = message->GetLineNumber(); + cout << *filename << ":" << linenum << " " << *exceptionText << endl; + + v8::String::Utf8Value sourceline(message->GetSourceLine()); + cout << *sourceline << endl; + + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) + cout << " "; + + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) + cout << "^"; + + cout << endl; + } + return s; + } + + class JSThreadConfig { + public: + JSThreadConfig(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args, + bool newScope = false) : + _started(), + _done(), + _newScope(newScope) { + jsassert(args.Length() > 0, "need at least one argument"); + jsassert(args[0]->IsFunction(), "first argument must be a function"); + + // arguments need to be copied into the isolate, go through bson + BSONObjBuilder b; + for(int i = 0; i < args.Length(); ++i) { + scope->v8ToMongoElement(b, "arg" + BSONObjBuilder::numStr(i), args[i]); + } + _args = b.obj(); + } + + ~JSThreadConfig() { + } + + void start() { + jsassert(!_started, "Thread already started"); + JSThread jt(*this); + _thread.reset(new boost::thread(jt)); + _started = true; + } + void join() { + jsassert(_started && !_done, "Thread not running"); + _thread->join(); + _done = true; + } + + BSONObj returnData() { + if (!_done) + join(); + return _returnData; + } + + private: + class JSThread { + public: + JSThread(JSThreadConfig& config) : _config(config) {} + + void operator()() { + try { + _config._scope.reset(static_cast<V8Scope*>(globalScriptEngine->newScope())); + v8::Locker v8lock(_config._scope->getIsolate()); + v8::Isolate::Scope iscope(_config._scope->getIsolate()); + v8::HandleScope handle_scope(_config._scope->getIsolate()); + v8::Context::Scope context_scope(_config._scope->getContext()); + + BSONObj args = _config._args; + v8::Local<v8::Function> f = + v8::Local<v8::Function>::Cast( v8::Local<v8::Value>( + _config._scope->mongoToV8Element(args.firstElement(), true))); + int argc = args.nFields() - 1; + + // TODO SERVER-8016: properly allocate handles on the stack + v8::Local<v8::Value> argv[24]; + BSONObjIterator it(args); + it.next(); + for(int i = 0; i < argc && i < 24; ++i) { + argv[i] = v8::Local<v8::Value>::New( + _config._scope->getIsolate(), + _config._scope->mongoToV8Element(*it, true)); + it.next(); + } + v8::TryCatch try_catch; + v8::Local<v8::Value> ret = + f->Call(_config._scope->getGlobal(), argc, argv); + if (ret.IsEmpty() || try_catch.HasCaught()) { + string e = _config._scope->v8ExceptionToSTLString(&try_catch); + log() << "js thread raised js exception: " << e << endl; + ret = v8::Undefined(_config._scope->getIsolate()); + // TODO propagate exceptions (or at least the fact that an exception was + // thrown) to the calling js on either join() or returnData(). + } + // ret is translated to BSON to switch isolate + BSONObjBuilder b; + _config._scope->v8ToMongoElement(b, "ret", ret); + _config._returnData = b.obj(); + } + catch (const DBException& e) { + // Keeping behavior the same as for js exceptions. + log() << "js thread threw c++ exception: " << e.toString(); + _config._returnData = BSON("ret" << BSONUndefined); + } + catch (const std::exception& e) { + log() << "js thread threw c++ exception: " << e.what(); + _config._returnData = BSON("ret" << BSONUndefined); + } + catch (...) { + log() << "js thread threw c++ non-exception"; + _config._returnData = BSON("ret" << BSONUndefined); + } + } + + private: + JSThreadConfig& _config; + }; + + bool _started; + bool _done; + bool _newScope; + BSONObj _args; + scoped_ptr<boost::thread> _thread; + scoped_ptr<V8Scope> _scope; + BSONObj _returnData; + }; + + v8::Local<v8::Value> ThreadInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Object> it = args.This(); + // NOTE I believe the passed JSThreadConfig will never be freed. If this + // policy is changed, JSThread may no longer be able to store JSThreadConfig + // by reference. + it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "_JSThreadConfig"), + v8::External::New(scope->getIsolate(), + new JSThreadConfig(scope, args))); + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> ScopedThreadInit(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::Object> it = args.This(); + // NOTE I believe the passed JSThreadConfig will never be freed. If this + // policy is changed, JSThread may no longer be able to store JSThreadConfig + // by reference. + it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "_JSThreadConfig"), + v8::External::New(scope->getIsolate(), + new JSThreadConfig(scope, args, true))); + return v8::Undefined(scope->getIsolate()); + } + + JSThreadConfig *thisConfig(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::Local<v8::External> c = v8::Local<v8::External>::Cast( + args.This()->GetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), + "_JSThreadConfig"))); + JSThreadConfig *config = (JSThreadConfig *)(c->Value()); + return config; + } + + v8::Local<v8::Value> ThreadStart(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + thisConfig(scope, args)->start(); + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> ThreadJoin(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + thisConfig(scope, args)->join(); + return v8::Undefined(scope->getIsolate()); + } + + v8::Local<v8::Value> ThreadReturnData(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + BSONObj data = thisConfig(scope, args)->returnData(); + return scope->mongoToV8Element(data.firstElement(), true); + } + + v8::Local<v8::Value> ThreadInject(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::EscapableHandleScope handle_scope(args.GetIsolate()); + jsassert(args.Length() == 1, "threadInject takes exactly 1 argument"); + jsassert(args[0]->IsObject(), "threadInject needs to be passed a prototype"); + v8::Local<v8::Object> o = args[0]->ToObject(); + + // install method on the Thread object + scope->injectV8Function("init", ThreadInit, o); + scope->injectV8Function("start", ThreadStart, o); + scope->injectV8Function("join", ThreadJoin, o); + scope->injectV8Function("returnData", ThreadReturnData, o); + return handle_scope.Escape(v8::Local<v8::Value>()); + } + + v8::Local<v8::Value> ScopedThreadInject(V8Scope* scope, + const v8::FunctionCallbackInfo<v8::Value>& args) { + v8::EscapableHandleScope handle_scope(args.GetIsolate()); + jsassert(args.Length() == 1, "threadInject takes exactly 1 argument"); + jsassert(args[0]->IsObject(), "threadInject needs to be passed a prototype"); + v8::Local<v8::Object> o = args[0]->ToObject(); + + scope->injectV8Function("init", ScopedThreadInit, o); + // inheritance takes care of other member functions + + return handle_scope.Escape(v8::Local<v8::Value>()); + } + + void installFork(V8Scope* scope, v8::Local<v8::Object> global, + v8::Local<v8::Context> context) { + scope->injectV8Function("_threadInject", ThreadInject, global); + scope->injectV8Function("_scopedThreadInject", ScopedThreadInject, global); + } + + v8::Local<v8::Value> v8AssertionException(const char* errorMessage) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return isolate->ThrowException( + v8::Exception::Error(v8::String::NewFromUtf8(isolate, errorMessage))); + } + v8::Local<v8::Value> v8AssertionException(const std::string& errorMessage) { + return v8AssertionException(errorMessage.c_str()); + } +} diff --git a/src/mongo/scripting/v8-3.25_utils.h b/src/mongo/scripting/v8-3.25_utils.h new file mode 100644 index 00000000000..f8e719d549e --- /dev/null +++ b/src/mongo/scripting/v8-3.25_utils.h @@ -0,0 +1,106 @@ +// v8_utils.h + +/* Copyright 2014 MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <cstdio> +#include <cstdlib> +#include <iostream> +#include <string> +#include <v8.h> + +#include <mongo/base/string_data.h> +#include <mongo/util/assert_util.h> + +namespace mongo { + +#define jsassert(x,msg) uassert(16664, (msg), (x)) + +#define argumentCheck(mustBeTrue, errorMessage) \ + if (!(mustBeTrue)) { \ + return v8AssertionException((errorMessage)); \ + } + + std::ostream& operator<<(std::ostream& s, const v8::Local<v8::Value>& o); + std::ostream& operator<<(std::ostream& s, const v8::Local<v8::TryCatch>* try_catch); + + /** Simple v8 object to string conversion helper */ + std::string toSTLString(const v8::Local<v8::Value>& o); + + /** Like toSTLString but doesn't allocate a new std::string + * + * This owns the string's memory so you need to be careful not to let the + * converted StringDatas outlive the V8Scope object. These rules are the + * same as converting from a std::string into a StringData. + * + * Safe: + * void someFunction(StringData argument); + * v8::Local<v8::String> aString; + * + * someFunction(V8String(aString)); // passing down stack as temporary + * + * V8String named (aString); + * someFunction(named); // passing up stack as named value + * + * StringData sd = named; // scope of sd is less than named + * + * Unsafe: + * StringData _member; + * + * StringData returningFunction() { + * StringData sd = V8String(aString); // sd outlives the temporary + * + * V8String named(aString) + * _member = named; // _member outlives named scope + * + * return V8String(aString); // passing up stack + * } + */ + class V8String { + public: + explicit V8String(const v8::Local<v8::Value>& o) :_str(o) { + massert(16686, "error converting js type to Utf8Value", *_str); + } + operator StringData () const { return StringData(*_str, _str.length()); } + private: + v8::String::Utf8Value _str; + }; + + /** Get the properties of an object (and it's prototype) as a comma-delimited string */ + std::string v8ObjectToString(const v8::Local<v8::Object>& o); + + class V8Scope; + void installFork(V8Scope* scope, + v8::Local<v8::Object> global, + v8::Local<v8::Context> context); + + /** Throw a V8 exception from Mongo callback code; message text will be preceded by "Error: ". + * Note: this function should be used for text that did not originate from the JavaScript + * engine. Errors from the JavaScript engine will already have a prefix such as + * ReferenceError, TypeError or SyntaxError. + * Note: call only from a native function called from JavaScript (a callback). + * The V8 ThrowException routine will note a JavaScript exception that will be + * "thrown" in JavaScript when we return from the native function. + * Note: it's required to return immediately to V8's execution control without calling any + * V8 API functions. In this state, an empty handle may (will) be returned. + * @param errorMessage Error message text. + * @return Empty handle to be returned from callback function. + */ + v8::Local<v8::Value> v8AssertionException(const char* errorMessage); + v8::Local<v8::Value> v8AssertionException(const std::string& errorMessage); +} + diff --git a/src/third_party/SConscript b/src/third_party/SConscript index 2d368b87977..ce497239c87 100644 --- a/src/third_party/SConscript +++ b/src/third_party/SConscript @@ -1,6 +1,6 @@ # -*- mode: python -*- -Import("env use_system_version_of_library windows darwin usev8") +Import("env use_system_version_of_library windows darwin usev8 v8suffix") env.SConscript( [ "murmurhash3/SConscript", @@ -56,9 +56,9 @@ if usev8: env.Library("shim_v8", ['shim_v8.cpp'], SYSLIBDEPS=[ env['LIBDEPS_V8_SYSLIBDEP'] ]) else: - env.Append(CPPPATH='$BUILD_DIR/third_party/v8/include') - env.SConscript('v8/SConscript') - env.Library('shim_v8', ['shim_v8.cpp'], LIBDEPS=['v8/v8']) + env.Append(CPPPATH='$BUILD_DIR/third_party/v8' + v8suffix + '/include') + env.SConscript('v8' + v8suffix + '/SConscript') + env.Library('shim_v8', ['shim_v8.cpp'], LIBDEPS=['v8' + v8suffix + '/v8']) if (GetOption("allocator") != "tcmalloc"): env.Library("shim_allocator", "shim_allocator.cpp") diff --git a/src/third_party/v8-3.25/SConscript b/src/third_party/v8-3.25/SConscript new file mode 100644 index 00000000000..fc944b3d860 --- /dev/null +++ b/src/third_party/v8-3.25/SConscript @@ -0,0 +1,437 @@ +# Copyright 2012 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# set up path for js2c import +import sys +from os.path import join, dirname, abspath +root_dir = dirname(File('SConscript').rfile().abspath) +sys.path.append(join(root_dir, 'tools')) +import js2c + +Import("env windows linux darwin solaris freebsd debugBuild openbsd") + +# pared-down copies of the equivalent structures in v8's SConstruct/SConscript: +LIBRARY_FLAGS = { + 'all': { + 'all': { + 'CPPDEFINES': ['ENABLE_DEBUGGER_SUPPORT', 'VERIFY_HEAP'], + }, + 'mode:debug': { + 'CPPDEFINES': ['V8_ENABLE_CHECKS', 'OBJECT_PRINT'] + }, + }, + 'gcc': { + 'all': { + 'CCFLAGS': ['-Wno-unused-parameter', + '-Woverloaded-virtual', + '-Wnon-virtual-dtor'] + }, + 'mode:debug': { + 'CPPDEFINES': ['ENABLE_DISASSEMBLER', 'DEBUG'], + }, + 'os:linux': { + 'CCFLAGS': ['-ansi', '-pedantic'], + }, + 'os:macos': { + 'CCFLAGS': ['-ansi', '-pedantic'], + }, + 'os:freebsd': { + 'CCFLAGS': ['-ansi'], + }, + 'os:solaris': { + # On Solaris, to get isinf, INFINITY, fpclassify and other macros one + # needs to define __C99FEATURES__. + 'CPPDEFINES': ['__C99FEATURES__'], + 'CCFLAGS': ['-ansi'], + }, + 'arch:ia32': { + 'CPPDEFINES': ['V8_TARGET_ARCH_IA32'], + }, + 'arch:x64': { + 'CPPDEFINES': ['V8_TARGET_ARCH_X64'], + }, + }, + 'msvc': { + 'all': { + 'CPPDEFINES': ['WIN32', '_CRT_RAND_S'], + 'CCFLAGS': ['/W3', '/WX', '/wd4351', '/wd4355', '/wd4800'], + }, + 'arch:ia32': { + 'CPPDEFINES': ['V8_TARGET_ARCH_IA32', '_USE_32BIT_TIME_T', 'V8_NO_FAST_TLS'], + }, + 'arch:x64': { + 'CPPDEFINES': ['V8_TARGET_ARCH_X64'], + }, + 'mode:debug': { + 'CPPDEFINES': ['_DEBUG', 'ENABLE_DISASSEMBLER', 'DEBUG'], + }, + } +} + +SOURCES = { + 'all': Split(""" + accessors.cc + allocation.cc + allocation-site-scopes.cc + allocation-tracker.cc + api.cc + arguments.cc + assembler.cc + assert-scope.cc + ast.cc + atomicops_internals_x86_gcc.cc + bignum-dtoa.cc + bignum.cc + bootstrapper.cc + builtins.cc + cached-powers.cc + checks.cc + code-stubs-hydrogen.cc + code-stubs.cc + codegen.cc + compilation-cache.cc + compiler.cc + contexts.cc + conversions.cc + counters.cc + cpu-profiler.cc + cpu.cc + data-flow.cc + date.cc + dateparser.cc + debug-agent.cc + debug.cc + deoptimizer.cc + disassembler.cc + diy-fp.cc + dtoa.cc + elements-kind.cc + elements.cc + execution.cc + extensions/externalize-string-extension.cc + extensions/free-buffer-extension.cc + extensions/gc-extension.cc + extensions/statistics-extension.cc + extensions/trigger-failure-extension.cc + factory.cc + fast-dtoa.cc + fixed-dtoa.cc + flags.cc + frames.cc + full-codegen.cc + func-name-inferrer.cc + gdb-jit.cc + global-handles.cc + handles.cc + heap-profiler.cc + heap-snapshot-generator.cc + heap.cc + hydrogen-bce.cc + hydrogen-bch.cc + hydrogen-canonicalize.cc + hydrogen-check-elimination.cc + hydrogen-dce.cc + hydrogen-dehoist.cc + hydrogen-environment-liveness.cc + hydrogen-escape-analysis.cc + hydrogen-gvn.cc + hydrogen-infer-representation.cc + hydrogen-infer-types.cc + hydrogen-instructions.cc + hydrogen-load-elimination.cc + hydrogen-mark-deoptimize.cc + hydrogen-mark-unreachable.cc + hydrogen-osr.cc + hydrogen-range-analysis.cc + hydrogen-redundant-phi.cc + hydrogen-removable-simulates.cc + hydrogen-representation-changes.cc + hydrogen-sce.cc + hydrogen-store-elimination.cc + hydrogen-uint32-analysis.cc + hydrogen.cc + ic.cc + icu_util.cc + incremental-marking.cc + interface.cc + interpreter-irregexp.cc + isolate.cc + jsregexp.cc + lithium-allocator.cc + lithium-codegen.cc + lithium.cc + liveedit.cc + log-utils.cc + log.cc + mark-compact.cc + messages.cc + objects-printer.cc + objects-visiting.cc + objects.cc + objects-debug.cc + once.cc + optimizing-compiler-thread.cc + parser.cc + preparse-data.cc + preparser.cc + profile-generator.cc + property.cc + regexp-macro-assembler-irregexp.cc + regexp-macro-assembler.cc + regexp-stack.cc + rewriter.cc + runtime-profiler.cc + runtime.cc + safepoint-table.cc + sampler.cc + scanner-character-streams.cc + scanner.cc + scopeinfo.cc + scopes.cc + serialize.cc + snapshot-common.cc + spaces.cc + store-buffer.cc + string-search.cc + string-stream.cc + strtod.cc + stub-cache.cc + sweeper-thread.cc + token.cc + transitions.cc + trig-table.cc + type-info.cc + types.cc + typing.cc + unicode.cc + utils.cc + v8-counters.cc + v8.cc + v8conversions.cc + v8threads.cc + v8utils.cc + variables.cc + version.cc + zone.cc + platform/condition-variable.cc + platform/mutex.cc + platform/semaphore.cc + platform/socket.cc + platform/time.cc + utils/random-number-generator.cc + """), + 'arch:ia32': Split(""" + ia32/assembler-ia32.cc + ia32/builtins-ia32.cc + ia32/code-stubs-ia32.cc + ia32/codegen-ia32.cc + ia32/cpu-ia32.cc + ia32/debug-ia32.cc + ia32/deoptimizer-ia32.cc + ia32/disasm-ia32.cc + ia32/frames-ia32.cc + ia32/full-codegen-ia32.cc + ia32/ic-ia32.cc + ia32/lithium-codegen-ia32.cc + ia32/lithium-gap-resolver-ia32.cc + ia32/lithium-ia32.cc + ia32/macro-assembler-ia32.cc + ia32/regexp-macro-assembler-ia32.cc + ia32/stub-cache-ia32.cc + """), + 'arch:x64': Split(""" + x64/assembler-x64.cc + x64/builtins-x64.cc + x64/code-stubs-x64.cc + x64/codegen-x64.cc + x64/cpu-x64.cc + x64/debug-x64.cc + x64/deoptimizer-x64.cc + x64/disasm-x64.cc + x64/frames-x64.cc + x64/full-codegen-x64.cc + x64/ic-x64.cc + x64/lithium-codegen-x64.cc + x64/lithium-gap-resolver-x64.cc + x64/lithium-x64.cc + x64/macro-assembler-x64.cc + x64/regexp-macro-assembler-x64.cc + x64/stub-cache-x64.cc + """), + 'os:freebsd': ['platform-freebsd.cc', 'platform-posix.cc'], + 'os:openbsd': ['platform-openbsd.cc', 'platform-posix.cc'], + 'os:linux': ['platform-linux.cc', 'platform-posix.cc'], + 'os:macos': ['platform-macos.cc', 'platform-posix.cc'], + 'os:solaris': ['platform-solaris.cc', 'platform-posix.cc'], + 'os:nullos': ['platform-nullos.cc'], + 'os:win32': ['platform-win32.cc', 'win32-math.cc'], + 'mode:release': [], + 'mode:debug': [ + 'prettyprinter.cc', 'regexp-macro-assembler-tracer.cc' + ] +} + +EXPERIMENTAL_LIBRARY_FILES = ''' +proxy.js +collection.js +'''.split() + +LIBRARY_FILES = ''' +runtime.js +v8natives.js +array.js +string.js +uri.js +math.js +messages.js +apinatives.js +date.js +regexp.js +json.js +liveedit-debugger.js +mirror-debugger.js +debug-debugger.js +'''.split() + +def get_flags(flag, toolchain, options): + ret = [] + for t in (toolchain, 'all'): + for o in (options.values() + ['all']): + ret.extend(LIBRARY_FLAGS[t].get(o,{}).get(flag,[])) + return ret + +def get_options(): + processor = env['PROCESSOR_ARCHITECTURE'] + if processor == 'i386': + arch_string = 'arch:ia32' + elif processor == 'i686': + arch_string = 'arch:ia32' + elif processor == 'x86_64': + arch_string = 'arch:x64' + elif processor == 'amd64': + arch_string = 'arch:x64' + else: + assert False, "Unsupported architecture: " + processor + + if linux: + os_string = 'os:linux' + elif darwin: + os_string = 'os:macos' + elif windows: + os_string = 'os:win32' + elif freebsd: + os_string = 'os:freebsd' + elif solaris: + os_string = 'os:solaris' + elif openbsd: + os_string = 'os:openbsd' + else: + os_string = 'os:nullos' + + if debugBuild: + mode_string = 'mode:debug' + else: + mode_string = 'mode:release' + + return {'mode': mode_string, 'os': os_string, 'arch': arch_string} + +def get_sources(options): + keys = options.values() + ['all'] + + sources = [] + for i in keys: + sources.extend(('src/'+s) for s in SOURCES[i]) + + # sources generated from .js files: + sources.append('src/libraries.cc') + sources.append('src/experimental-libraries.cc') + + # we're building with v8 "snapshot=off", which requires this file: + sources.append('src/snapshot-empty.cc') + + return sources + +def get_toolchain(): + if windows: + return 'msvc' + else: + return 'gcc' + +# convert our SConstruct variables to their v8 equivalents: +toolchain = get_toolchain() +options = get_options() +sources = get_sources(options) + +env = env.Clone() + +# remove -Iinclude and prepend -Isrc, to resolve namespace conflicts: +# +# mongo source needs to compile with include/v8.h, but v8 source +# needs to compile with src/v8.h +# +# in addition, v8 source needs to compile with src/parser.h, which +# is being placed here earlier in the search path than windows sdk's +# Include/parser.h (v8 doesn't even use any of those header files) +env['CPPPATH'].remove('$BUILD_DIR/third_party/v8-3.25/include') +env.Prepend(CPPPATH='$BUILD_DIR/third_party/v8-3.25/src') + +# add v8 ccflags and cppdefines to environment if they're not already +# present +ccflags = get_flags('CCFLAGS', toolchain, options) +ccflags = filter(lambda f : + f not in env['CCFLAGS'] + env['CXXFLAGS'] + env['CFLAGS'], + ccflags) +env.Append(CCFLAGS=ccflags) +cppdefines = get_flags('CPPDEFINES', toolchain, options) +cppdefines = filter(lambda f : f not in env['CPPDEFINES'], cppdefines) +env.Append(CPPDEFINES=cppdefines) + +# NOTE: Suppress attempts to enable warnings in v8. Trying to individually suppress with -Wno- +# results in a game of whack-a-mole between various versions of clang and gcc as they add new +# warnings. We won't be changing the v8 sources, so the warnings aren't helpful. +def removeIfPresent(lst, item): + try: + lst.remove(item) + except ValueError: + pass + +for to_remove in ['-Werror', '-Wall', '-W']: + removeIfPresent(env['CCFLAGS'], to_remove) + +# specify rules for building libraries.cc and experimental-libraries.cc +env['BUILDERS']['JS2C'] = Builder(action=js2c.JS2C) +experimental_library_files = [('src/'+s) for s in EXPERIMENTAL_LIBRARY_FILES] +experimental_library_files.append('src/macros.py') +env.JS2C(['src/experimental-libraries.cc'], + experimental_library_files, + TYPE='EXPERIMENTAL', + COMPRESSION='off') +library_files = [('src/'+s) for s in LIBRARY_FILES] +library_files.append('src/macros.py') +env.JS2C(['src/libraries.cc'], library_files, TYPE='CORE', COMPRESSION='off') + +env.Library("v8", sources) |