diff options
Diffstat (limited to 'src/mongo/scripting/engine_v8.cpp')
-rw-r--r-- | src/mongo/scripting/engine_v8.cpp | 1880 |
1 files changed, 0 insertions, 1880 deletions
diff --git a/src/mongo/scripting/engine_v8.cpp b/src/mongo/scripting/engine_v8.cpp deleted file mode 100644 index e4463106ece..00000000000 --- a/src/mongo/scripting/engine_v8.cpp +++ /dev/null @@ -1,1880 +0,0 @@ -// engine_v8.cpp - -/* Copyright 2009 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery - -#include "mongo/platform/basic.h" - -#include "mongo/scripting/engine_v8.h" - -#include <iostream> - -#include "mongo/base/init.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/service_context.h" -#include "mongo/platform/decimal128.h" -#include "mongo/platform/unordered_set.h" -#include "mongo/scripting/v8_db.h" -#include "mongo/scripting/v8_utils.h" -#include "mongo/util/base64.h" -#include "mongo/util/log.h" -#include "mongo/util/mongoutils/str.h" - -using namespace mongoutils; - -namespace mongo { - -using std::cout; -using std::endl; -using std::map; -using std::string; -using std::stringstream; - -#ifndef _MSC_EXTENSIONS -const int V8Scope::objectDepthLimit; -#endif - -// Generated symbols for JS files -namespace JSFiles { -extern const JSFile types; -extern const JSFile assert; -} - -// The unwrapXXX functions extract internal fields from an object wrapped by wrapBSONObject. -// These functions are currently only used in places that should always have the correct -// type of object, however it may be possible for users to come up with a way to make these -// called with the wrong type so calling code should always check the returns. -static BSONHolder* unwrapHolder(V8Scope* scope, const v8::Handle<v8::Object>& obj) { - // Warning: can't throw exceptions in this context. - if (!scope->LazyBsonFT()->HasInstance(obj)) - return NULL; - - v8::Handle<v8::External> field = v8::Handle<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::Handle<v8::Object>& obj) { - // Warning: can't throw exceptions in this context. - BSONHolder* holder = unwrapHolder(scope, obj); - return holder ? holder->_obj : BSONObj(); -} - -static v8::Handle<v8::Object> unwrapObject(V8Scope* scope, const v8::Handle<v8::Object>& obj) { - // Warning: can't throw exceptions in this context. - if (!scope->LazyBsonFT()->HasInstance(obj)) - return v8::Handle<v8::Object>(); - - return obj->GetInternalField(1).As<v8::Object>(); -} - -void V8Scope::wrapBSONObject(v8::Handle<v8::Object> obj, BSONObj data, bool readOnly) { - verify(LazyBsonFT()->HasInstance(obj)); - - // Nothing below throws - BSONHolder* holder = new BSONHolder(data); - holder->_readOnly = readOnly; - holder->_scope = this; - obj->SetInternalField(0, v8::External::New(holder)); // Holder - obj->SetInternalField(1, v8::Object::New()); // Object - v8::Persistent<v8::Object> p = v8::Persistent<v8::Object>::New(obj); - bsonHolderTracker.track(p, holder); -} - -static v8::Handle<v8::Value> namedGet(v8::Local<v8::String> name, const v8::AccessorInfo& info) { - v8::HandleScope handle_scope; - v8::Handle<v8::Value> val; - try { - V8Scope* scope = getScope(info.GetIsolate()); - v8::Handle<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return v8::Handle<v8::Value>(); - if (realObject->HasOwnProperty(name)) { - // value already cached or added - return handle_scope.Close(realObject->Get(name)); - } - - string key = toSTLString(name); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder || holder->_removed.count(key)) - return handle_scope.Close(v8::Handle<v8::Value>()); - - BSONObj obj = holder->_obj; - BSONElement elmt = obj.getField(key.c_str()); - if (elmt.eoo()) - return handle_scope.Close(v8::Handle<v8::Value>()); - - val = scope->mongoToV8Element(elmt, holder->_readOnly); - - 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) { - return v8AssertionException(dbEx.toString()); - } catch (...) { - return v8AssertionException(string("error getting property ") + toSTLString(name)); - } - return handle_scope.Close(val); -} - -static v8::Handle<v8::Value> namedGetRO(v8::Local<v8::String> name, const v8::AccessorInfo& info) { - return namedGet(name, info); -} - -static v8::Handle<v8::Value> namedSet(v8::Local<v8::String> name, - v8::Local<v8::Value> value_obj, - const v8::AccessorInfo& info) { - string key = toSTLString(name); - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return v8::Handle<v8::Value>(); - holder->_removed.erase(key); - holder->_modified = true; - - v8::Handle<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return v8::Handle<v8::Value>(); - realObject->Set(name, value_obj); - return value_obj; -} - -static v8::Handle<v8::Array> namedEnumerator(const v8::AccessorInfo& info) { - v8::HandleScope handle_scope; - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return v8::Handle<v8::Array>(); - BSONObj obj = holder->_obj; - v8::Handle<v8::Array> out = v8::Array::New(); - 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::Handle<v8::String> name = scope->v8StringData(sname); - added.insert(sname); - out->Set(outIndex++, name); - } - - - v8::Handle<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return v8::Handle<v8::Array>(); - v8::Handle<v8::Array> fields = realObject->GetOwnPropertyNames(); - const int len = fields->Length(); - for (int field = 0; field < len; field++) { - v8::Handle<v8::String> name = fields->Get(field).As<v8::String>(); - V8String sname(name); - if (added.count(sname)) - continue; - out->Set(outIndex++, name); - } - return handle_scope.Close(out); -} - -v8::Handle<v8::Boolean> namedDelete(v8::Local<v8::String> name, const v8::AccessorInfo& info) { - v8::HandleScope handle_scope; - string key = toSTLString(name); - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return v8::Handle<v8::Boolean>(); - holder->_removed.insert(key); - holder->_modified = true; - - v8::Handle<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return v8::Handle<v8::Boolean>(); - realObject->Delete(name); - return v8::True(); -} - -static v8::Handle<v8::Value> indexedGet(uint32_t index, const v8::AccessorInfo& info) { - v8::HandleScope handle_scope; - v8::Handle<v8::Value> val; - try { - V8Scope* scope = getScope(info.GetIsolate()); - v8::Handle<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return v8::Handle<v8::Value>(); - if (realObject->Has(index)) { - // value already cached or added - return handle_scope.Close(realObject->Get(index)); - } - string key = str::stream() << index; - - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return v8::Handle<v8::Value>(); - if (holder->_removed.count(key)) - return handle_scope.Close(v8::Handle<v8::Value>()); - - BSONObj obj = holder->_obj; - BSONElement elmt = obj.getField(key); - if (elmt.eoo()) - return handle_scope.Close(v8::Handle<v8::Value>()); - val = scope->mongoToV8Element(elmt, holder->_readOnly); - 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) { - return v8AssertionException(dbEx.toString()); - } catch (...) { - return v8AssertionException(str::stream() << "error getting indexed property " << index); - } - return handle_scope.Close(val); -} - -v8::Handle<v8::Boolean> indexedDelete(uint32_t index, const v8::AccessorInfo& info) { - string key = str::stream() << index; - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return v8::Handle<v8::Boolean>(); - holder->_removed.insert(key); - holder->_modified = true; - - // also delete in JS obj - v8::Handle<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return v8::Handle<v8::Boolean>(); - realObject->Delete(index); - return v8::True(); -} - -static v8::Handle<v8::Value> indexedGetRO(uint32_t index, const v8::AccessorInfo& info) { - return indexedGet(index, info); -} - -static v8::Handle<v8::Value> indexedSet(uint32_t index, - v8::Local<v8::Value> value_obj, - const v8::AccessorInfo& info) { - string key = str::stream() << index; - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return v8::Handle<v8::Value>(); - holder->_removed.erase(key); - holder->_modified = true; - - v8::Handle<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return v8::Handle<v8::Value>(); - realObject->Set(index, value_obj); - return value_obj; -} - -v8::Handle<v8::Value> NamedReadOnlySet(v8::Local<v8::String> property, - v8::Local<v8::Value> value, - const v8::AccessorInfo& info) { - cout << "cannot write property " << V8String(property) << " to read-only object" << endl; - return value; -} - -v8::Handle<v8::Boolean> NamedReadOnlyDelete(v8::Local<v8::String> property, - const v8::AccessorInfo& info) { - cout << "cannot delete property " << V8String(property) << " from read-only object" << endl; - return v8::Boolean::New(false); -} - -v8::Handle<v8::Value> IndexedReadOnlySet(uint32_t index, - v8::Local<v8::Value> value, - const v8::AccessorInfo& info) { - cout << "cannot write property " << index << " to read-only array" << endl; - return value; -} - -v8::Handle<v8::Boolean> IndexedReadOnlyDelete(uint32_t index, const v8::AccessorInfo& info) { - cout << "cannot delete property " << index << " from read-only array" << endl; - return v8::Boolean::New(false); -} - -/** - * 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::V8::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() : _opToScopeMap(), _deadlineMonitor() {} - -V8ScriptEngine::~V8ScriptEngine() {} - -void ScriptEngine::setup() { - if (!globalScriptEngine) { - globalScriptEngine = new V8ScriptEngine(); - - if (hasGlobalServiceContext()) { - getGlobalServiceContext()->registerKillOpListener(globalScriptEngine); - } - } -} - -std::string ScriptEngine::getInterpreterVersionString() { - return "V8 3.12.19"; -} - -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(), - _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 handleScope; - _context = v8::Context::New(); - v8::Context::Scope context_scope(_context); - - _isolate->SetData(this); - - // display heap statistics on MarkAndSweep GC run - v8::V8::AddGCPrologueCallback(gcCallback<GCPrologueState>, v8::kGCTypeMarkSweepCompact); - v8::V8::AddGCEpilogueCallback(gcCallback<GCEpilogueState>, v8::kGCTypeMarkSweepCompact); - - // if the isolate runs out of heap space, raise a flag on the StackGuard instead of - // calling abort() - v8::V8::IgnoreOutOfMemoryException(); - - // create a global (rooted) object - _global = v8::Persistent<v8::Object>::New(_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::Handle<v8::Value> regexp = _global->Get(strLitToV8("RegExp")); - verify(regexp->IsFunction()); - _jsRegExpConstructor = v8::Persistent<v8::Function>::New(regexp.As<v8::Function>()); - - // initialize lazy object template - _LazyBsonFT = v8::Persistent<v8::FunctionTemplate>::New(v8::FunctionTemplate::New()); - 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(true), v8::DontEnum); - - _ROBsonFT = v8::Persistent<v8::FunctionTemplate>::New(v8::FunctionTemplate::New()); - 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(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 _context->HasOutOfMemoryException(); - return false; -} - -v8::Handle<v8::Value> V8Scope::load(V8Scope* scope, const v8::Arguments& args) { - v8::Context::Scope context_scope(scope->_context); - 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(); -} - -v8::Handle<v8::Value> V8Scope::nativeCallback(V8Scope* scope, const v8::Arguments& args) { - BSONObj ret; - string exceptionText; - v8::HandleScope handle_scope; - 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); - } - return handle_scope.Close(scope->mongoToV8Element(ret.firstElement())); -} - -v8::Handle<v8::Value> V8Scope::v8Callback(const v8::Arguments& args) { - v8::HandleScope handle_scope; - V8Scope* scope = getScope(args.GetIsolate()); - - if (!scope->nativePrologue()) - // execution terminated - return v8::Undefined(); - - v8::Local<v8::External> f = v8::Local<v8::External>::Cast(args.Data()); - v8Function function = (v8Function)(f->Value()); - v8::Handle<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 - return v8::Undefined(); - - if (!exceptionText.empty()) { - return v8AssertionException(exceptionText); - } - return handle_scope.Close(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 - _global->ForceSet(v8StringData(field), v8::Number::New(val)); -} - -void V8Scope::setString(const char* field, StringData val) { - V8_SIMPLE_HEADER - _global->ForceSet(v8StringData(field), v8::String::New(val.rawData(), val.size())); -} - -void V8Scope::setBoolean(const char* field, bool val) { - V8_SIMPLE_HEADER - _global->ForceSet(v8StringData(field), v8::Boolean::New(val)); -} - -void V8Scope::setElement(const char* field, const BSONElement& e) { - V8_SIMPLE_HEADER - _global->ForceSet(v8StringData(field), mongoToV8Element(e)); -} - -void V8Scope::setObject(const char* field, const BSONObj& obj, bool readOnly) { - V8_SIMPLE_HEADER - _global->ForceSet(v8StringData(field), mongoToLZV8(obj, readOnly ? v8::ReadOnly : v8::None)); -} - -int V8Scope::type(const char* field) { - V8_SIMPLE_HEADER - v8::Handle<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::Handle<v8::Value> V8Scope::get(const char* field) { - return _global->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(); -} - -Decimal128 V8Scope::getNumberDecimal(const char* field) { - V8_SIMPLE_HEADER - return Decimal128(toSTLString(get(field)->ToString())); -} - -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::Handle<v8::Value> v = get(field); - if (v->IsNull() || v->IsUndefined()) - return BSONObj(); - uassert(10231, "not an object", v->IsObject()); - return v8ToMongo(v->ToObject()); -} - -v8::Handle<v8::FunctionTemplate> getNumberLongFunctionTemplate(V8Scope* scope) { - v8::Handle<v8::FunctionTemplate> numberLong = scope->createV8Function(numberLongInit); - v8::Handle<v8::ObjectTemplate> proto = numberLong->PrototypeTemplate(); - scope->injectV8Method("valueOf", numberLongValueOf, proto); - scope->injectV8Method("toNumber", numberLongToNumber, proto); - scope->injectV8Method("toString", numberLongToString, proto); - return numberLong; -} - -v8::Handle<v8::FunctionTemplate> getNumberIntFunctionTemplate(V8Scope* scope) { - v8::Handle<v8::FunctionTemplate> numberInt = scope->createV8Function(numberIntInit); - v8::Handle<v8::ObjectTemplate> proto = numberInt->PrototypeTemplate(); - scope->injectV8Method("valueOf", numberIntValueOf, proto); - scope->injectV8Method("toNumber", numberIntToNumber, proto); - scope->injectV8Method("toString", numberIntToString, proto); - return numberInt; -} - -v8::Handle<v8::FunctionTemplate> getNumberDecimalFunctionTemplate(V8Scope* scope) { - v8::Handle<v8::FunctionTemplate> numberDecimal = scope->createV8Function(numberDecimalInit); - v8::Handle<v8::ObjectTemplate> proto = numberDecimal->PrototypeTemplate(); - scope->injectV8Method("toString", numberDecimalToString, proto); - return numberDecimal; -} - -v8::Handle<v8::FunctionTemplate> getBinDataFunctionTemplate(V8Scope* scope) { - v8::Handle<v8::FunctionTemplate> binData = scope->createV8Function(binDataInit); - binData->InstanceTemplate()->SetInternalFieldCount(1); - v8::Handle<v8::ObjectTemplate> proto = binData->PrototypeTemplate(); - scope->injectV8Method("toString", binDataToString, proto); - scope->injectV8Method("base64", binDataToBase64, proto); - scope->injectV8Method("hex", binDataToHex, proto); - return binData; -} - -v8::Handle<v8::FunctionTemplate> getTimestampFunctionTemplate(V8Scope* scope) { - v8::Handle<v8::FunctionTemplate> ts = scope->createV8Function(dbTimestampInit); - return ts; -} - -v8::Handle<v8::Value> minKeyToJson(V8Scope* scope, const v8::Arguments& 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 }"); -} - -v8::Handle<v8::Value> minKeyCall(const v8::Arguments& args) { - // 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::Handle<v8::Function> func = scope->MinKeyFT()->GetFunction(); - v8::Handle<v8::String> name = scope->strLitToV8("singleton"); - v8::Handle<v8::Value> singleton = func->GetHiddenValue(name); - if (!singleton.IsEmpty()) - return singleton; - - if (!args.IsConstructCall()) - return func->NewInstance(); - - verify(scope->MinKeyFT()->HasInstance(args.This())); - - func->SetHiddenValue(name, args.This()); - return v8::Undefined(); -} - -v8::Handle<v8::FunctionTemplate> getMinKeyFunctionTemplate(V8Scope* scope) { - v8::Handle<v8::FunctionTemplate> myTemplate = v8::FunctionTemplate::New(minKeyCall); - myTemplate->InstanceTemplate()->SetCallAsFunctionHandler(minKeyCall); - myTemplate->PrototypeTemplate()->Set("tojson", - scope->createV8Function(minKeyToJson)->GetFunction()); - myTemplate->SetClassName(scope->strLitToV8("MinKey")); - return myTemplate; -} - -v8::Handle<v8::Value> maxKeyToJson(V8Scope* scope, const v8::Arguments& args) { - return scope->strLitToV8("{ \"$maxKey\" : 1 }"); -} - -v8::Handle<v8::Value> maxKeyCall(const v8::Arguments& args) { - // See comment in minKeyCall. - V8Scope* scope = getScope(args.GetIsolate()); - - v8::Handle<v8::Function> func = scope->MaxKeyFT()->GetFunction(); - v8::Handle<v8::String> name = scope->strLitToV8("singleton"); - v8::Handle<v8::Value> singleton = func->GetHiddenValue(name); - if (!singleton.IsEmpty()) - return singleton; - - if (!args.IsConstructCall()) - return func->NewInstance(); - - verify(scope->MaxKeyFT()->HasInstance(args.This())); - - func->SetHiddenValue(name, args.This()); - return v8::Undefined(); -} - -v8::Handle<v8::FunctionTemplate> getMaxKeyFunctionTemplate(V8Scope* scope) { - v8::Handle<v8::FunctionTemplate> myTemplate = v8::FunctionTemplate::New(maxKeyCall); - myTemplate->InstanceTemplate()->SetCallAsFunctionHandler(maxKeyCall); - myTemplate->PrototypeTemplate()->Set("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::Handle<v8::Message> message = try_catch->Message(); - if (message.IsEmpty()) - return ss.str(); - - // get the resource (e.g. file or internal call) - v8::String::Utf8Value resourceName(message->GetScriptResourceName()); - if (!*resourceName) - return ss.str(); - - string resourceNameString = *resourceName; - if (resourceNameString.compare("undefined") == 0) - return ss.str(); - if (resourceNameString.find("_funcs") == 0) { - // script loaded from __createFunction - string code; - // find the source script based on the resource name supplied to v8::Script::Compile(). - // this is accomplished by converting the integer after the '_funcs' prefix. - unsigned int funcNum = str::toUnsigned(resourceNameString.substr(6)); - for (map<string, ScriptingFunction>::iterator it = getFunctionCache().begin(); - it != getFunctionCache().end(); - ++it) { - if (it->second == funcNum) { - code = it->first; - break; - } - } - if (!code.empty()) { - // append surrounding code (padded with up to 20 characters on each side) - int startPos = message->GetStartPosition(); - const int kPadding = 20; - if (startPos - kPadding < 0) - // lower bound exceeded - startPos = 0; - else - startPos -= kPadding; - - int displayRange = message->GetEndPosition(); - if (displayRange + kPadding > static_cast<int>(code.length())) - // upper bound exceeded - displayRange -= startPos; - else - // compensate for startPos padding - displayRange = (displayRange - startPos) + kPadding; - - if (startPos > static_cast<int>(code.length()) || - displayRange > static_cast<int>(code.length())) - return ss.str(); - - string codeNear = code.substr(startPos, displayRange); - for (size_t newLine = codeNear.find('\n'); newLine != string::npos; - newLine = codeNear.find('\n')) { - if (static_cast<int>(newLine) > displayRange - kPadding) { - // truncate at first newline past the reported end position - codeNear = codeNear.substr(0, newLine - 1); - break; - } - // convert newlines to spaces - codeNear.replace(newLine, 1, " "); - } - // trim leading chars - codeNear = str::ltrim(codeNear); - ss << " near '" << codeNear << "' "; - const int linenum = message->GetLineNumber(); - if (linenum != 1) - ss << " (line " << linenum << ")"; - } - } else if (resourceNameString.find("(shell") == 0) { - // script loaded from shell input -- simply print the error - } else { - // script loaded from file - ss << " at " << *resourceName; - const int linenum = message->GetLineNumber(); - if (linenum != 1) - ss << ":" << linenum; - } - return ss.str(); -} - -// --- functions ----- - -bool hasFunctionIdentifier(StringData code) { - if (code.size() < 9 || code.find("function") != 0) - return false; - - return code[8] == ' ' || code[8] == '('; -} - -v8::Local<v8::Function> V8Scope::__createFunction(const char* raw, - ScriptingFunction functionNumber) { - v8::HandleScope handle_scope; - v8::TryCatch try_catch; - raw = jsSkipWhiteSpace(raw); - string code = raw; - if (!hasFunctionIdentifier(code)) { - if (code.find('\n') == string::npos && !hasJSReturn(code) && - (code.find(';') == string::npos || code.find(';') == code.size() - 1)) { - code = "return " + code; - } - code = "function(){ " + code + "}"; - } - - string fn = str::stream() << "_funcs" << functionNumber; - code = str::stream() << fn << " = " << code; - - v8::Handle<v8::Script> script = v8::Script::Compile( - v8::String::New(code.c_str(), code.length()), v8::String::New(fn.c_str())); - - // throw on error - checkV8ErrorState(script, try_catch); - - v8::Local<v8::Value> result = script->Run(); - - // throw on error - checkV8ErrorState(result, try_catch); - - return handle_scope.Close( - v8::Handle<v8::Function>(v8::Function::Cast(*_global->Get(v8::String::New(fn.c_str()))))); -} - -ScriptingFunction V8Scope::_createFunction(const char* raw, ScriptingFunction functionNumber) { - V8_SIMPLE_HEADER - v8::Local<v8::Value> ret = __createFunction(raw, functionNumber); - v8::Persistent<v8::Value> f = v8::Persistent<v8::Value>::New(ret); - uassert(10232, "not a function", f->IsFunction()); - _funcs.push_back(f); - return functionNumber; -} - -void V8Scope::setFunction(const char* field, const char* code) { - V8_SIMPLE_HEADER - _global->ForceSet(v8StringData(field), __createFunction(code, getFunctionCache().size() + 1)); -} - -void V8Scope::rename(const char* from, const char* to) { - V8_SIMPLE_HEADER; - v8::Handle<v8::String> f = v8StringData(from); - v8::Handle<v8::String> t = v8StringData(to); - _global->ForceSet(t, _global->Get(f)); - _global->ForceSet(f, v8::Undefined()); -} - -int V8Scope::invoke(ScriptingFunction func, - const BSONObj* argsObject, - const BSONObj* recv, - int timeoutMs, - bool ignoreReturn, - bool readOnlyArgs, - bool readOnlyRecv) { - V8_SIMPLE_HEADER - v8::Handle<v8::Value> funcValue = _funcs[func - 1]; - 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::Handle<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::Handle<v8::Object> v8recv; - if (recv != 0) - v8recv = mongoToLZV8(*recv, readOnlyRecv); - else - v8recv = _global; - - 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::Handle<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; - } - _global->ForceSet(strLitToV8("__returnValue"), result); - } - - return 0; -} - -bool V8Scope::exec(StringData code, - const string& name, - bool printResult, - bool reportError, - bool assertOnError, - int timeoutMs) { - V8_SIMPLE_HEADER - v8::TryCatch try_catch; - - v8::Handle<v8::Script> script = v8::Script::Compile( - v8::String::New(code.rawData(), code.size()), v8::String::New(name.c_str(), name.length())); - - 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::Handle<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; - - _global->ForceSet(strLitToV8("__lastres__"), result); - - if (printResult && !result->IsUndefined()) { - // appears to only be used by shell - cout << V8String(result) << endl; - } - - return true; -} - -void V8Scope::injectNative(const char* field, NativeFunction func, void* data) { - V8_SIMPLE_HEADER // required due to public access - injectNative(field, func, _global, data); -} - -void V8Scope::injectNative(const char* field, - NativeFunction nativeFunc, - v8::Handle<v8::Object>& obj, - void* data) { - v8::Handle<v8::FunctionTemplate> ft = createV8Function(nativeCallback); - injectV8Function(field, ft, obj); - v8::Handle<v8::Function> func = ft->GetFunction(); - func->SetHiddenValue(strLitToV8("_native_function"), v8::External::New((void*)nativeFunc)); - func->SetHiddenValue(strLitToV8("_native_data"), v8::External::New(data)); -} - -v8::Handle<v8::FunctionTemplate> V8Scope::injectV8Function(const char* field, v8Function func) { - return injectV8Function(field, func, _global); -} - -v8::Handle<v8::FunctionTemplate> V8Scope::injectV8Function(const char* field, - v8Function func, - v8::Handle<v8::Object>& obj) { - return injectV8Function(field, createV8Function(func), obj); -} - -v8::Handle<v8::FunctionTemplate> V8Scope::injectV8Function(const char* fieldCStr, - v8::Handle<v8::FunctionTemplate> ft, - v8::Handle<v8::Object>& obj) { - v8::Handle<v8::String> field = v8StringData(fieldCStr); - ft->SetClassName(field); - v8::Handle<v8::Function> func = ft->GetFunction(); - func->SetName(field); - obj->ForceSet(field, func); - return ft; -} - -v8::Handle<v8::FunctionTemplate> V8Scope::injectV8Method(const char* fieldCStr, - v8Function func, - v8::Handle<v8::ObjectTemplate>& proto) { - v8::Handle<v8::String> field = v8StringData(fieldCStr); - v8::Handle<v8::FunctionTemplate> ft = createV8Function(func); - v8::Handle<v8::Function> f = ft->GetFunction(); - f->SetName(field); - proto->Set(field, f); - return ft; -} - -v8::Handle<v8::FunctionTemplate> V8Scope::createV8Function(v8Function func) { - v8::Handle<v8::Value> funcHandle = v8::External::New(reinterpret_cast<void*>(func)); - v8::Handle<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(v8Callback, funcHandle); - ft->Set(strLitToV8("_v8_function"), - v8::Boolean::New(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) { - typedef v8::Persistent<v8::FunctionTemplate> FTPtr; - { - 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); - } - - // 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 = FTPtr::New(getMongoFunctionTemplate(this, true)); - 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() { - typedef v8::Persistent<v8::FunctionTemplate> FTPtr; - 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, _global, _context); - - // install 'load' helper function - injectV8Function("load", load); - - // install the Mongo function object - _MongoFT = FTPtr::New(getMongoFunctionTemplate(this, false)); - injectV8Function("Mongo", MongoFT(), _global); - execCoreFiles(); - _connectState = EXTERNAL; -} - -void V8Scope::installDBAccess() { - typedef v8::Persistent<v8::FunctionTemplate> FTPtr; - _DBFT = FTPtr::New(createV8Function(dbInit)); - _DBQueryFT = FTPtr::New(createV8Function(dbQueryInit)); - _DBCollectionFT = FTPtr::New(createV8Function(collectionInit)); - - // These must be done before calling injectV8Function - DBFT()->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter, collectionSetter); - DBQueryFT()->InstanceTemplate()->SetIndexedPropertyHandler(dbQueryIndexAccess); - DBCollectionFT()->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter, - collectionSetter); - - 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 = FTPtr::New(getInternalCursorFunctionTemplate(this)); -} - -void V8Scope::installBSONTypes() { - typedef v8::Persistent<v8::FunctionTemplate> FTPtr; - _ObjectIdFT = FTPtr::New(injectV8Function("ObjectId", objectIdInit)); - _DBRefFT = FTPtr::New(injectV8Function("DBRef", dbRefInit)); - _DBPointerFT = FTPtr::New(injectV8Function("DBPointer", dbPointerInit)); - - _BinDataFT = FTPtr::New(getBinDataFunctionTemplate(this)); - _NumberLongFT = FTPtr::New(getNumberLongFunctionTemplate(this)); - _NumberIntFT = FTPtr::New(getNumberIntFunctionTemplate(this)); - _NumberDecimalFT = FTPtr::New(getNumberDecimalFunctionTemplate(this)); - _TimestampFT = FTPtr::New(getTimestampFunctionTemplate(this)); - _MinKeyFT = FTPtr::New(getMinKeyFunctionTemplate(this)); - _MaxKeyFT = FTPtr::New(getMaxKeyFunctionTemplate(this)); - - injectV8Function("BinData", BinDataFT(), _global); - injectV8Function("NumberLong", NumberLongFT(), _global); - injectV8Function("NumberInt", NumberIntFT(), _global); - if (Decimal128::enabled) { - injectV8Function("NumberDecimal", NumberDecimalFT(), _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::HandleScope handle_scope; - v8::TryCatch try_catch; - string codeStr = str::stream() << "____MongoToV8_newFunction_temp = " << code; - - v8::Local<v8::Script> compiled = - v8::Script::New(v8::String::New(codeStr.c_str(), codeStr.length())); - - // 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.Close(ret); -} - -v8::Local<v8::Value> V8Scope::newId(const OID& id) { - v8::HandleScope handle_scope; - v8::Handle<v8::Function> idCons = ObjectIdFT()->GetFunction(); - v8::Handle<v8::Value> argv[1]; - const string& idString = id.toString(); - argv[0] = v8::String::New(idString.c_str(), idString.length()); - return handle_scope.Close(idCons->NewInstance(1, argv)); -} - -/** - * converts a BSONObj to a Lazy V8 object - */ -v8::Handle<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::Handle<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::Handle<v8::FunctionTemplate> templ = readOnly ? ROBsonFT() : LazyBsonFT(); - v8::Handle<v8::Object> o = templ->GetFunction()->NewInstance(); - massert(16496, - str::stream() << "V8: NULL Object template instantiated. " - << (v8::V8::IsExecutionTerminating() ? "v8 execution is terminating." - : "v8 still executing."), - *o != NULL); - - wrapBSONObject(o, m, readOnly); - return o; -} - -v8::Handle<v8::Value> V8Scope::mongoToV8Element(const BSONElement& elem, bool readOnly) { - v8::Handle<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 - Decimal128 nativeDecimal; // native representation of NumberDecimal - - - switch (elem.type()) { - case mongo::Code: - return newFunction(elem.valueStringData()); - case CodeWScope: - if (!elem.codeWScopeObject().isEmpty()) - warning() << "CodeWScope doesn't transfer to db.eval" << endl; - return newFunction(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen() - 1)); - case mongo::Symbol: - case mongo::String: - return v8::String::New(elem.valuestr(), elem.valuestrsize() - 1); - case mongo::jstOID: - return newId(elem.__oid()); - case mongo::NumberDouble: - case mongo::NumberInt: - return v8::Number::New(elem.number()); - case mongo::Array: { - // NB: This comment may no longer be accurate. - // for arrays it's better to use non lazy object because: - // - the lazy array is not a true v8 array and requires some v8 src change - // for all methods to work - // - it made several tests about 1.5x slower - // - most times when an array is accessed, all its values will be used - - // It is faster to allow the v8::Array to grow than call nFields() on the array - v8::Handle<v8::Array> array = v8::Array::New(); - int i = 0; - BSONForEach(subElem, elem.embeddedObject()) { - array->Set(i++, mongoToV8Element(subElem, readOnly)); - } - return array; - } - case mongo::Object: - return mongoToLZV8(elem.embeddedObject(), readOnly); - case mongo::Date: - return v8::Date::New(static_cast<double>(elem.date().toMillisSinceEpoch())); - case mongo::Bool: - return v8::Boolean::New(elem.boolean()); - case mongo::EOO: - case mongo::jstNULL: - case mongo::Undefined: // duplicate sm behavior - return v8::Null(); - case mongo::RegEx: { - // TODO parse into a custom type that can support any patterns and flags SERVER-9803 - v8::TryCatch tryCatch; - - v8::Handle<v8::Value> args[] = {v8::String::New(elem.regex()), - v8::String::New(elem.regexFlags())}; - - v8::Handle<v8::Value> ret = _jsRegExpConstructor->NewInstance(2, args); - uassert(16863, - str::stream() << "Error converting " << elem.toString(false) << " in field " - << elem.fieldName() << " to a JS RegExp object: " - << toSTLString(tryCatch.Exception()), - !tryCatch.HasCaught()); - - return ret; - } - case mongo::BinData: { - int len; - const char* data = elem.binData(len); - stringstream ss; - base64::encode(ss, data, len); - argv[0] = v8::Number::New(elem.binDataType()); - argv[1] = v8::String::New(ss.str().c_str()); - return BinDataFT()->GetFunction()->NewInstance(2, argv); - } - case mongo::bsonTimestamp: { - v8::TryCatch tryCatch; - - argv[0] = v8::Number::New(elem.timestampTime().toMillisSinceEpoch() / 1000); - argv[1] = v8::Number::New(elem.timestampInc()); - - v8::Handle<v8::Value> ret = TimestampFT()->GetFunction()->NewInstance(2, argv); - uassert(17355, - str::stream() << "Error converting " << elem.toString(false) << " in field " - << elem.fieldName() << " to a JS Timestamp object: " - << toSTLString(tryCatch.Exception()), - !tryCatch.HasCaught()); - - return ret; - } - case mongo::NumberLong: - nativeUnsignedLong = elem.numberLong(); - // values above 2^53 are not accurately represented in JS - if ((long long)nativeUnsignedLong == - (long long)(double)(long long)(nativeUnsignedLong) && - nativeUnsignedLong < 9007199254740992ULL) { - argv[0] = v8::Number::New((double)(long long)(nativeUnsignedLong)); - return NumberLongFT()->GetFunction()->NewInstance(1, argv); - } else { - argv[0] = v8::Number::New((double)(long long)(nativeUnsignedLong)); - argv[1] = v8::Integer::New(nativeUnsignedLong >> 32); - argv[2] = - v8::Integer::New((unsigned long)(nativeUnsignedLong & 0x00000000ffffffff)); - return NumberLongFT()->GetFunction()->NewInstance(3, argv); - } - case mongo::NumberDecimal: { - nativeDecimal = elem.numberDecimal(); - // Store number decimals as strings - // Note: This prevents shell arithmetic, which is performed for number longs - // by converting them to doubles, which is imprecise. Until there is a better - // method to handle non-double shell arithmetic, decimals will remain - // as a non-numeric js type. - std::string decString = nativeDecimal.toString(); - argv[0] = v8::String::New(decString.c_str()); - return NumberDecimalFT()->GetFunction()->NewInstance(1, argv); - } - case mongo::MinKey: - return MinKeyFT()->GetFunction()->NewInstance(); - case mongo::MaxKey: - return MaxKeyFT()->GetFunction()->NewInstance(); - case mongo::DBRef: - argv[0] = v8StringData(elem.dbrefNS()); - argv[1] = newId(elem.dbrefOID()); - return DBPointerFT()->GetFunction()->NewInstance(2, argv); - default: - massert(16661, - str::stream() << "can't handle type: " << elem.type() << " " << elem.toString(), - false); - break; - } - return v8::Undefined(); -} - -void V8Scope::v8ToMongoNumber(BSONObjBuilder& b, - StringData elementName, - v8::Handle<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::Handle<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::Handle<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::Handle<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::Handle<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::Handle<v8::Value> value, - int depth, - BSONObj* originalParent) { - verify(value->IsObject()); - v8::Handle<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 (NumberDecimalFT()->HasInstance(value)) { - b.append(elementName, numberDecimalVal(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::Handle<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::Handle<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::fromMillisSinceEpoch(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::Handle<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::Handle<v8::Value> V8Scope::Print(V8Scope* scope, const v8::Arguments& args) { - LogstreamBuilder builder(jsPrintLogDomain, getThreadName(), logger::LogSeverity::Log()); - std::ostream& ss = builder.stream(); - v8::HandleScope handle_scope; - bool first = true; - for (int i = 0; i < args.Length(); i++) { - if (first) - first = false; - else - ss << " "; - - if (args[i].IsEmpty()) { - // failed to get object to convert - ss << "[unknown type]"; - continue; - } - if (args[i]->IsExternal()) { - // object is External - ss << "[mongo internal]"; - continue; - } - - v8::String::Utf8Value str(args[i]); - ss << *str; - } - ss << "\n"; - return handle_scope.Close(v8::Undefined()); -} - -v8::Handle<v8::Value> V8Scope::Version(V8Scope* scope, const v8::Arguments& args) { - v8::HandleScope handle_scope; - return handle_scope.Close(v8::String::New(v8::V8::GetVersion())); -} - -v8::Handle<v8::Value> V8Scope::GCV8(V8Scope* scope, const v8::Arguments& args) { - // trigger low memory notification. for more granular control over garbage - // collection cycle, @see v8::V8::IdleNotification. - v8::V8::LowMemoryNotification(); - return v8::Undefined(); -} - -v8::Handle<v8::Value> V8Scope::startCpuProfiler(V8Scope* scope, const v8::Arguments& args) { - if (args.Length() != 1 || !args[0]->IsString()) { - return v8AssertionException("startCpuProfiler takes a string argument"); - } - scope->_cpuProfiler.start(*v8::String::Utf8Value(args[0]->ToString())); - return v8::Undefined(); -} - -v8::Handle<v8::Value> V8Scope::stopCpuProfiler(V8Scope* scope, const v8::Arguments& args) { - if (args.Length() != 1 || !args[0]->IsString()) { - return v8AssertionException("stopCpuProfiler takes a string argument"); - } - scope->_cpuProfiler.stop(*v8::String::Utf8Value(args[0]->ToString())); - return v8::Undefined(); -} - -v8::Handle<v8::Value> V8Scope::getCpuProfile(V8Scope* scope, const v8::Arguments& 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 << std::endl; - if (assertOnError) - uasserted(16722, _error); - return true; - } - - return false; -} - -MONGO_INITIALIZER(JavascriptPrintDomain)(InitializerContext*) { - jsPrintLogDomain = logger::globalLogManager()->getNamedDomain("javascriptOutput"); - return Status::OK(); -} - -} // namespace mongo |