summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SConstruct14
-rw-r--r--buildscripts/utils.py5
-rw-r--r--src/mongo/SConscript10
-rw-r--r--src/mongo/scripting/engine_v8-3.25.cpp1852
-rw-r--r--src/mongo/scripting/engine_v8-3.25.h615
-rw-r--r--src/mongo/scripting/v8-3.25_db.cpp1117
-rw-r--r--src/mongo/scripting/v8-3.25_db.h167
-rw-r--r--src/mongo/scripting/v8-3.25_profiler.cpp77
-rw-r--r--src/mongo/scripting/v8-3.25_profiler.h46
-rw-r--r--src/mongo/scripting/v8-3.25_utils.cpp299
-rw-r--r--src/mongo/scripting/v8-3.25_utils.h106
-rw-r--r--src/third_party/SConscript8
-rw-r--r--src/third_party/v8-3.25/SConscript437
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)