summaryrefslogtreecommitdiff
path: root/src/mongo/scripting/engine_v8-3.25.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/scripting/engine_v8-3.25.cpp')
-rw-r--r--src/mongo/scripting/engine_v8-3.25.cpp3348
1 files changed, 1668 insertions, 1680 deletions
diff --git a/src/mongo/scripting/engine_v8-3.25.cpp b/src/mongo/scripting/engine_v8-3.25.cpp
index 13e3fac2f30..b7f241c8291 100644
--- a/src/mongo/scripting/engine_v8-3.25.cpp
+++ b/src/mongo/scripting/engine_v8-3.25.cpp
@@ -47,1432 +47,1426 @@ using namespace mongoutils;
namespace mongo {
- using std::cout;
- using std::endl;
- using std::map;
- using std::string;
- using std::stringstream;
+using std::cout;
+using std::endl;
+using std::map;
+using std::string;
+using std::stringstream;
#ifndef _MSC_EXTENSIONS
- const int V8Scope::objectDepthLimit;
+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);
+// 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());
- 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);
+ if (realObject.IsEmpty())
+ return;
+ if (realObject->HasOwnProperty(name)) {
+ // value already cached or added
+ result.Set(realObject->Get(name));
+ return;
}
- 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;
+ if (!holder || 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));
- }
- }
+ BSONObj obj = holder->_obj;
+ BSONElement elmt = obj.getField(key.c_str());
+ if (elmt.eoo())
+ return;
- void indexedDelete(uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
- v8::Local<v8::Boolean> val;
- v8::ReturnValue<v8::Boolean> result = info.GetReturnValue();
+ val = scope->mongoToV8Element(elmt, holder->_readOnly);
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;
+ 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());
- 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 (!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();
-
- if (hasGlobalServiceContext()) {
- getGlobalServiceContext()->registerKillOpListener(globalScriptEngine);
- }
- }
- }
-
- std::string ScriptEngine::getInterpreterVersionString() {
- return "V8 3.25.28";
- }
-
- void V8ScriptEngine::interrupt(unsigned opId) {
- stdx::lock_guard<stdx::mutex> 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() {
- stdx::lock_guard<stdx::mutex> interruptLock(_globalInterruptLock);
- for (OpIdToScopeMap::iterator iScope = _opToScopeMap.begin();
- iScope != _opToScopeMap.end(); ++iScope) {
- iScope->second->kill();
- }
- }
-
- void V8Scope::registerOperation(OperationContext* txn) {
- stdx::lock_guard<stdx::mutex> giLock(_engine->_globalInterruptLock);
- invariant(_opId == 0);
- _opId = txn->getOpID();
- _engine->_opToScopeMap[_opId] = this;
- LOG(2) << "V8Scope " << static_cast<const void*>(this) << " registered for op " << _opId;
- Status status = txn->checkForInterruptNoAssert();
- if (!status.isOK()) {
- kill();
- }
- }
-
- void V8Scope::unregisterOperation() {
- stdx::lock_guard<stdx::mutex> giLock(_engine->_globalInterruptLock);
- LOG(2) << "V8Scope " << static_cast<const void*>(this) << " unregistered for op "
- << _opId << endl;
- if (_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);
- _opId = 0;
- }
- }
-
- bool V8Scope::nativePrologue() {
- v8::Locker l(_isolate);
- stdx::lock_guard<stdx::mutex> cbEnterLock(_interruptLock);
- if (v8::V8::IsExecutionTerminating(_isolate)) {
- LOG(2) << "v8 execution interrupted. isolate: "
- << static_cast<const void*>(_isolate) << endl;
- return false;
- }
- if (isKillPending()) {
- // 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);
- stdx::lock_guard<stdx::mutex> cbLeaveLock(_interruptLock);
- _inNativeExecution = false;
- if (v8::V8::IsExecutionTerminating(_isolate)) {
- LOG(2) << "v8 execution interrupted. isolate: "
- << static_cast<const void*>(_isolate) << endl;
- return false;
- }
- if (isKillPending()) {
- 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() {
- stdx::lock_guard<stdx::mutex> 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;
- }
-
- OperationContext* V8Scope::getOpContext() const {
- return _opCtx;
- }
-
- /**
- * Display a list of all known ops (for verbose output)
- */
- std::string V8ScriptEngine::printKnownOps_inlock() {
- stringstream out;
- if (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),
- _opId(0),
- _opCtx(NULL) {
-
- // 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);
- }
-
- V8Scope::~V8Scope() {
- unregisterOperation();
- }
-
- 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 = args.Callee()->GetHiddenValue(
- scope->strLitToV8("_native_function")).As<v8::External>();
- NativeFunction function = (NativeFunction)(f->Value());
- v8::Local<v8::External> data = args.Callee()->GetHiddenValue(
- scope->strLitToV8("_native_data")).As<v8::External>();
- 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()));
+ if (realObject.IsEmpty())
return;
+ if (realObject->Has(index)) {
+ // value already cached or added
+ result.Set(realObject->Get(index));
}
+ string key = str::stream() << index;
- v8::Local<v8::External> f = v8::Local<v8::External>::Cast(args.Data());
- 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()));
+ BSONHolder* holder = unwrapHolder(scope, info.Holder());
+ if (!holder)
return;
- }
-
- if (!exceptionText.empty()) {
- result.Set(v8AssertionException(exceptionText));
+ if (holder->_removed.count(key))
return;
- }
- result.Set(ret);
- }
- void V8Scope::init(const BSONObj * data) {
- if (! data)
+ BSONObj obj = holder->_obj;
+ BSONElement elmt = obj.getField(key);
+ if (elmt.eoo())
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, 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;
+ 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 (!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();
+
+ if (hasGlobalServiceContext()) {
+ getGlobalServiceContext()->registerKillOpListener(globalScriptEngine);
+ }
+ }
+}
+
+std::string ScriptEngine::getInterpreterVersionString() {
+ return "V8 3.25.28";
+}
+
+void V8ScriptEngine::interrupt(unsigned opId) {
+ stdx::lock_guard<stdx::mutex> 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() {
+ stdx::lock_guard<stdx::mutex> interruptLock(_globalInterruptLock);
+ for (OpIdToScopeMap::iterator iScope = _opToScopeMap.begin(); iScope != _opToScopeMap.end();
+ ++iScope) {
+ iScope->second->kill();
+ }
+}
+
+void V8Scope::registerOperation(OperationContext* txn) {
+ stdx::lock_guard<stdx::mutex> giLock(_engine->_globalInterruptLock);
+ invariant(_opId == 0);
+ _opId = txn->getOpID();
+ _engine->_opToScopeMap[_opId] = this;
+ LOG(2) << "V8Scope " << static_cast<const void*>(this) << " registered for op " << _opId;
+ Status status = txn->checkForInterruptNoAssert();
+ if (!status.isOK()) {
+ kill();
+ }
+}
+
+void V8Scope::unregisterOperation() {
+ stdx::lock_guard<stdx::mutex> giLock(_engine->_globalInterruptLock);
+ LOG(2) << "V8Scope " << static_cast<const void*>(this) << " unregistered for op " << _opId
+ << endl;
+ if (_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);
+ _opId = 0;
+ }
+}
+
+bool V8Scope::nativePrologue() {
+ v8::Locker l(_isolate);
+ stdx::lock_guard<stdx::mutex> cbEnterLock(_interruptLock);
+ if (v8::V8::IsExecutionTerminating(_isolate)) {
+ LOG(2) << "v8 execution interrupted. isolate: " << static_cast<const void*>(_isolate)
+ << endl;
+ return false;
}
-
- 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;
+ if (isKillPending()) {
+ // 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;
+}
- v8::Local<v8::FunctionTemplate> getTimestampFunctionTemplate(V8Scope* scope) {
- v8::Local<v8::FunctionTemplate> ts = scope->createV8Function(dbTimestampInit);
- return ts;
+bool V8Scope::nativeEpilogue() {
+ v8::Locker l(_isolate);
+ stdx::lock_guard<stdx::mutex> cbLeaveLock(_interruptLock);
+ _inNativeExecution = false;
+ if (v8::V8::IsExecutionTerminating(_isolate)) {
+ LOG(2) << "v8 execution interrupted. isolate: " << static_cast<const void*>(_isolate)
+ << endl;
+ return false;
}
-
- 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 }");
+ if (isKillPending()) {
+ LOG(2) << "marked for death while leaving callback. isolate: "
+ << static_cast<const void*>(_isolate) << endl;
+ v8::V8::TerminateExecution(_isolate);
+ return false;
}
-
- 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());
+ return true;
+}
+
+void V8Scope::kill() {
+ stdx::lock_guard<stdx::mutex> 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;
+}
+
+OperationContext* V8Scope::getOpContext() const {
+ return _opCtx;
+}
+
+/**
+ * Display a list of all known ops (for verbose output)
+ */
+std::string V8ScriptEngine::printKnownOps_inlock() {
+ stringstream out;
+ if (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),
+ _opId(0),
+ _opCtx(NULL) {
+ // 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);
+}
+
+V8Scope::~V8Scope() {
+ unregisterOperation();
+}
+
+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 =
+ args.Callee()->GetHiddenValue(scope->strLitToV8("_native_function")).As<v8::External>();
+ NativeFunction function = (NativeFunction)(f->Value());
+ v8::Local<v8::External> data =
+ args.Callee()->GetHiddenValue(scope->strLitToV8("_native_data")).As<v8::External>();
+ 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::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::External> f = v8::Local<v8::External>::Cast(args.Data());
+ v8Function function = (v8Function)(f->Value());
+ v8::Local<v8::Value> ret;
+ string exceptionText;
- v8::Local<v8::Value> maxKeyToJson(V8Scope* scope,
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- return scope->strLitToV8("{ \"$maxKey\" : 1 }");
+ try {
+ // execute the native function
+ ret = function(scope, args);
+ } catch (const std::exception& e) {
+ exceptionText = e.what();
+ } catch (...) {
+ exceptionText = "unknown exception in V8Scope::v8Callback";
}
- 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());
+ 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, 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::Local<v8::Value> stackTrace = try_catch->StackTrace();
+ if (!stackTrace.IsEmpty()) {
+ ss << StringData(V8String(stackTrace));
+ } else {
+ ss << StringData(V8String((try_catch->Exception())));
+ }
+
+ // get the exception message
+ v8::Local<v8::Message> message = try_catch->Message();
+ if (message.IsEmpty())
+ return ss.str();
- 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;
- }
+ // get the resource (e.g. file or internal call)
+ v8::String::Utf8Value resourceName(message->GetScriptResourceName());
+ if (!*resourceName)
+ return ss.str();
- std::string V8Scope::v8ExceptionToSTLString(const v8::TryCatch* try_catch) {
- stringstream ss;
- v8::Local<v8::Value> stackTrace = try_catch->StackTrace();
- if (!stackTrace.IsEmpty()) {
- ss << StringData(V8String(stackTrace));
- }
- else {
- ss << StringData(V8String((try_catch->Exception())));
+ 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;
- // 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;
+ 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, " ");
}
- 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;
+ // trim leading chars
+ codeNear = str::ltrim(codeNear);
+ ss << " near '" << codeNear << "' ";
const int linenum = message->GetLineNumber();
- if (linenum != 1) ss << ":" << linenum;
+ if (linenum != 1)
+ ss << " (line " << linenum << ")";
}
- return ss.str();
+ } 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 -----
+// --- functions -----
- bool hasFunctionIdentifier(StringData code) {
- if (code.size() < 9 || code.find("function") != 0 )
- return false;
+bool hasFunctionIdentifier(StringData code) {
+ if (code.size() < 9 || code.find("function") != 0)
+ return false;
- return code[8] == ' ' || code[8] == '(';
- }
+ 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 + "}";
+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;
+ string fn = str::stream() << "_funcs" << functionNumber;
+ code = str::stream() << fn << " = " << code;
- v8::Local<v8::Script> script = v8::Script::Compile(
- v8StringData(code),
- v8StringData(fn));
+ v8::Local<v8::Script> script = v8::Script::Compile(v8StringData(code), v8StringData(fn));
- // throw on error
- checkV8ErrorState(script, try_catch);
+ // throw on error
+ checkV8ErrorState(script, try_catch);
- v8::Local<v8::Value> result = script->Run();
+ v8::Local<v8::Value> result = script->Run();
- // throw on error
- checkV8ErrorState(result, try_catch);
+ // throw on error
+ checkV8ErrorState(result, try_catch);
- return handle_scope.Escape(v8::Local<v8::Function>::Cast(
- getGlobal()->Get(v8StringData(fn))));
- }
+ 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;
- }
+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),
+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);
+}
+
+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";
+ error() << _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";
+ error() << _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(t, global->Get(f));
- global->ForceSet(f, v8::Undefined(_isolate));
+ global->ForceSet(strLitToV8("__returnValue"), result);
}
- 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();
+ return 0;
+}
- if (!nativeEpilogue()) {
- _error = "JavaScript execution terminated";
- error() << _error << endl;
- uasserted(16711, _error);
- }
-
- if (timeoutMs)
- // start the deadline timer for this script
- _engine->getDeadlineMonitor()->startDeadline(this, timeoutMs);
+bool V8Scope::exec(StringData code,
+ const string& name,
+ bool printResult,
+ bool reportError,
+ bool assertOnError,
+ int timeoutMs) {
+ V8_SIMPLE_HEADER
+ v8::TryCatch try_catch;
- result = ((v8::Function*)(*funcValue))->Call(v8recv, nargs, nargs ? args : NULL);
+ v8::Local<v8::Script> script = v8::Script::Compile(v8StringData(code), v8StringData(name));
- if (timeoutMs)
- // stop the deadline timer for this script
- _engine->getDeadlineMonitor()->stopDeadline(this);
+ if (checkV8ErrorState(script, try_catch, reportError, assertOnError))
+ return false;
- if (!nativePrologue()) {
- _error = "JavaScript execution terminated";
+ if (!nativeEpilogue()) {
+ _error = "JavaScript execution terminated";
+ if (reportError)
error() << _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;
+ if (assertOnError)
+ uasserted(13475, _error);
+ return false;
}
- bool V8Scope::exec(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)
- error() << _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)
- error() << _error << endl;
- if (assertOnError)
- uasserted(16721, _error);
- return false;
- }
-
- if (checkV8ErrorState(result, try_catch, reportError, assertOnError))
- return false;
+ if (timeoutMs)
+ // start the deadline timer for this script
+ _engine->getDeadlineMonitor()->startDeadline(this, timeoutMs);
- v8::Local<v8::Object> global = getGlobal();
- global->ForceSet(strLitToV8("__lastres__"), result);
+ v8::Local<v8::Value> result = script->Run();
- if (printResult && !result->IsUndefined()) {
- // appears to only be used by shell
- cout << V8String(result) << endl;
- }
+ if (timeoutMs)
+ // stopt the deadline timer for this script
+ _engine->getDeadlineMonitor()->stopDeadline(this);
- 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 nativeFunc,
- v8::Local<v8::Object>& obj,
- void* data) {
- v8::Local<v8::FunctionTemplate> ft = createV8Function(nativeCallback);
- injectV8Function(field, ft, obj);
- v8::Local<v8::Function> func = ft->GetFunction();
- func->SetHiddenValue(strLitToV8("_native_function"),
- v8::External::New(_isolate, (void*)nativeFunc));
- func->SetHiddenValue(strLitToV8("_native_data"),
- v8::External::New(_isolate, data));
- }
-
- v8::Local<v8::FunctionTemplate> V8Scope::injectV8Function(const char *field,
- v8Function func) {
- v8::Local<v8::Object> global = getGlobal();
- return injectV8Function(field, func, global);
+ if (!nativePrologue()) {
+ _error = "JavaScript execution terminated";
+ if (reportError)
+ error() << _error << endl;
+ if (assertOnError)
+ uasserted(16721, _error);
+ return false;
}
- v8::Local<v8::FunctionTemplate> V8Scope::injectV8Function(const char *field,
- v8Function func,
- v8::Local<v8::Object> obj) {
- return injectV8Function(field, createV8Function(func), obj);
- }
+ if (checkV8ErrorState(result, try_catch, reportError, assertOnError))
+ return false;
- 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::Object> global = getGlobal();
+ global->ForceSet(strLitToV8("__lastres__"), result);
- 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;
+ if (printResult && !result->IsUndefined()) {
+ // appears to only be used by shell
+ cout << V8String(result) << endl;
}
- v8::Local<v8::FunctionTemplate> V8Scope::createV8Function(v8Function func) {
- v8::Local<v8::Value> funcHandle = v8::External::New(_isolate,
- reinterpret_cast<void*>(func));
- v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(_isolate, v8Callback,
- funcHandle);
- ft->Set(strLitToV8("_v8_function"),
- v8::Boolean::New(_isolate, true),
- static_cast<v8::PropertyAttribute>(v8::DontEnum | v8::ReadOnly));
- return ft;
- }
+ return true;
+}
- 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::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 nativeFunc,
+ v8::Local<v8::Object>& obj,
+ void* data) {
+ v8::Local<v8::FunctionTemplate> ft = createV8Function(nativeCallback);
+ injectV8Function(field, ft, obj);
+ v8::Local<v8::Function> func = ft->GetFunction();
+ func->SetHiddenValue(strLitToV8("_native_function"),
+ v8::External::New(_isolate, (void*)nativeFunc));
+ func->SetHiddenValue(strLitToV8("_native_data"), v8::External::New(_isolate, data));
+}
+
+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::Value> funcHandle = v8::External::New(_isolate, reinterpret_cast<void*>(func));
+ v8::Local<v8::FunctionTemplate> ft =
+ v8::FunctionTemplate::New(_isolate, v8Callback, funcHandle);
+ ft->Set(strLitToV8("_v8_function"),
+ v8::Boolean::New(_isolate, true),
+ 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::localConnectForDbEval(OperationContext* txn, const char* dbName) {
+ {
+ V8_SIMPLE_HEADER;
- void V8Scope::localConnectForDbEval(OperationContext* txn, const char * dbName) {
- {
- V8_SIMPLE_HEADER;
-
- invariant(_opCtx == NULL);
- _opCtx = txn;
-
- 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);
- }
+ invariant(_opCtx == NULL);
+ _opCtx = txn;
- // 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;
+ 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);
}
- loadStored(txn);
- }
- void V8Scope::externalSetup() {
- V8_SIMPLE_HEADER
- if (_connectState == EXTERNAL)
- return;
- if (_connectState == LOCAL)
- uasserted(12512, "localConnect already called, can't call externalSetup");
+ // 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 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));
-
+ // install the Mongo function object and instantiate the 'db' global
+ _MongoFT.Set(_isolate, getMongoFunctionTemplate(this, true));
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
- unregisterOperation();
- _error = "";
- _pendingKill = false;
- _inNativeExecution = true;
- }
-
- v8::Local<v8::Value> V8Scope::newFunction(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.toString();
- 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;
+ 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(txn);
+}
+
+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
+ unregisterOperation();
+ _error = "";
+ _pendingKill = false;
+ _inNativeExecution = true;
+}
+
+v8::Local<v8::Value> V8Scope::newFunction(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.toString();
+ 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);
+ 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;
- }
+ 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
+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()) {
+ switch (elem.type()) {
case mongo::Code:
return newFunction(elem.valueStringData());
case CodeWScope:
@@ -1512,29 +1506,27 @@ namespace mongo {
return v8::Boolean::New(_isolate, elem.boolean());
case mongo::EOO:
case mongo::jstNULL:
- case mongo::Undefined: // duplicate sm behavior
+ 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> 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()),
+ 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);
+ const char* data = elem.binData(len);
stringstream ss;
base64::encode(ss, data, len);
argv[0] = v8::Number::New(_isolate, elem.binDataType());
@@ -1547,11 +1539,11 @@ namespace mongo {
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()),
+ 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;
@@ -1560,16 +1552,15 @@ namespace mongo {
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) {
+ (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 {
+ } 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));
+ argv[2] = v8::Integer::New(
+ _isolate, (unsigned long)(nativeUnsignedLong & 0x00000000ffffffff));
return NumberLongFT()->GetFunction()->NewInstance(3, argv);
}
case mongo::MinKey:
@@ -1581,345 +1572,342 @@ namespace mongo {
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);
+ massert(16661,
+ str::stream() << "can't handle type: " << elem.type() << " " << elem.toString(),
+ false);
break;
- }
- return v8::Undefined(_isolate);
- }
-
- void V8Scope::v8ToMongoNumber(BSONObjBuilder& b,
- 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,
- 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,
- 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,
- StringData elementName,
- v8::Local<v8::Object> obj) {
-
- verify(BinDataFT()->HasInstance(obj));
- verify(obj->InternalFieldCount() == 1);
- std::string binData(base64::decode(toSTLString(obj->GetInternalField(0))));
- b.appendBinData(elementName,
- binData.size(),
- mongo::BinDataType(obj->Get(strLitToV8("type"))->ToInt32()->Value()),
- binData.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,
- 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)) {
- Timestamp 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, 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 v8::Undefined(_isolate);
+}
+
+void V8Scope::v8ToMongoNumber(BSONObjBuilder& b,
+ 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;
}
- 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.
}
+ b.append(elementName, val);
+}
+
+void V8Scope::v8ToMongoRegex(BSONObjBuilder& b,
+ 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, 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,
+ StringData elementName,
+ v8::Local<v8::Object> obj) {
+ verify(BinDataFT()->HasInstance(obj));
+ verify(obj->InternalFieldCount() == 1);
+ std::string binData(base64::decode(toSTLString(obj->GetInternalField(0))));
+ b.appendBinData(elementName,
+ binData.size(),
+ mongo::BinDataType(obj->Get(strLitToV8("type"))->ToInt32()->Value()),
+ binData.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,
+ 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)) {
+ Timestamp 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,
+ 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 << " ";
- // --- 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;
+ if (args[i].IsEmpty()) {
+ // failed to get object to convert
+ ss << "[unknown type]";
+ continue;
}
- 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");
+ if (args[i]->IsExternal()) {
+ // object is External
+ ss << "[mongo internal]";
+ continue;
}
- 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::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::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())));
+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())));
+}
+
+/**
+ * Check for an error condition (e.g. empty handle, JS exception, OOM) after executing
+ * a v8 operation.
+ * @resultHandle handle storing the result of the preceding 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)
+ error() << _error << endl;
+ if (assertOnError)
+ uasserted(16722, _error);
+ return true;
}
- /**
- * Check for an error condition (e.g. empty handle, JS exception, OOM) after executing
- * a v8 operation.
- * @resultHandle handle storing the result of the preceding 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;
- }
+ return false;
+}
- if (haveError) {
- if (reportError)
- error() << _error << endl;
- if (assertOnError)
- uasserted(16722, _error);
- return true;
- }
-
- return false;
- }
-
- MONGO_INITIALIZER(JavascriptPrintDomain)(InitializerContext*) {
- jsPrintLogDomain = logger::globalLogManager()->getNamedDomain("javascriptOutput");
- return Status::OK();
- }
+MONGO_INITIALIZER(JavascriptPrintDomain)(InitializerContext*) {
+ jsPrintLogDomain = logger::globalLogManager()->getNamedDomain("javascriptOutput");
+ return Status::OK();
+}
-} // namespace mongo
+} // namespace mongo