diff options
author | Andrew Morrow <acm@mongodb.com> | 2015-07-16 16:25:13 -0400 |
---|---|---|
committer | Andrew Morrow <acm@mongodb.com> | 2015-08-24 08:31:31 -0400 |
commit | 3184194f9e0eb63fc3f6d73579681c4007022e9b (patch) | |
tree | 8bef720832965ac6af6269247f5185e9f7de9b45 | |
parent | 9a988c6c0ed719a03350d7e7dc7e62f555068bfd (diff) | |
download | mongo-3184194f9e0eb63fc3f6d73579681c4007022e9b.tar.gz |
SERVER-19378 Remove support for building with v8
-rw-r--r-- | SConstruct | 28 | ||||
-rw-r--r-- | distsrc/THIRD-PARTY-NOTICES | 52 | ||||
-rw-r--r-- | src/mongo/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/scripting/SConscript | 23 | ||||
-rw-r--r-- | src/mongo/scripting/engine_v8-3.25.cpp | 1913 | ||||
-rw-r--r-- | src/mongo/scripting/engine_v8-3.25.h | 653 | ||||
-rw-r--r-- | src/mongo/scripting/engine_v8.cpp | 1880 | ||||
-rw-r--r-- | src/mongo/scripting/engine_v8.h | 629 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_db.cpp | 1079 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_db.h | 155 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_profiler.cpp | 78 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_profiler.h | 58 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_utils.cpp | 420 | ||||
-rw-r--r-- | src/mongo/scripting/v8-3.25_utils.h | 117 | ||||
-rw-r--r-- | src/mongo/scripting/v8_db.cpp | 1195 | ||||
-rw-r--r-- | src/mongo/scripting/v8_db.h | 146 | ||||
-rw-r--r-- | src/mongo/scripting/v8_profiler.cpp | 78 | ||||
-rw-r--r-- | src/mongo/scripting/v8_profiler.h | 58 | ||||
-rw-r--r-- | src/mongo/scripting/v8_utils.cpp | 406 | ||||
-rw-r--r-- | src/mongo/scripting/v8_utils.h | 117 | ||||
-rw-r--r-- | src/third_party/SConscript | 27 |
21 files changed, 7 insertions, 9108 deletions
diff --git a/SConstruct b/SConstruct index 26fcc887a3b..1ccc0506d6c 100644 --- a/SConstruct +++ b/SConstruct @@ -116,7 +116,7 @@ def use_system_version_of_library(name): # add a new C++ library dependency that may be shimmed out to the system, add it to the below # list. def using_system_version_of_cxx_libraries(): - cxx_library_names = ["tcmalloc", "boost", "v8"] + cxx_library_names = ["tcmalloc", "boost"] return True in [use_system_version_of_library(x) for x in cxx_library_names] def make_variant_dir_generator(): @@ -213,11 +213,10 @@ add_option('wiredtiger', type='choice', ) -# library choices -js_engine_choices = ['v8-3.12', 'v8-3.25', 'mozjs', 'none'] +js_engine_choices = ['mozjs', 'none'] add_option('js-engine', choices=js_engine_choices, - default=js_engine_choices[2], + default=js_engine_choices[0], help='JavaScript scripting engine implementation', type='choice', ) @@ -358,11 +357,6 @@ add_option('use-system-zlib', nargs=0, ) -add_option('use-system-v8', - help='use system version of v8 library', - nargs=0, -) - add_option('use-system-stemmer', help='use system version of stemmer', nargs=0) @@ -786,14 +780,9 @@ jsEngine = get_option( "js-engine") serverJs = get_option( "server-js" ) == "on" -usev8 = (jsEngine.startswith('v8')) - usemozjs = (jsEngine.startswith('mozjs')) -v8version = jsEngine[3:] if jsEngine.startswith('v8-') else 'none' -v8suffix = '' if v8version == '3.12' else '-' + v8version - -if not serverJs and not usev8 and not usemozjs: +if not serverJs and not usemozjs: print("Warning: --server-js=off is not needed with --js-engine=none") # We defer building the env until we have determined whether we want certain values. Some values @@ -2234,13 +2223,6 @@ def doConfigure(myenv): if env.TargetOSIs('solaris'): conf.CheckLib( "nsl" ) - if usev8 and use_system_version_of_library("v8"): - if debugBuild: - v8_lib_choices = ["v8_g", "v8"] - else: - v8_lib_choices = ["v8"] - conf.FindSysLibDep( "v8", v8_lib_choices ) - conf.env['MONGO_BUILD_SASL_CLIENT'] = bool(has_option("use-sasl-client")) if conf.env['MONGO_BUILD_SASL_CLIENT'] and not conf.CheckLibWithHeader( "sasl2", @@ -2441,9 +2423,7 @@ Export("env") Export("get_option") Export("has_option use_system_version_of_library") Export("serverJs") -Export("usev8") Export("usemozjs") -Export("v8version v8suffix") Export("boostSuffix") Export('module_sconscripts') Export("debugBuild optBuild") diff --git a/distsrc/THIRD-PARTY-NOTICES b/distsrc/THIRD-PARTY-NOTICES index 37349340d22..353772aee41 100644 --- a/distsrc/THIRD-PARTY-NOTICES +++ b/distsrc/THIRD-PARTY-NOTICES @@ -42,58 +42,6 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -2) License Notice for V8 -------------------------- - -http://code.google.com/p/v8/source/browse/trunk/LICENSE - -This license applies to all parts of V8 that are not externally -maintained libraries. The externally maintained libraries used by V8 -are: - - - Jscre, located under third_party/jscre. This code is copyrighted - by the University of Cambridge and Apple Inc. and released under a - 2-clause BSD license. - - - Dtoa, located under third_party/dtoa. This code is copyrighted by - David M. Gay and released under an MIT license. - - - Strongtalk assembler, the basis of the files assembler-arm-inl.h, - assembler-arm.cc, assembler-arm.h, assembler-ia32-inl.h, - assembler-ia32.cc, assembler-ia32.h, assembler.cc and assembler.h. - This code is copyrighted by Sun Microsystems Inc. and released - under a 3-clause BSD license. - -These libraries have their own licenses; we recommend you read them, -as their terms may differ from the terms below. - -Copyright 2006-2008, Google Inc. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - 3) License Notice for PCRE -------------------------- diff --git a/src/mongo/SConscript b/src/mongo/SConscript index bab71bd6ac8..947bd37f6bd 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -14,7 +14,6 @@ import SCons.Action Import("env") Import("has_option") Import("get_option") -Import("usev8") Import("usemozjs") Import("use_system_version_of_library") @@ -348,7 +347,7 @@ if env.TargetOSIs('osx') or env["_HAVEPCAP"]: # --- shell --- -if not has_option('noshell') and (usev8 or usemozjs): +if not has_option('noshell') and usemozjs: shell_core_env = env.Clone() if has_option("safeshell"): shell_core_env.Append(CPPDEFINES=["MONGO_SAFE_SHELL"]) diff --git a/src/mongo/scripting/SConscript b/src/mongo/scripting/SConscript index 5db8dd52228..e6bc59b5464 100644 --- a/src/mongo/scripting/SConscript +++ b/src/mongo/scripting/SConscript @@ -3,9 +3,7 @@ Import([ 'env', 'serverJs', - 'usev8', 'usemozjs', - 'v8suffix', ]) env.Library( @@ -54,26 +52,7 @@ env.Library( ], ) -if usev8: - scriptingEnv = env.Clone() - scriptingEnv.InjectThirdPartyIncludePaths(libraries=['v8']) - scriptingEnv.Library( - target='scripting', - source=[ - 'engine_v8' + v8suffix + '.cpp', - 'v8' + v8suffix + '_db.cpp', - 'v8' + v8suffix + '_utils.cpp', - 'v8' + v8suffix + '_profiler.cpp', - ], - LIBDEPS=[ - 'bson_template_evaluator', - 'scripting_common', - '$BUILD_DIR/third_party/shim_v8', - '$BUILD_DIR/mongo/shell/mongojs', - '$BUILD_DIR/mongo/db/service_context', - ], - ) -elif usemozjs: +if usemozjs: scriptingEnv = env.Clone() scriptingEnv.InjectThirdPartyIncludePaths(libraries=['mozjs']) diff --git a/src/mongo/scripting/engine_v8-3.25.cpp b/src/mongo/scripting/engine_v8-3.25.cpp deleted file mode 100644 index b7f241c8291..00000000000 --- a/src/mongo/scripting/engine_v8-3.25.cpp +++ /dev/null @@ -1,1913 +0,0 @@ -/* Copyright 2014 MongoDB Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery - -#include "mongo/platform/basic.h" - -#include "mongo/scripting/engine_v8-3.25.h" - -#include <iostream> - -#include "mongo/base/init.h" -#include "mongo/db/service_context.h" -#include "mongo/db/operation_context.h" -#include "mongo/platform/unordered_set.h" -#include "mongo/scripting/v8-3.25_db.h" -#include "mongo/scripting/v8-3.25_utils.h" -#include "mongo/util/base64.h" -#include "mongo/util/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::Local<v8::Object>& obj) { - // Warning: can't throw exceptions in this context. - if (!scope->LazyBsonFT()->HasInstance(obj)) - return NULL; - - v8::Local<v8::External> field = v8::Local<v8::External>::Cast(obj->GetInternalField(0)); - if (field.IsEmpty() || !field->IsExternal()) - return 0; - void* ptr = field->Value(); - return (BSONHolder*)ptr; -} - -static BSONObj unwrapBSONObj(V8Scope* scope, const v8::Local<v8::Object>& obj) { - // Warning: can't throw exceptions in this context. - BSONHolder* holder = unwrapHolder(scope, obj); - return holder ? holder->_obj : BSONObj(); -} - -static v8::Local<v8::Object> unwrapObject(V8Scope* scope, const v8::Local<v8::Object>& obj) { - // Warning: can't throw exceptions in this context. - if (!scope->LazyBsonFT()->HasInstance(obj)) - return v8::Local<v8::Object>(); - - return obj->GetInternalField(1).As<v8::Object>(); -} - -void V8Scope::wrapBSONObject(v8::Local<v8::Object> obj, BSONObj data, bool readOnly) { - verify(LazyBsonFT()->HasInstance(obj)); - - // Nothing below throws - BSONHolder* holder = new BSONHolder(this, data, readOnly); - obj->SetInternalField(0, v8::External::New(_isolate, holder)); // Holder - obj->SetInternalField(1, v8::Object::New(_isolate)); // Object - bsonHolderTracker.track(_isolate, obj, holder); -} - -static void namedGet(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info) { - v8::HandleScope handle_scope(info.GetIsolate()); - v8::Local<v8::Value> val; - v8::ReturnValue<v8::Value> result = info.GetReturnValue(); - result.Set(val); - try { - V8Scope* scope = getScope(info.GetIsolate()); - v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return; - if (realObject->HasOwnProperty(name)) { - // value already cached or added - result.Set(realObject->Get(name)); - return; - } - - string key = toSTLString(name); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder || holder->_removed.count(key)) - return; - - BSONObj obj = holder->_obj; - BSONElement elmt = obj.getField(key.c_str()); - if (elmt.eoo()) - return; - - val = scope->mongoToV8Element(elmt, holder->_readOnly); - result.Set(val); - - if (obj.objsize() > 128 || val->IsObject()) { - // Only cache if expected to help (large BSON) or is required due to js semantics - realObject->Set(name, val); - } - - if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) { - // if accessing a subobject, it may get modified and base obj would not know - // have to set base as modified, which means some optim is lost - holder->_modified = true; - } - } catch (const DBException& dbEx) { - result.Set(v8AssertionException(dbEx.toString())); - } catch (...) { - result.Set(v8AssertionException(string("error getting property ") + toSTLString(name))); - } -} - -static void namedGetRO(v8::Local<v8::String> name, - const v8::PropertyCallbackInfo<v8::Value>& info) { - namedGet(name, info); -} - -static void namedSet(v8::Local<v8::String> name, - v8::Local<v8::Value> value_obj, - const v8::PropertyCallbackInfo<v8::Value>& info) { - v8::Local<v8::Value> val; - v8::ReturnValue<v8::Value> result = info.GetReturnValue(); - result.Set(val); - string key = toSTLString(name); - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return; - holder->_removed.erase(key); - holder->_modified = true; - - v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return; - realObject->Set(name, value_obj); - result.Set(value_obj); -} - -static void namedEnumerator(const v8::PropertyCallbackInfo<v8::Array>& info) { - v8::HandleScope handle_scope(info.GetIsolate()); - v8::Local<v8::Array> val; - v8::ReturnValue<v8::Array> result = info.GetReturnValue(); - result.Set(val); - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return; - BSONObj obj = holder->_obj; - v8::Local<v8::Array> out = v8::Array::New(scope->getIsolate()); - int outIndex = 0; - - unordered_set<StringData, StringData::Hasher> added; - // note here that if keys are parseable number, v8 will access them using index - for (BSONObjIterator it(obj); it.more();) { - const BSONElement& f = it.next(); - StringData sname(f.fieldName(), f.fieldNameSize() - 1); - if (holder->_removed.count(sname.toString())) - continue; - - v8::Local<v8::String> name = scope->v8StringData(sname); - added.insert(sname); - out->Set(outIndex++, name); - } - - - v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return; - v8::Local<v8::Array> fields = realObject->GetOwnPropertyNames(); - const int len = fields->Length(); - for (int field = 0; field < len; field++) { - v8::Local<v8::String> name = fields->Get(field).As<v8::String>(); - V8String sname(name); - if (added.count(sname)) - continue; - out->Set(outIndex++, name); - } - result.Set(out); -} - -void namedDelete(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Boolean>& info) { - v8::HandleScope handle_scope(info.GetIsolate()); - v8::Local<v8::Boolean> val; - v8::ReturnValue<v8::Boolean> result = info.GetReturnValue(); - result.Set(val); - string key = toSTLString(name); - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return; - holder->_removed.insert(key); - holder->_modified = true; - - v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return; - realObject->Delete(name); - result.Set(v8::True(scope->getIsolate())); -} - -static void indexedGet(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) { - v8::HandleScope handle_scope(info.GetIsolate()); - v8::Local<v8::Value> val; - v8::ReturnValue<v8::Value> result = info.GetReturnValue(); - result.Set(val); - try { - V8Scope* scope = getScope(info.GetIsolate()); - v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return; - if (realObject->Has(index)) { - // value already cached or added - result.Set(realObject->Get(index)); - } - string key = str::stream() << index; - - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return; - if (holder->_removed.count(key)) - return; - - BSONObj obj = holder->_obj; - BSONElement elmt = obj.getField(key); - if (elmt.eoo()) - return; - val = scope->mongoToV8Element(elmt, holder->_readOnly); - result.Set(val); - realObject->Set(index, val); - - if (elmt.type() == mongo::Object || elmt.type() == mongo::Array) { - // if accessing a subobject, it may get modified and base obj would not know - // have to set base as modified, which means some optim is lost - holder->_modified = true; - } - } catch (const DBException& dbEx) { - result.Set(v8AssertionException(dbEx.toString())); - } catch (...) { - result.Set( - v8AssertionException(str::stream() << "error getting indexed property " << index)); - } -} - -void indexedDelete(uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& info) { - v8::Local<v8::Boolean> val; - v8::ReturnValue<v8::Boolean> result = info.GetReturnValue(); - result.Set(val); - string key = str::stream() << index; - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return; - holder->_removed.insert(key); - holder->_modified = true; - - // also delete in JS obj - v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return; - realObject->Delete(index); - result.Set(v8::True(scope->getIsolate())); -} - -static void indexedGetRO(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) { - indexedGet(index, info); -} - -static void indexedSet(uint32_t index, - v8::Local<v8::Value> value_obj, - const v8::PropertyCallbackInfo<v8::Value>& info) { - v8::Local<v8::Value> val; - v8::ReturnValue<v8::Value> result = info.GetReturnValue(); - result.Set(val); - string key = str::stream() << index; - V8Scope* scope = getScope(info.GetIsolate()); - BSONHolder* holder = unwrapHolder(scope, info.Holder()); - if (!holder) - return; - holder->_removed.erase(key); - holder->_modified = true; - - v8::Local<v8::Object> realObject = unwrapObject(scope, info.Holder()); - if (realObject.IsEmpty()) - return; - realObject->Set(index, value_obj); - result.Set(value_obj); -} - -void NamedReadOnlySet(v8::Local<v8::String> property, - v8::Local<v8::Value> value, - const v8::PropertyCallbackInfo<v8::Value>& info) { - cout << "cannot write property " << V8String(property) << " to read-only object" << endl; - v8::ReturnValue<v8::Value> result = info.GetReturnValue(); - result.Set(value); -} - -void NamedReadOnlyDelete(v8::Local<v8::String> property, - const v8::PropertyCallbackInfo<v8::Boolean>& info) { - v8::ReturnValue<v8::Boolean> result = info.GetReturnValue(); - result.Set(v8::Boolean::New(info.GetIsolate(), false)); - cout << "cannot delete property " << V8String(property) << " from read-only object" << endl; -} - -void IndexedReadOnlySet(uint32_t index, - v8::Local<v8::Value> value, - const v8::PropertyCallbackInfo<v8::Value>& info) { - v8::Local<v8::Value> val; - v8::ReturnValue<v8::Value> result = info.GetReturnValue(); - result.Set(val); - cout << "cannot write property " << index << " to read-only array" << endl; -} - -void IndexedReadOnlyDelete(uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& info) { - v8::ReturnValue<v8::Boolean> result = info.GetReturnValue(); - result.Set(v8::Boolean::New(info.GetIsolate(), false)); - cout << "cannot delete property " << index << " from read-only array" << endl; -} - -/** - * GC Prologue and Epilogue constants (used to display description constants) - */ -struct GCPrologueState { - static const char* name; -}; -const char* GCPrologueState::name = "prologue"; -struct GCEpilogueState { - static const char* name; -}; -const char* GCEpilogueState::name = "epilogue"; - -template <typename _GCState> -void gcCallback(v8::GCType type, v8::GCCallbackFlags flags) { - if (!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())); - return; - } - - 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())); - 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(); - - // 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::EscapableHandleScope handle_scope(_isolate); - v8::TryCatch try_catch; - raw = jsSkipWhiteSpace(raw); - string code = raw; - if (!hasFunctionIdentifier(code)) { - if (code.find('\n') == string::npos && !hasJSReturn(code) && - (code.find(';') == string::npos || code.find(';') == code.size() - 1)) { - code = "return " + code; - } - code = "function(){ " + code + "}"; - } - - string fn = str::stream() << "_funcs" << functionNumber; - code = str::stream() << fn << " = " << code; - - v8::Local<v8::Script> script = v8::Script::Compile(v8StringData(code), v8StringData(fn)); - - // throw on error - checkV8ErrorState(script, try_catch); - - v8::Local<v8::Value> result = script->Run(); - - // throw on error - checkV8ErrorState(result, try_catch); - - return handle_scope.Escape(v8::Local<v8::Function>::Cast(getGlobal()->Get(v8StringData(fn)))); -} - -ScriptingFunction V8Scope::_createFunction(const char* raw, ScriptingFunction functionNumber) { - V8_SIMPLE_HEADER - v8::Local<v8::Value> ret = __createFunction(raw, functionNumber); - v8::Eternal<v8::Value> f(_isolate, ret); - uassert(10232, "not a function", ret->IsFunction()); - _funcs.push_back(f); - return functionNumber; -} - -void V8Scope::setFunction(const char* field, const char* code) { - V8_SIMPLE_HEADER - getGlobal()->ForceSet(v8StringData(field), - __createFunction(code, getFunctionCache().size() + 1)); -} - -void V8Scope::rename(const char* from, const char* to) { - V8_SIMPLE_HEADER; - v8::Local<v8::String> f = v8StringData(from); - v8::Local<v8::String> t = v8StringData(to); - v8::Local<v8::Object> global = getGlobal(); - global->ForceSet(t, global->Get(f)); - global->ForceSet(f, v8::Undefined(_isolate)); -} - -int V8Scope::invoke(ScriptingFunction func, - const BSONObj* argsObject, - const BSONObj* recv, - int timeoutMs, - bool ignoreReturn, - bool readOnlyArgs, - bool readOnlyRecv) { - V8_SIMPLE_HEADER - v8::Local<v8::Value> funcValue = _funcs[func - 1].Get(_isolate); - v8::TryCatch try_catch; - v8::Local<v8::Value> result; - - // TODO SERVER-8016: properly allocate handles on the stack - static const int MAX_ARGS = 24; - const int nargs = argsObject ? argsObject->nFields() : 0; - uassert(16862, "Too many arguments. Max is 24", nargs <= MAX_ARGS); - - v8::Local<v8::Value> args[MAX_ARGS]; - if (nargs) { - BSONObjIterator it(*argsObject); - for (int i = 0; i < nargs; i++) { - BSONElement next = it.next(); - args[i] = mongoToV8Element(next, readOnlyArgs); - } - } - - v8::Local<v8::Object> v8recv; - if (recv != 0) - v8recv = mongoToLZV8(*recv, readOnlyRecv); - else - v8recv = getGlobal(); - - if (!nativeEpilogue()) { - _error = "JavaScript execution terminated"; - 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(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::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; - - v8::Local<v8::Object> global = getGlobal(); - global->ForceSet(strLitToV8("__lastres__"), result); - - if (printResult && !result->IsUndefined()) { - // appears to only be used by shell - cout << V8String(result) << endl; - } - - return true; -} - -void V8Scope::injectNative(const char* field, NativeFunction func, void* data) { - V8_SIMPLE_HEADER // required due to public access - v8::Local<v8::Object> global = getGlobal(); - injectNative(field, func, global, data); -} - -void V8Scope::injectNative(const char* field, - NativeFunction 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; - - 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.Set(_isolate, getMongoFunctionTemplate(this, true)); - v8::Local<v8::Object> global = getGlobal(); - injectV8Function("Mongo", MongoFT(), global); - execCoreFiles(); - exec("_mongo = new Mongo();", "local connect 2", false, true, true, 0); - exec((string) "db = _mongo.getDB(\"" + dbName + "\");", - "local connect 3", - false, - true, - true, - 0); - _connectState = LOCAL; - _localDBName = dbName; - } - loadStored(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); - - wrapBSONObject(o, m, readOnly); - return o; -} - -v8::Local<v8::Value> V8Scope::mongoToV8Element(const BSONElement& elem, bool readOnly) { - v8::Local<v8::Value> argv[3]; // arguments for v8 instance constructors - v8::Local<v8::Object> instance; // instance of v8 type - uint64_t nativeUnsignedLong; // native representation of NumberLong - - switch (elem.type()) { - case mongo::Code: - return newFunction(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 v8StringData(elem.valueStringData()); - } - case mongo::jstOID: - return newId(elem.__oid()); - case mongo::NumberDouble: - case mongo::NumberInt: - return v8::Number::New(_isolate, elem.number()); - case mongo::Array: { - // NB: This comment may no longer be accurate. - // for arrays it's better to use non lazy object because: - // - the lazy array is not a true v8 array and requires some v8 src change - // for all methods to work - // - it made several tests about 1.5x slower - // - most times when an array is accessed, all its values will be used - - // It is faster to allow the v8::Array to grow than call nFields() on the array - v8::Local<v8::Array> array = v8::Array::New(_isolate); - int i = 0; - BSONForEach(subElem, elem.embeddedObject()) { - array->Set(i++, mongoToV8Element(subElem, readOnly)); - } - return array; - } - case mongo::Object: - return mongoToLZV8(elem.embeddedObject(), readOnly); - case mongo::Date: - return v8::Date::New(_isolate, static_cast<double>(elem.date.toMillisSinceEpoch())); - case mongo::Bool: - return v8::Boolean::New(_isolate, elem.boolean()); - case mongo::EOO: - case mongo::jstNULL: - case mongo::Undefined: // duplicate sm behavior - return v8::Null(_isolate); - case mongo::RegEx: { - // TODO parse into a custom type that can support any patterns and flags SERVER-9803 - v8::TryCatch tryCatch; - - v8::Local<v8::Value> args[] = {v8StringData(elem.regex()), - v8StringData(elem.regexFlags())}; - - v8::Local<v8::Value> ret = _jsRegExpConstructor.Get(_isolate)->NewInstance(2, args); - uassert(16863, - str::stream() << "Error converting " << elem.toString(false) << " in field " - << elem.fieldName() << " to a JS RegExp object: " - << toSTLString(tryCatch.Exception()), - !tryCatch.HasCaught()); - - return ret; - } - case mongo::BinData: { - int len; - const char* data = elem.binData(len); - stringstream ss; - base64::encode(ss, data, len); - argv[0] = v8::Number::New(_isolate, elem.binDataType()); - argv[1] = v8StringData(ss.str()); - return BinDataFT()->GetFunction()->NewInstance(2, argv); - } - case mongo::bsonTimestamp: { - v8::TryCatch tryCatch; - - argv[0] = v8::Number::New(_isolate, elem.timestampTime() / 1000); - argv[1] = v8::Number::New(_isolate, elem.timestampInc()); - - v8::Local<v8::Value> ret = TimestampFT()->GetFunction()->NewInstance(2, argv); - uassert(17355, - str::stream() << "Error converting " << elem.toString(false) << " in field " - << elem.fieldName() << " to a JS Timestamp object: " - << toSTLString(tryCatch.Exception()), - !tryCatch.HasCaught()); - - return ret; - } - case mongo::NumberLong: - nativeUnsignedLong = elem.numberLong(); - // values above 2^53 are not accurately represented in JS - if ((long long)nativeUnsignedLong == - (long long)(double)(long long)(nativeUnsignedLong) && - nativeUnsignedLong < 9007199254740992ULL) { - argv[0] = v8::Number::New(_isolate, (double)(long long)(nativeUnsignedLong)); - return NumberLongFT()->GetFunction()->NewInstance(1, argv); - } else { - argv[0] = v8::Number::New(_isolate, (double)(long long)(nativeUnsignedLong)); - argv[1] = v8::Integer::New(_isolate, nativeUnsignedLong >> 32); - argv[2] = v8::Integer::New( - _isolate, (unsigned long)(nativeUnsignedLong & 0x00000000ffffffff)); - return NumberLongFT()->GetFunction()->NewInstance(3, argv); - } - case mongo::MinKey: - return MinKeyFT()->GetFunction()->NewInstance(); - case mongo::MaxKey: - return MaxKeyFT()->GetFunction()->NewInstance(); - case mongo::DBRef: - argv[0] = v8StringData(elem.dbrefNS()); - argv[1] = newId(elem.dbrefOID()); - return DBPointerFT()->GetFunction()->NewInstance(2, argv); - default: - massert(16661, - str::stream() << "can't handle type: " << elem.type() << " " << elem.toString(), - false); - break; - } - return v8::Undefined(_isolate); -} - -void V8Scope::v8ToMongoNumber(BSONObjBuilder& b, - 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; - } else if (value->IsNull()) { - b.appendNull(sname); - return; - } - uasserted(16662, - str::stream() << "unable to convert JavaScript property to mongo element " << sname); -} - -BSONObj V8Scope::v8ToMongo(v8::Local<v8::Object> o, int depth) { - BSONObj originalBSON; - if (LazyBsonFT()->HasInstance(o)) { - originalBSON = unwrapBSONObj(this, o); - BSONHolder* holder = unwrapHolder(this, o); - if (holder && !holder->_modified) { - // object was not modified, use bson as is - return originalBSON; - } - } - - BSONObjBuilder b; - - // We special case the _id field in top-level objects and move it to the front. - // This matches other drivers behavior and makes finding the _id field quicker in BSON. - if (depth == 0) { - if (o->HasOwnProperty(strLitToV8("_id"))) { - v8ToMongoElement(b, "_id", o->Get(strLitToV8("_id")), 0, &originalBSON); - } - } - - v8::Local<v8::Array> names = o->GetOwnPropertyNames(); - for (unsigned int i = 0; i < names->Length(); i++) { - v8::Local<v8::String> name = names->Get(i)->ToString(); - - if (depth == 0 && name->StrictEquals(strLitToV8("_id"))) - continue; // already handled above - - V8String sname(name); - v8::Local<v8::Value> value = o->Get(name); - v8ToMongoElement(b, sname, value, depth + 1, &originalBSON); - } - - const int sizeWithEOO = b.len() + 1 /*EOO*/ - 4 /*BSONObj::Holder ref count*/; - uassert(17260, - str::stream() << "Converting from JavaScript to BSON failed: " - << "Object size " << sizeWithEOO << " exceeds limit of " - << BSONObjMaxInternalSize << " bytes.", - sizeWithEOO <= BSONObjMaxInternalSize); - - return b.obj(); // Would give an uglier error than above for oversized objects. -} - -// --- random utils ---- - -static logger::MessageLogDomain* jsPrintLogDomain; -v8::Local<v8::Value> V8Scope::Print(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - LogstreamBuilder builder(jsPrintLogDomain, getThreadName(), logger::LogSeverity::Log()); - std::ostream& ss = builder.stream(); - v8::EscapableHandleScope handle_scope(args.GetIsolate()); - bool first = true; - for (int i = 0; i < args.Length(); i++) { - if (first) - first = false; - else - ss << " "; - - if (args[i].IsEmpty()) { - // failed to get object to convert - ss << "[unknown type]"; - continue; - } - if (args[i]->IsExternal()) { - // object is External - ss << "[mongo internal]"; - continue; - } - - v8::String::Utf8Value str(args[i]); - ss << *str; - } - ss << "\n"; - return handle_scope.Escape(v8::Local<v8::Value>(v8::Undefined(scope->getIsolate()))); -} - -v8::Local<v8::Value> V8Scope::Version(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::EscapableHandleScope handle_scope(args.GetIsolate()); - return handle_scope.Escape(scope->v8StringData(v8::V8::GetVersion())); -} - -v8::Local<v8::Value> V8Scope::GCV8(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - // trigger low memory notification. for more granular control over garbage - // collection cycle, @see v8::V8::IdleNotification. - v8::V8::LowMemoryNotification(); - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> V8Scope::startCpuProfiler(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - if (args.Length() != 1 || !args[0]->IsString()) { - return v8AssertionException("startCpuProfiler takes a string argument"); - } - scope->_cpuProfiler.start(scope->_isolate, *v8::String::Utf8Value(args[0]->ToString())); - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> V8Scope::stopCpuProfiler(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - if (args.Length() != 1 || !args[0]->IsString()) { - return v8AssertionException("stopCpuProfiler takes a string argument"); - } - scope->_cpuProfiler.stop(scope->_isolate, *v8::String::Utf8Value(args[0]->ToString())); - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> V8Scope::getCpuProfile(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - if (args.Length() != 1 || !args[0]->IsString()) { - return v8AssertionException("getCpuProfile takes a string argument"); - } - return scope->mongoToLZV8( - scope->_cpuProfiler.fetch(*v8::String::Utf8Value(args[0]->ToString()))); -} - -/** - * 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; - } - - return false; -} - -MONGO_INITIALIZER(JavascriptPrintDomain)(InitializerContext*) { - jsPrintLogDomain = logger::globalLogManager()->getNamedDomain("javascriptOutput"); - return Status::OK(); -} - -} // namespace mongo diff --git a/src/mongo/scripting/engine_v8-3.25.h b/src/mongo/scripting/engine_v8-3.25.h deleted file mode 100644 index 0865ab59ac3..00000000000 --- a/src/mongo/scripting/engine_v8-3.25.h +++ /dev/null @@ -1,653 +0,0 @@ -// engine_v8.h - -/* Copyright 2014 MongoDB Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#pragma once - -#include <v8.h> -#include <set> -#include <string> -#include <vector> - -#include "mongo/base/disallow_copying.h" -#include "mongo/base/string_data.h" -#include "mongo/client/dbclientinterface.h" -#include "mongo/client/dbclientcursor.h" -#include "mongo/platform/unordered_map.h" -#include "mongo/scripting/engine.h" -#include "mongo/scripting/deadline_monitor.h" -#include "mongo/scripting/v8-3.25_profiler.h" - -/** - * V8_SIMPLE_HEADER must be placed in any function called from a public API - * that work with v8 handles (and/or must be within the V8Scope's isolate - * and context). Be sure to close the handle_scope if returning a v8::Handle! - */ -#define V8_SIMPLE_HEADER \ - v8::Locker v8lock(_isolate); /* acquire isolate lock */ \ - v8::Isolate::Scope iscope(_isolate); /* enter the isolate; exit when out of scope */ \ - v8::HandleScope handle_scope(_isolate); /* make the current scope own local */ \ - /* handles */ \ - v8::Context::Scope context_scope(getContext()); /* enter the context; exit when */ \ - /* out of scope */ - -namespace mongo { - -class V8ScriptEngine; -class V8Scope; -class BSONHolder; -class JSThreadConfig; - -typedef v8::Local<v8::Value>(*v8Function)(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - -/** - * The ObjTracker class keeps track of all weakly referenced v8 objects. This is - * required because v8 does not invoke the WeakReferenceCallback when shutting down - * the context/isolate. To track a new object, add an ObjTracker<MyObjType> member - * variable to the V8Scope (if one does not already exist for that type). Instead - * of calling v8::Persistent::MakeWeak() directly, simply invoke track() with the - * persistent handle and the pointer to be freed. - */ -template <typename _ObjType> -class ObjTracker { -public: - /** Track an object to be freed when it is no longer referenced in JavaScript. - * Return handle to object instance shared pointer. - * @param instanceHandle persistent handle to the weakly referenced object - * @param rawData pointer to the object instance - */ - v8::Local<v8::External> track(v8::Isolate* isolate, - v8::Local<v8::Value> instanceHandle, - _ObjType* instance) { - TrackedPtr* collectionHandle = new TrackedPtr(isolate, instanceHandle, instance, this); - _container.insert(collectionHandle); - collectionHandle->_instanceHandle.SetWeak(collectionHandle, deleteOnCollect); - return v8::External::New(isolate, &(collectionHandle->_objPtr)); - } - /** - * Free any remaining objects and their TrackedPtrs. Invoked when the - * V8Scope is destructed. - */ - ~ObjTracker() { - typename std::set<TrackedPtr*>::iterator it = _container.begin(); - while (it != _container.end()) { - delete *it; - _container.erase(it++); - } - } - -private: - /** - * Simple struct which contains a pointer to the tracked object, and a pointer - * to the ObjTracker which owns it. This is the argument supplied to v8's - * WeakReferenceCallback and MakeWeak(). - */ - struct TrackedPtr { - public: - TrackedPtr(v8::Isolate* isolate, - v8::Local<v8::Value>& instanceHandle, - _ObjType* instance, - ObjTracker<_ObjType>* tracker) - : _instanceHandle(isolate, instanceHandle), _objPtr(instance), _tracker(tracker) {} - v8::Persistent<v8::Value> _instanceHandle; - std::shared_ptr<_ObjType> _objPtr; - ObjTracker<_ObjType>* _tracker; - }; - - /** - * v8 callback for weak persistent handles that have been marked for removal by the - * garbage collector. Signature conforms to v8's WeakReferenceCallback. - * @param data Weak callback data. Contains pointer to the TrackedPtr instance. - */ - static void deleteOnCollect(const v8::WeakCallbackData<v8::Value, TrackedPtr>& data) { - std::unique_ptr<TrackedPtr> trackedPtr(data.GetParameter()); - invariant(trackedPtr.get()); - - trackedPtr->_tracker->_container.erase(trackedPtr.get()); - trackedPtr->_instanceHandle.Reset(); - } - - // container for all TrackedPtrs created by this ObjTracker instance - std::set<TrackedPtr*> _container; -}; - -/** - * A V8Scope represents a unit of javascript execution environment; specifically a single - * isolate and a single context executing in a single mongo thread. A V8Scope can be reused - * in another thread only after reset() has been called. - * - * NB: - * - v8 objects/handles/etc. cannot be shared between V8Scopes - * - in mongod, each scope is associated with an opId (for KillOp support) - * - any public functions that call the v8 API should use a V8_SIMPLE_HEADER - * - the caller of any public function that returns a v8 type or has a v8 handle argument - * must enter the isolate, context, and set up the appropriate handle scope - */ -class V8Scope : public Scope { -public: - V8Scope(V8ScriptEngine* engine); - ~V8Scope(); - - virtual void init(const BSONObj* data); - - /** - * Reset the state of this scope for use by another thread or operation - */ - virtual void reset(); - - /** - * Terminate this scope - */ - virtual void kill(); - - /** check if there is a pending killOp request */ - bool isKillPending() const; - - /** - * Obtains the operation context associated with this Scope, so it can be given to the - * DBDirectClient used by the V8 engine's connection. Only needed for dbEval. - */ - OperationContext* getOpContext() const; - - /** - * Register this scope with the mongo op id. If executing outside the - * context of a mongo operation (e.g. from the shell), killOp will not - * be supported. - */ - virtual void registerOperation(OperationContext* txn); - - /** - * Unregister this scope with the mongo op id. - */ - virtual void unregisterOperation(); - - /** - * Connect to a local database, create a Mongo object instance, and load any - * server-side js into the global object - */ - virtual void localConnectForDbEval(OperationContext* txn, const char* dbName); - - virtual void externalSetup(); - - virtual void installDBAccess(); - - virtual void installBSONTypes(); - - virtual std::string getError() { - return _error; - } - - virtual bool hasOutOfMemoryException(); - - /** - * Run the garbage collector on this scope (native function). @see GCV8 for the - * javascript binding version. - */ - void gc(); - - /** - * get a global property. caller must set up the v8 state. - */ - v8::Local<v8::Value> get(const char* field); - - virtual double getNumber(const char* field); - virtual int getNumberInt(const char* field); - virtual long long getNumberLongLong(const char* field); - virtual std::string getString(const char* field); - virtual bool getBoolean(const char* field); - virtual BSONObj getObject(const char* field); - - virtual void setNumber(const char* field, double val); - virtual void setString(const char* field, StringData val); - virtual void setBoolean(const char* field, bool val); - virtual void setElement(const char* field, const BSONElement& e); - virtual void setObject(const char* field, const BSONObj& obj, bool readOnly); - virtual void setFunction(const char* field, const char* code); - - virtual int type(const char* field); - - virtual void rename(const char* from, const char* to); - - virtual int invoke(ScriptingFunction func, - const BSONObj* args, - const BSONObj* recv, - int timeoutMs = 0, - bool ignoreReturn = false, - bool readOnlyArgs = false, - bool readOnlyRecv = false); - - virtual bool exec(StringData code, - const std::string& name, - bool printResult, - bool reportError, - bool assertOnError, - int timeoutMs); - - // functions to create v8 object and function templates - virtual void injectNative(const char* field, NativeFunction func, void* data = 0); - void injectNative(const char* field, - NativeFunction func, - v8::Local<v8::Object>& obj, - void* data = 0); - - // These functions inject a function (either an unwrapped function pointer or a pre-wrapped - // FunctionTemplate) into the provided object. If no object is provided, the function will - // be injected at global scope. These functions take care of setting the function and class - // name on the returned FunctionTemplate. - v8::Local<v8::FunctionTemplate> injectV8Function(const char* name, v8Function func); - v8::Local<v8::FunctionTemplate> injectV8Function(const char* name, - v8Function func, - v8::Local<v8::Object> obj); - v8::Local<v8::FunctionTemplate> injectV8Function(const char* name, - v8::Local<v8::FunctionTemplate> ft, - v8::Local<v8::Object> obj); - - // Injects a method into the provided prototype - v8::Local<v8::FunctionTemplate> injectV8Method(const char* name, - v8Function func, - v8::Local<v8::ObjectTemplate>& proto); - v8::Local<v8::FunctionTemplate> createV8Function(v8Function func); - virtual ScriptingFunction _createFunction(const char* code, - ScriptingFunction functionNumber = 0); - v8::Local<v8::Function> __createFunction(const char* code, - ScriptingFunction functionNumber = 0); - - /** - * Convert BSON types to v8 Javascript types - */ - v8::Local<v8::Object> mongoToLZV8(const mongo::BSONObj& m, bool readOnly = false); - v8::Local<v8::Value> mongoToV8Element(const BSONElement& f, bool readOnly = false); - - /** - * Convert v8 Javascript types to BSON types - */ - mongo::BSONObj v8ToMongo(v8::Local<v8::Object> obj, int depth = 0); - void v8ToMongoElement(BSONObjBuilder& b, - StringData sname, - v8::Local<v8::Value> value, - int depth = 0, - BSONObj* originalParent = 0); - void v8ToMongoObject(BSONObjBuilder& b, - StringData sname, - v8::Local<v8::Value> value, - int depth, - BSONObj* originalParent); - void v8ToMongoNumber(BSONObjBuilder& b, - StringData elementName, - v8::Local<v8::Number> value, - BSONObj* originalParent); - void v8ToMongoRegex(BSONObjBuilder& b, StringData elementName, v8::Local<v8::RegExp> v8Regex); - void v8ToMongoDBRef(BSONObjBuilder& b, StringData elementName, v8::Local<v8::Object> obj); - void v8ToMongoBinData(BSONObjBuilder& b, StringData elementName, v8::Local<v8::Object> obj); - OID v8ToMongoObjectID(v8::Local<v8::Object> obj); - - v8::Local<v8::Value> newId(const OID& id); - - /** - * Convert a JavaScript exception to a stl string. Requires - * access to the V8Scope instance to report source context information. - */ - std::string v8ExceptionToSTLString(const v8::TryCatch* try_catch); - - /** - * Create a V8 string with a local handle - */ - inline v8::Local<v8::String> v8StringData(StringData str) { - return v8::String::NewFromUtf8( - _isolate, str.rawData(), v8::String::kNormalString, str.size()); - } - - /** - * Get the isolate this scope belongs to (can be called from any thread, but v8 requires - * the new thread enter the isolate and context. Only one thread can enter the isolate. - */ - v8::Isolate* getIsolate() const { - return _isolate; - } - - /** - * Get the JS context this scope executes within. - */ - v8::Local<v8::Context> getContext() { - return _context.Get(_isolate); - } - - /** - * Get the global JS object - */ - v8::Local<v8::Object> getGlobal() { - return _global.Get(_isolate); - } - - ObjTracker<BSONHolder> bsonHolderTracker; - ObjTracker<DBClientBase> dbClientBaseTracker; - // Track both cursor and connection. - // This ensures the connection outlives the cursor. - struct DBConnectionAndCursor { - std::shared_ptr<DBClientBase> conn; - std::shared_ptr<DBClientCursor> cursor; - DBConnectionAndCursor(std::shared_ptr<DBClientBase> conn, - std::shared_ptr<DBClientCursor> cursor) - : conn(conn), cursor(cursor) {} - }; - ObjTracker<DBConnectionAndCursor> dbConnectionAndCursor; - ObjTracker<JSThreadConfig> jsThreadConfigTracker; - - // These are all named after the JS constructor name + FT - v8::Local<v8::FunctionTemplate> ObjectIdFT() { - return _ObjectIdFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> DBRefFT() { - return _DBRefFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> DBPointerFT() { - return _DBPointerFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> BinDataFT() { - return _BinDataFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> NumberLongFT() { - return _NumberLongFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> NumberIntFT() { - return _NumberIntFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> TimestampFT() { - return _TimestampFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> MinKeyFT() { - return _MinKeyFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> MaxKeyFT() { - return _MaxKeyFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> MongoFT() { - return _MongoFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> DBFT() { - return _DBFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> DBCollectionFT() { - return _DBCollectionFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> DBQueryFT() { - return _DBQueryFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> InternalCursorFT() { - return _InternalCursorFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> LazyBsonFT() { - return _LazyBsonFT.Get(_isolate); - } - v8::Local<v8::FunctionTemplate> ROBsonFT() { - return _ROBsonFT.Get(_isolate); - } - - template <size_t N> - v8::Local<v8::String> strLitToV8(const char(&str)[N]) { - // Note that _strLitMap is keyed on string pointer not string - // value. This is OK because each string literal has a constant - // pointer for the program's lifetime. This works best if (but does - // not require) the linker interns all string literals giving - // identical strings used in different places the same pointer. - - StrLitMap::iterator it = _strLitMap.find(str); - if (it != _strLitMap.end()) - return it->second.Get(_isolate); - - StringData sd(str, StringData::LiteralTag()); - v8::Local<v8::String> v8Str = v8StringData(sd); - - // Eternal should last as long as V8Scope exists. - _strLitMap[str].Set(_isolate, v8Str); - - return v8Str; - } - -private: - /** - * Recursion limit when converting from JS objects to BSON. - */ - static const int objectDepthLimit = 150; - - /** - * Attach data to obj such that the data has the same lifetime as the Object obj points to. - * obj must have been created by either LazyBsonFT or ROBsonFT. - */ - void wrapBSONObject(v8::Local<v8::Object> obj, BSONObj data, bool readOnly); - - /** - * Trampoline to call a c++ function with a specific signature (V8Scope*, - * v8::FunctionCallbackInfo<v8::Value>&). - * Handles interruption, exceptions, etc. - */ - static void v8Callback(const v8::FunctionCallbackInfo<v8::Value>& args); - - /** - * Interpreter agnostic 'Native Callback' trampoline. Note this is only called - * from v8Callback(). - */ - static v8::Local<v8::Value> nativeCallback(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - - /** - * v8-specific implementations of basic global functions - */ - static v8::Local<v8::Value> load(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - static v8::Local<v8::Value> Print(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - static v8::Local<v8::Value> Version(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - static v8::Local<v8::Value> GCV8(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - - static v8::Local<v8::Value> startCpuProfiler(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - static v8::Local<v8::Value> stopCpuProfiler(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - static v8::Local<v8::Value> getCpuProfile(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - - /** Signal that this scope has entered a native (C++) execution context. - * @return false if execution has been interrupted - */ - bool nativePrologue(); - - /** Signal that this scope has completed native execution and is returning to v8. - * @return false if execution has been interrupted - */ - bool nativeEpilogue(); - - /** - * Create a new function; primarily used for BSON/V8 conversion. - */ - v8::Local<v8::Value> newFunction(StringData code); - - template <typename _HandleType> - bool checkV8ErrorState(const _HandleType& resultHandle, - const v8::TryCatch& try_catch, - bool reportError = true, - bool assertOnError = true); - - V8ScriptEngine* _engine; - - v8::Eternal<v8::Context> _context; - v8::Eternal<v8::Object> _global; - std::string _error; - std::vector<v8::Eternal<v8::Value>> _funcs; - - enum ConnectState { NOT, LOCAL, EXTERNAL }; - ConnectState _connectState; - - // These are all named after the JS constructor name + FT - v8::Eternal<v8::FunctionTemplate> _ObjectIdFT; - v8::Eternal<v8::FunctionTemplate> _DBRefFT; - v8::Eternal<v8::FunctionTemplate> _DBPointerFT; - v8::Eternal<v8::FunctionTemplate> _BinDataFT; - v8::Eternal<v8::FunctionTemplate> _NumberLongFT; - v8::Eternal<v8::FunctionTemplate> _NumberIntFT; - v8::Eternal<v8::FunctionTemplate> _TimestampFT; - v8::Eternal<v8::FunctionTemplate> _MinKeyFT; - v8::Eternal<v8::FunctionTemplate> _MaxKeyFT; - v8::Eternal<v8::FunctionTemplate> _MongoFT; - v8::Eternal<v8::FunctionTemplate> _DBFT; - v8::Eternal<v8::FunctionTemplate> _DBCollectionFT; - v8::Eternal<v8::FunctionTemplate> _DBQueryFT; - v8::Eternal<v8::FunctionTemplate> _InternalCursorFT; - v8::Eternal<v8::FunctionTemplate> _LazyBsonFT; - v8::Eternal<v8::FunctionTemplate> _ROBsonFT; - - v8::Eternal<v8::Function> _jsRegExpConstructor; - - /// Like v8::Isolate* but calls Dispose() in destructor. - class IsolateHolder { - MONGO_DISALLOW_COPYING(IsolateHolder); - - public: - IsolateHolder() : _isolate(NULL) {} - ~IsolateHolder() { - if (_isolate) { - _isolate->Dispose(); - _isolate = NULL; - } - } - - void set(v8::Isolate* isolate) { - fassert(17184, !_isolate); - _isolate = isolate; - } - - v8::Isolate* operator->() const { - return _isolate; - }; - operator v8::Isolate*() const { - return _isolate; - }; - - private: - v8::Isolate* _isolate; - }; - - IsolateHolder _isolate; // NOTE: this must be destructed before the ObjTrackers - - V8CpuProfiler _cpuProfiler; - - // See comments in strLitToV8 - typedef unordered_map<const char*, v8::Eternal<v8::String>> StrLitMap; - StrLitMap _strLitMap; - - stdx::mutex _interruptLock; // protects interruption-related flags - bool _inNativeExecution; // protected by _interruptLock - bool _pendingKill; // protected by _interruptLock - unsigned int _opId; // op id for this scope - OperationContext* _opCtx; // Op context for DbEval -}; - -/// Helper to extract V8Scope for an Isolate -inline V8Scope* getScope(v8::Isolate* isolate) { - invariant(isolate); - invariant(isolate->GetNumberOfDataSlots() >= 1U); - uint32_t slot = 0; - return static_cast<V8Scope*>(isolate->GetData(slot)); -} - -class V8ScriptEngine : public ScriptEngine { -public: - V8ScriptEngine(); - virtual ~V8ScriptEngine(); - virtual Scope* createScope() { - return new V8Scope(this); - } - virtual void runTest() {} - bool utf8Ok() const { - return true; - } - - /** - * Interrupt a single active v8 execution context - * NB: To interrupt a context, we must acquire the following locks (in order): - * - mutex to protect the the map of all scopes (_globalInterruptLock) - * - mutex to protect the scope that's being interrupted (_interruptLock) - * The scope will be removed from the map upon destruction, and the op id - * will be updated if the scope is ever reused from a pool. - */ - virtual void interrupt(unsigned opId); - - /** - * Interrupt all v8 contexts (and isolates). @see interrupt(). - */ - virtual void interruptAll(); - -private: - friend class V8Scope; - - std::string printKnownOps_inlock(); - - /** - * Get the deadline monitor instance for the v8 ScriptEngine - */ - DeadlineMonitor<V8Scope>* getDeadlineMonitor() { - return &_deadlineMonitor; - } - - typedef std::map<unsigned, V8Scope*> OpIdToScopeMap; - stdx::mutex _globalInterruptLock; // protects map of all operation ids -> scope - OpIdToScopeMap _opToScopeMap; // map of mongo op ids to scopes (protected by - // _globalInterruptLock). - DeadlineMonitor<V8Scope> _deadlineMonitor; -}; - -class BSONHolder { - MONGO_DISALLOW_COPYING(BSONHolder); - -public: - BSONHolder(V8Scope* scope, BSONObj obj, bool readOnly) - : _scope(scope), _obj(obj.getOwned()), _modified(false), _readOnly(readOnly) { - invariant(scope); - if (_scope->getIsolate()) { - // give hint v8's GC - _scope->getIsolate()->AdjustAmountOfExternalAllocatedMemory(_obj.objsize()); - } - } - ~BSONHolder() { - if (_scope->getIsolate()) { - // if v8 is still up, send hint to GC - _scope->getIsolate()->AdjustAmountOfExternalAllocatedMemory(-_obj.objsize()); - } - } - const V8Scope* _scope; - const BSONObj _obj; - bool _modified; - const bool _readOnly; - std::set<std::string> _removed; -}; - -extern ScriptEngine* globalScriptEngine; -} 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 diff --git a/src/mongo/scripting/engine_v8.h b/src/mongo/scripting/engine_v8.h deleted file mode 100644 index 2cf5b218e3c..00000000000 --- a/src/mongo/scripting/engine_v8.h +++ /dev/null @@ -1,629 +0,0 @@ -// engine_v8.h - -/* Copyright 2009 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <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. - */ - -#pragma once - -#include <v8.h> -#include <vector> - -#include "mongo/base/disallow_copying.h" -#include "mongo/base/string_data.h" -#include "mongo/client/dbclientinterface.h" -#include "mongo/client/dbclientcursor.h" -#include "mongo/platform/unordered_map.h" -#include "mongo/scripting/engine.h" -#include "mongo/scripting/deadline_monitor.h" -#include "mongo/scripting/v8_profiler.h" - -/** - * V8_SIMPLE_HEADER must be placed in any function called from a public API - * that work with v8 handles (and/or must be within the V8Scope's isolate - * and context). Be sure to close the handle_scope if returning a v8::Handle! - */ -#define V8_SIMPLE_HEADER \ - v8::Locker v8lock(_isolate); /* acquire isolate lock */ \ - v8::Isolate::Scope iscope(_isolate); /* enter the isolate; exit when out of scope */ \ - v8::HandleScope handle_scope; /* make the current scope own local handles */ \ - v8::Context::Scope context_scope(_context); /* enter the context; exit when out of scope */ - -namespace mongo { - -class V8ScriptEngine; -class V8Scope; -class BSONHolder; -class JSThreadConfig; - -typedef v8::Handle<v8::Value>(*v8Function)(V8Scope* scope, const v8::Arguments& args); - -/** - * The ObjTracker class keeps track of all weakly referenced v8 objects. This is - * required because v8 does not invoke the WeakReferenceCallback when shutting down - * the context/isolate. To track a new object, add an ObjTracker<MyObjType> member - * variable to the V8Scope (if one does not already exist for that type). Instead - * of calling v8::Persistent::MakeWeak() directly, simply invoke track() with the - * persistent handle and the pointer to be freed. - */ -template <typename _ObjType> -class ObjTracker { -public: - /** Track an object to be freed when it is no longer referenced in JavaScript. - * Return handle to object instance shared pointer. - * @param instanceHandle persistent handle to the weakly referenced object - * @param rawData pointer to the object instance - */ - v8::Local<v8::External> track(v8::Persistent<v8::Value> instanceHandle, _ObjType* instance) { - TrackedPtr* collectionHandle = new TrackedPtr(instance, this); - _container.insert(collectionHandle); - instanceHandle.MakeWeak(collectionHandle, deleteOnCollect); - return v8::External::New(&(collectionHandle->_objPtr)); - } - /** - * Free any remaining objects and their TrackedPtrs. Invoked when the - * V8Scope is destructed. - */ - ~ObjTracker() { - typename std::set<TrackedPtr*>::iterator it = _container.begin(); - while (it != _container.end()) { - delete *it; - _container.erase(it++); - } - } - -private: - /** - * Simple struct which contains a pointer to the tracked object, and a pointer - * to the ObjTracker which owns it. This is the argument supplied to v8's - * WeakReferenceCallback and MakeWeak(). - */ - struct TrackedPtr { - public: - TrackedPtr(_ObjType* instance, ObjTracker<_ObjType>* tracker) - : _objPtr(instance), _tracker(tracker) {} - std::shared_ptr<_ObjType> _objPtr; - ObjTracker<_ObjType>* _tracker; - }; - - /** - * v8 callback for weak persistent handles that have been marked for removal by the - * garbage collector. Signature conforms to v8's WeakReferenceCallback. - * @param instanceHandle persistent handle to the weakly referenced object - * @param rawData pointer to the TrackedPtr instance - */ - static void deleteOnCollect(v8::Persistent<v8::Value> instanceHandle, void* rawData) { - TrackedPtr* trackedPtr = static_cast<TrackedPtr*>(rawData); - trackedPtr->_tracker->_container.erase(trackedPtr); - delete trackedPtr; - instanceHandle.Dispose(); - } - - // container for all TrackedPtrs created by this ObjTracker instance - std::set<TrackedPtr*> _container; -}; - -/** - * A V8Scope represents a unit of javascript execution environment; specifically a single - * isolate and a single context executing in a single mongo thread. A V8Scope can be reused - * in another thread only after reset() has been called. - * - * NB: - * - v8 objects/handles/etc. cannot be shared between V8Scopes - * - in mongod, each scope is associated with an opId (for KillOp support) - * - any public functions that call the v8 API should use a V8_SIMPLE_HEADER - * - the caller of any public function that returns a v8 type or has a v8 handle argument - * must enter the isolate, context, and set up the appropriate handle scope - */ -class V8Scope : public Scope { -public: - V8Scope(V8ScriptEngine* engine); - ~V8Scope(); - - virtual void init(const BSONObj* data); - - /** - * Reset the state of this scope for use by another thread or operation - */ - virtual void reset(); - - /** - * Terminate this scope - */ - virtual void kill(); - - /** check if there is a pending killOp request */ - bool isKillPending() const; - - /** - * Register this scope with the mongo op id. If executing outside the - * context of a mongo operation (e.g. from the shell), killOp will not - * be supported. - */ - virtual void registerOperation(OperationContext* txn); - - /** - * Unregister this scope with the mongo op id. - */ - virtual void unregisterOperation(); - - /** - * Obtains the operation context associated with this Scope, so it can be given to the - * DBDirectClient used by the V8 engine's connection. Only needed for dbEval. - */ - OperationContext* getOpContext() const; - - /** - * Connect to a local database, create a Mongo object instance, and load any - * server-side js into the global object - */ - virtual void localConnectForDbEval(OperationContext* txn, const char* dbName); - - virtual void externalSetup(); - - virtual void installDBAccess(); - - virtual void installBSONTypes(); - - virtual std::string getError() { - return _error; - } - - virtual bool hasOutOfMemoryException(); - - /** - * Run the garbage collector on this scope (native function). @see GCV8 for the - * javascript binding version. - */ - void gc(); - - /** - * get a global property. caller must set up the v8 state. - */ - v8::Handle<v8::Value> get(const char* field); - - virtual double getNumber(const char* field); - virtual int getNumberInt(const char* field); - virtual long long getNumberLongLong(const char* field); - virtual Decimal128 getNumberDecimal(const char* field); - virtual std::string getString(const char* field); - virtual bool getBoolean(const char* field); - virtual BSONObj getObject(const char* field); - - virtual void setNumber(const char* field, double val); - virtual void setString(const char* field, StringData val); - virtual void setBoolean(const char* field, bool val); - virtual void setElement(const char* field, const BSONElement& e); - virtual void setObject(const char* field, const BSONObj& obj, bool readOnly); - virtual void setFunction(const char* field, const char* code); - - virtual int type(const char* field); - - virtual void rename(const char* from, const char* to); - - virtual int invoke(ScriptingFunction func, - const BSONObj* args, - const BSONObj* recv, - int timeoutMs = 0, - bool ignoreReturn = false, - bool readOnlyArgs = false, - bool readOnlyRecv = false); - - virtual bool exec(StringData code, - const std::string& name, - bool printResult, - bool reportError, - bool assertOnError, - int timeoutMs); - - // functions to create v8 object and function templates - virtual void injectNative(const char* field, NativeFunction func, void* data = 0); - void injectNative(const char* field, - NativeFunction func, - v8::Handle<v8::Object>& obj, - void* data = 0); - - // These functions inject a function (either an unwrapped function pointer or a pre-wrapped - // FunctionTemplate) into the provided object. If no object is provided, the function will - // be injected at global scope. These functions take care of setting the function and class - // name on the returned FunctionTemplate. - v8::Handle<v8::FunctionTemplate> injectV8Function(const char* name, v8Function func); - v8::Handle<v8::FunctionTemplate> injectV8Function(const char* name, - v8Function func, - v8::Handle<v8::Object>& obj); - v8::Handle<v8::FunctionTemplate> injectV8Function(const char* name, - v8::Handle<v8::FunctionTemplate> ft, - v8::Handle<v8::Object>& obj); - - // Injects a method into the provided prototype - v8::Handle<v8::FunctionTemplate> injectV8Method(const char* name, - v8Function func, - v8::Handle<v8::ObjectTemplate>& proto); - v8::Handle<v8::FunctionTemplate> createV8Function(v8Function func); - virtual ScriptingFunction _createFunction(const char* code, - ScriptingFunction functionNumber = 0); - v8::Local<v8::Function> __createFunction(const char* code, - ScriptingFunction functionNumber = 0); - - /** - * Convert BSON types to v8 Javascript types - */ - v8::Handle<v8::Object> mongoToLZV8(const mongo::BSONObj& m, bool readOnly = false); - v8::Handle<v8::Value> mongoToV8Element(const BSONElement& f, bool readOnly = false); - - /** - * Convert v8 Javascript types to BSON types - */ - mongo::BSONObj v8ToMongo(v8::Handle<v8::Object> obj, int depth = 0); - void v8ToMongoElement(BSONObjBuilder& b, - StringData sname, - v8::Handle<v8::Value> value, - int depth = 0, - BSONObj* originalParent = 0); - void v8ToMongoObject(BSONObjBuilder& b, - StringData sname, - v8::Handle<v8::Value> value, - int depth, - BSONObj* originalParent); - void v8ToMongoNumber(BSONObjBuilder& b, - StringData elementName, - v8::Handle<v8::Number> value, - BSONObj* originalParent); - void v8ToMongoRegex(BSONObjBuilder& b, StringData elementName, v8::Handle<v8::RegExp> v8Regex); - void v8ToMongoDBRef(BSONObjBuilder& b, StringData elementName, v8::Handle<v8::Object> obj); - void v8ToMongoBinData(BSONObjBuilder& b, StringData elementName, v8::Handle<v8::Object> obj); - OID v8ToMongoObjectID(v8::Handle<v8::Object> obj); - - v8::Local<v8::Value> newId(const OID& id); - - /** - * Convert a JavaScript exception to a stl string. Requires - * access to the V8Scope instance to report source context information. - */ - std::string v8ExceptionToSTLString(const v8::TryCatch* try_catch); - - /** - * Create a V8 std::string with a local handle - */ - static inline v8::Handle<v8::String> v8StringData(StringData str) { - return v8::String::New(str.rawData(), str.size()); - } - - /** - * Get the isolate this scope belongs to (can be called from any thread, but v8 requires - * the new thread enter the isolate and context. Only one thread can enter the isolate. - */ - v8::Isolate* getIsolate() { - return _isolate; - } - - /** - * Get the JS context this scope executes within. - */ - v8::Persistent<v8::Context> getContext() { - return _context; - } - - /** - * Get the global JS object - */ - v8::Persistent<v8::Object> getGlobal() { - return _global; - } - - ObjTracker<BSONHolder> bsonHolderTracker; - ObjTracker<DBClientBase> dbClientBaseTracker; - // Track both cursor and connection. - // This ensures the connection outlives the cursor. - struct DBConnectionAndCursor { - std::shared_ptr<DBClientBase> conn; - std::shared_ptr<DBClientCursor> cursor; - DBConnectionAndCursor(std::shared_ptr<DBClientBase> conn, - std::shared_ptr<DBClientCursor> cursor) - : conn(conn), cursor(cursor) {} - }; - ObjTracker<DBConnectionAndCursor> dbConnectionAndCursor; - ObjTracker<JSThreadConfig> jsThreadConfigTracker; - - // These are all named after the JS constructor name + FT - v8::Handle<v8::FunctionTemplate> ObjectIdFT() const { - return _ObjectIdFT; - } - v8::Handle<v8::FunctionTemplate> DBRefFT() const { - return _DBRefFT; - } - v8::Handle<v8::FunctionTemplate> DBPointerFT() const { - return _DBPointerFT; - } - v8::Handle<v8::FunctionTemplate> BinDataFT() const { - return _BinDataFT; - } - v8::Handle<v8::FunctionTemplate> NumberLongFT() const { - return _NumberLongFT; - } - v8::Handle<v8::FunctionTemplate> NumberIntFT() const { - return _NumberIntFT; - } - v8::Handle<v8::FunctionTemplate> NumberDecimalFT() const { - return _NumberDecimalFT; - } - v8::Handle<v8::FunctionTemplate> TimestampFT() const { - return _TimestampFT; - } - v8::Handle<v8::FunctionTemplate> MinKeyFT() const { - return _MinKeyFT; - } - v8::Handle<v8::FunctionTemplate> MaxKeyFT() const { - return _MaxKeyFT; - } - v8::Handle<v8::FunctionTemplate> MongoFT() const { - return _MongoFT; - } - v8::Handle<v8::FunctionTemplate> DBFT() const { - return _DBFT; - } - v8::Handle<v8::FunctionTemplate> DBCollectionFT() const { - return _DBCollectionFT; - } - v8::Handle<v8::FunctionTemplate> DBQueryFT() const { - return _DBQueryFT; - } - v8::Handle<v8::FunctionTemplate> InternalCursorFT() const { - return _InternalCursorFT; - } - v8::Handle<v8::FunctionTemplate> LazyBsonFT() const { - return _LazyBsonFT; - } - v8::Handle<v8::FunctionTemplate> ROBsonFT() const { - return _ROBsonFT; - } - - template <size_t N> - v8::Handle<v8::String> strLitToV8(const char(&str)[N]) { - // Note that _strLitMap is keyed on std::string pointer not string - // value. This is OK because each std::string literal has a constant - // pointer for the program's lifetime. This works best if (but does - // not require) the linker interns all std::string literals giving - // identical strings used in different places the same pointer. - - StrLitMap::iterator it = _strLitMap.find(str); - if (it != _strLitMap.end()) - return it->second; - - StringData sd(str, StringData::LiteralTag()); - v8::Handle<v8::String> v8Str = v8StringData(sd); - - // We never need to Dispose since this should last as long as V8Scope exists - _strLitMap[str] = v8::Persistent<v8::String>::New(v8Str); - - return v8Str; - } - -private: - /** - * Recursion limit when converting from JS objects to BSON. - */ - static const int objectDepthLimit = 150; - - /** - * Attach data to obj such that the data has the same lifetime as the Object obj points to. - * obj must have been created by either LazyBsonFT or ROBsonFT. - */ - void wrapBSONObject(v8::Handle<v8::Object> obj, BSONObj data, bool readOnly); - - /** - * Trampoline to call a c++ function with a specific signature (V8Scope*, v8::Arguments&). - * Handles interruption, exceptions, etc. - */ - static v8::Handle<v8::Value> v8Callback(const v8::Arguments& args); - - /** - * Interpreter agnostic 'Native Callback' trampoline. Note this is only called - * from v8Callback(). - */ - static v8::Handle<v8::Value> nativeCallback(V8Scope* scope, const v8::Arguments& args); - - /** - * v8-specific implementations of basic global functions - */ - static v8::Handle<v8::Value> load(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> Print(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> Version(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> GCV8(V8Scope* scope, const v8::Arguments& args); - - static v8::Handle<v8::Value> startCpuProfiler(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> stopCpuProfiler(V8Scope* scope, const v8::Arguments& args); - static v8::Handle<v8::Value> getCpuProfile(V8Scope* scope, const v8::Arguments& args); - - /** Signal that this scope has entered a native (C++) execution context. - * @return false if execution has been interrupted - */ - bool nativePrologue(); - - /** Signal that this scope has completed native execution and is returning to v8. - * @return false if execution has been interrupted - */ - bool nativeEpilogue(); - - /** - * Create a new function; primarily used for BSON/V8 conversion. - */ - v8::Local<v8::Value> newFunction(StringData code); - - template <typename _HandleType> - bool checkV8ErrorState(const _HandleType& resultHandle, - const v8::TryCatch& try_catch, - bool reportError = true, - bool assertOnError = true); - - V8ScriptEngine* _engine; - - v8::Persistent<v8::Context> _context; - v8::Persistent<v8::Object> _global; - std::string _error; - std::vector<v8::Persistent<v8::Value>> _funcs; - - enum ConnectState { NOT, LOCAL, EXTERNAL }; - ConnectState _connectState; - - // These are all named after the JS constructor name + FT - v8::Persistent<v8::FunctionTemplate> _ObjectIdFT; - v8::Persistent<v8::FunctionTemplate> _DBRefFT; - v8::Persistent<v8::FunctionTemplate> _DBPointerFT; - v8::Persistent<v8::FunctionTemplate> _BinDataFT; - v8::Persistent<v8::FunctionTemplate> _NumberLongFT; - v8::Persistent<v8::FunctionTemplate> _NumberIntFT; - v8::Persistent<v8::FunctionTemplate> _NumberDecimalFT; - v8::Persistent<v8::FunctionTemplate> _TimestampFT; - v8::Persistent<v8::FunctionTemplate> _MinKeyFT; - v8::Persistent<v8::FunctionTemplate> _MaxKeyFT; - v8::Persistent<v8::FunctionTemplate> _MongoFT; - v8::Persistent<v8::FunctionTemplate> _DBFT; - v8::Persistent<v8::FunctionTemplate> _DBCollectionFT; - v8::Persistent<v8::FunctionTemplate> _DBQueryFT; - v8::Persistent<v8::FunctionTemplate> _InternalCursorFT; - v8::Persistent<v8::FunctionTemplate> _LazyBsonFT; - v8::Persistent<v8::FunctionTemplate> _ROBsonFT; - - v8::Persistent<v8::Function> _jsRegExpConstructor; - - /// Like v8::Isolate* but calls Dispose() in destructor. - class IsolateHolder { - MONGO_DISALLOW_COPYING(IsolateHolder); - - public: - IsolateHolder() : _isolate(NULL) {} - ~IsolateHolder() { - if (_isolate) { - _isolate->Dispose(); - _isolate = NULL; - } - } - - void set(v8::Isolate* isolate) { - fassert(17184, !_isolate); - _isolate = isolate; - } - - v8::Isolate* operator->() const { - return _isolate; - }; - operator v8::Isolate*() const { - return _isolate; - }; - - private: - v8::Isolate* _isolate; - }; - - IsolateHolder _isolate; // NOTE: this must be destructed before the ObjTrackers - - V8CpuProfiler _cpuProfiler; - - // See comments in strLitToV8 - typedef unordered_map<const char*, v8::Handle<v8::String>> StrLitMap; - StrLitMap _strLitMap; - - stdx::mutex _interruptLock; // protects interruption-related flags - bool _inNativeExecution; // protected by _interruptLock - bool _pendingKill; // protected by _interruptLock - unsigned int _opId; // op id for this scope - OperationContext* _opCtx; // Op context for DbEval -}; - -/// Helper to extract V8Scope for an Isolate -inline V8Scope* getScope(v8::Isolate* isolate) { - return static_cast<V8Scope*>(isolate->GetData()); -} - -class V8ScriptEngine : public ScriptEngine { -public: - V8ScriptEngine(); - virtual ~V8ScriptEngine(); - virtual Scope* createScope() { - return new V8Scope(this); - } - virtual void runTest() {} - bool utf8Ok() const { - return true; - } - - /** - * Interrupt a single active v8 execution context - * NB: To interrupt a context, we must acquire the following locks (in order): - * - mutex to protect the the map of all scopes (_globalInterruptLock) - * - mutex to protect the scope that's being interrupted (_interruptLock) - * The scope will be removed from the map upon destruction, and the op id - * will be updated if the scope is ever reused from a pool. - */ - virtual void interrupt(unsigned opId); - - /** - * Interrupt all v8 contexts (and isolates). @see interrupt(). - */ - virtual void interruptAll(); - -private: - friend class V8Scope; - - std::string printKnownOps_inlock(); - - /** - * Get the deadline monitor instance for the v8 ScriptEngine - */ - DeadlineMonitor<V8Scope>* getDeadlineMonitor() { - return &_deadlineMonitor; - } - - typedef std::map<unsigned, V8Scope*> OpIdToScopeMap; - stdx::mutex _globalInterruptLock; // protects map of all operation ids -> scope - OpIdToScopeMap _opToScopeMap; // map of mongo op ids to scopes (protected by - // _globalInterruptLock). - DeadlineMonitor<V8Scope> _deadlineMonitor; -}; - -class BSONHolder { - MONGO_DISALLOW_COPYING(BSONHolder); - -public: - explicit BSONHolder(BSONObj obj) : _scope(NULL), _obj(obj.getOwned()), _modified(false) { - // give hint v8's GC - v8::V8::AdjustAmountOfExternalAllocatedMemory(_obj.objsize()); - } - ~BSONHolder() { - if (_scope && _scope->getIsolate()) - // if v8 is still up, send hint to GC - v8::V8::AdjustAmountOfExternalAllocatedMemory(-_obj.objsize()); - } - V8Scope* _scope; - BSONObj _obj; - bool _modified; - bool _readOnly; - std::set<std::string> _removed; -}; - -extern ScriptEngine* globalScriptEngine; -} diff --git a/src/mongo/scripting/v8-3.25_db.cpp b/src/mongo/scripting/v8-3.25_db.cpp deleted file mode 100644 index e4fd6d890c2..00000000000 --- a/src/mongo/scripting/v8-3.25_db.cpp +++ /dev/null @@ -1,1079 +0,0 @@ -// v8_db.cpp - -/* Copyright 2014 MongoDB Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#include "mongo/scripting/v8-3.25_db.h" - -#include <iostream> -#include <iomanip> - -#include "mongo/base/init.h" -#include "mongo/client/sasl_client_authenticate.h" -#include "mongo/db/namespace_string.h" -#include "mongo/s/d_state.h" -#include "mongo/scripting/engine_v8-3.25.h" -#include "mongo/scripting/v8-3.25_utils.h" -#include "mongo/util/assert_util.h" -#include "mongo/util/base64.h" -#include "mongo/util/hex.h" -#include "mongo/util/text.h" - -using namespace std; -using std::unique_ptr; -using std::shared_ptr; - -namespace mongo { - -namespace { -std::vector<V8FunctionPrototypeManipulatorFn> _mongoPrototypeManipulators; -bool _mongoPrototypeManipulatorsFrozen = false; - -MONGO_INITIALIZER(V8MongoPrototypeManipulatorRegistry)(InitializerContext* context) { - return Status::OK(); -} - -MONGO_INITIALIZER_WITH_PREREQUISITES(V8MongoPrototypeManipulatorRegistrationDone, - ("V8MongoPrototypeManipulatorRegistry")) -(InitializerContext* context) { - _mongoPrototypeManipulatorsFrozen = true; - return Status::OK(); -} - -} // namespace - -void v8RegisterMongoPrototypeManipulator(const V8FunctionPrototypeManipulatorFn& manipulator) { - fassert(16467, !_mongoPrototypeManipulatorsFrozen); - _mongoPrototypeManipulators.push_back(manipulator); -} - -static v8::Local<v8::Value> newInstance(v8::Local<v8::Function> f, - const v8::FunctionCallbackInfo<v8::Value>& args) { - // need to translate arguments into an array - v8::EscapableHandleScope handle_scope(args.GetIsolate()); - const int argc = args.Length(); - static const int MAX_ARGC = 24; - uassert(16858, "Too many arguments. Max is 24", argc <= MAX_ARGC); - - // TODO SERVER-8016: properly allocate handles on the stack - v8::Local<v8::Value> argv[MAX_ARGC]; - for (int i = 0; i < argc; ++i) { - argv[i] = args[i]; - } - return handle_scope.Escape(f->NewInstance(argc, argv)); -} - -v8::Local<v8::FunctionTemplate> getInternalCursorFunctionTemplate(V8Scope* scope) { - v8::Local<v8::FunctionTemplate> ic = scope->createV8Function(internalCursorCons); - ic->InstanceTemplate()->SetInternalFieldCount(1); - v8::Local<v8::ObjectTemplate> icproto = ic->PrototypeTemplate(); - scope->injectV8Method("next", internalCursorNext, icproto); - scope->injectV8Method("hasNext", internalCursorHasNext, icproto); - scope->injectV8Method("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto); - scope->injectV8Method("readOnly", internalCursorReadOnly, icproto); - return ic; -} - -v8::Local<v8::FunctionTemplate> getMongoFunctionTemplate(V8Scope* scope, bool local) { - v8::Local<v8::FunctionTemplate> mongo; - if (local) - mongo = scope->createV8Function(mongoConsLocal); - else - mongo = scope->createV8Function(mongoConsExternal); - mongo->InstanceTemplate()->SetInternalFieldCount(1); - v8::Local<v8::ObjectTemplate> proto = mongo->PrototypeTemplate(); - scope->injectV8Method("find", mongoFind, proto); - scope->injectV8Method("insert", mongoInsert, proto); - scope->injectV8Method("remove", mongoRemove, proto); - scope->injectV8Method("update", mongoUpdate, proto); - scope->injectV8Method("auth", mongoAuth, proto); - scope->injectV8Method("logout", mongoLogout, proto); - scope->injectV8Method("cursorFromId", mongoCursorFromId, proto); - - fassert(16468, _mongoPrototypeManipulatorsFrozen); - for (size_t i = 0; i < _mongoPrototypeManipulators.size(); ++i) - _mongoPrototypeManipulators[i](scope, mongo); - - return mongo; -} - - -v8::Local<v8::Value> mongoConsExternal(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - string host = "127.0.0.1"; - if (args.Length() > 0 && args[0]->IsString()) { - v8::String::Utf8Value utf(args[0]); - host = string(*utf); - } - - // only allow function template to be used by a constructor - uassert(16859, "Mongo function is only usable as a constructor", args.IsConstructCall()); - verify(scope->MongoFT()->HasInstance(args.This())); - - auto statusWithHost = ConnectionString::parse(host); - if (!status.isOK()) { - return v8AssertionException(statusWithHost.getStatus().reason()); - } - - const ConnectionString cs(statusWithHost.getValue()); - - string errmsg; - DBClientBase* conn; - conn = cs.connect(errmsg); - if (!conn) { - return v8AssertionException(errmsg); - } - - v8::Local<v8::External> connHandle = - scope->dbClientBaseTracker.track(scope->getIsolate(), args.This(), conn); - - ScriptEngine::runConnectCallback(*conn); - - args.This()->SetInternalField(0, connHandle); - args.This()->ForceSet(scope->v8StringData("slaveOk"), - v8::Boolean::New(scope->getIsolate(), false)); - args.This()->ForceSet(scope->v8StringData("host"), scope->v8StringData(host)); - - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> mongoConsLocal(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 0, "local Mongo constructor takes no args") - - // only allow function template to be used by a constructor - uassert(16860, "Mongo function is only usable as a constructor", args.IsConstructCall()); - verify(scope->MongoFT()->HasInstance(args.This())); - - DBClientBase* conn = createDirectClient(scope->getOpContext()); - v8::Local<v8::External> connHandle = - scope->dbClientBaseTracker.track(scope->getIsolate(), args.This(), conn); - - args.This()->SetInternalField(0, connHandle); - args.This()->ForceSet(scope->v8StringData("slaveOk"), - v8::Boolean::New(scope->getIsolate(), false)); - args.This()->ForceSet(scope->v8StringData("host"), scope->v8StringData("EMBEDDED")); - - return v8::Undefined(scope->getIsolate()); -} - -std::shared_ptr<mongo::DBClientBase> getConnection( - V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - verify(scope->MongoFT()->HasInstance(args.This())); - verify(args.This()->InternalFieldCount() == 1); - v8::Local<v8::External> c = v8::Local<v8::External>::Cast(args.This()->GetInternalField(0)); - std::shared_ptr<DBClientBase>* conn = static_cast<std::shared_ptr<DBClientBase>*>(c->Value()); - massert(16667, "Unable to get db client connection", conn && conn->get()); - return *conn; -} - -/** - * JavaScript binding for Mongo.prototype.find(namespace, query, fields, limit, skip) - */ -v8::Local<v8::Value> mongoFind(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 7, "find needs 7 args") - argumentCheck(args[1]->IsObject(), "needs to be an object") - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - BSONObj fields; - BSONObj q = scope->v8ToMongo(args[1]->ToObject()); - bool haveFields = args[2]->IsObject() && args[2]->ToObject()->GetPropertyNames()->Length() > 0; - if (haveFields) - fields = scope->v8ToMongo(args[2]->ToObject()); - - std::shared_ptr<mongo::DBClientCursor> cursor; - int nToReturn = args[3]->Int32Value(); - int nToSkip = args[4]->Int32Value(); - int batchSize = args[5]->Int32Value(); - int options = args[6]->Int32Value(); - cursor = - conn->query(ns, q, nToReturn, nToSkip, haveFields ? &fields : NULL, options, batchSize); - if (!cursor.get()) { - return v8AssertionException("error doing query: failed"); - } - - v8::Local<v8::Function> cons = scope->InternalCursorFT()->GetFunction(); - v8::Local<v8::Object> c = cons->NewInstance(); - c->SetInternalField(0, v8::External::New(scope->getIsolate(), cursor.get())); - scope->dbConnectionAndCursor.track( - scope->getIsolate(), c, new V8Scope::DBConnectionAndCursor(conn, cursor)); - return c; -} - -v8::Local<v8::Value> mongoCursorFromId(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 2 || args.Length() == 3, "cursorFromId needs 2 or 3 args") - argumentCheck(scope->NumberLongFT()->HasInstance(args[1]), "2nd arg must be a NumberLong") - argumentCheck(args[2]->IsUndefined() || args[2]->IsNumber(), - "3rd arg must be a js Number") - - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - long long cursorId = numberLongVal(scope, args[1]->ToObject()); - - std::shared_ptr<mongo::DBClientCursor> cursor( - new DBClientCursor(conn.get(), ns, cursorId, 0, 0)); - - if (!args[2]->IsUndefined()) - cursor->setBatchSize(args[2]->Int32Value()); - - v8::Local<v8::Function> cons = scope->InternalCursorFT()->GetFunction(); - v8::Local<v8::Object> c = cons->NewInstance(); - c->SetInternalField(0, v8::External::New(scope->getIsolate(), cursor.get())); - scope->dbConnectionAndCursor.track( - scope->getIsolate(), c, new V8Scope::DBConnectionAndCursor(conn, cursor)); - return c; -} - -v8::Local<v8::Value> mongoInsert(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 3, "insert needs 3 args") - argumentCheck(args[1]->IsObject(), "attempted to insert a non-object") - - verify(scope->MongoFT()->HasInstance(args.This())); - - if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) { - return v8AssertionException("js db in read only mode"); - } - - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - - v8::Local<v8::Integer> flags = args[2]->ToInteger(); - - if (args[1]->IsArray()) { - v8::Local<v8::Array> arr = v8::Local<v8::Array>::Cast(args[1]); - vector<BSONObj> bos; - uint32_t len = arr->Length(); - argumentCheck(len > 0, "attempted to insert an empty array") - - for (uint32_t i = 0; i < len; i++) { - v8::Local<v8::Object> el = arr->CloneElementAt(i); - argumentCheck(!el.IsEmpty(), "attempted to insert an array of non-object types") - - // Set ID on the element if necessary - if (!el->Has(scope->v8StringData("_id"))) { - v8::Local<v8::Value> argv[1]; - el->ForceSet(scope->v8StringData("_id"), - scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv)); - } - bos.push_back(scope->v8ToMongo(el)); - } - conn->insert(ns, bos, flags->Int32Value()); - } else { - v8::Local<v8::Object> in = args[1]->ToObject(); - if (!in->Has(scope->v8StringData("_id"))) { - v8::Local<v8::Value> argv[1]; - in->ForceSet(scope->v8StringData("_id"), - scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv)); - } - BSONObj o = scope->v8ToMongo(in); - conn->insert(ns, o); - } - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> mongoRemove(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 2 || args.Length() == 3, "remove needs 2 or 3 args") - argumentCheck(args[1]->IsObject(), "attempted to remove a non-object") - - verify(scope->MongoFT()->HasInstance(args.This())); - - if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) { - return v8AssertionException("js db in read only mode"); - } - - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - - v8::Local<v8::Object> in = args[1]->ToObject(); - BSONObj o = scope->v8ToMongo(in); - - bool justOne = false; - if (args.Length() > 2) { - justOne = args[2]->BooleanValue(); - } - - conn->remove(ns, o, justOne); - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> mongoUpdate(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() >= 3, "update needs at least 3 args") - argumentCheck(args[1]->IsObject(), "1st param to update has to be an object") - argumentCheck(args[2]->IsObject(), "2nd param to update has to be an object") - - verify(scope->MongoFT()->HasInstance(args.This())); - - if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) { - return v8AssertionException("js db in read only mode"); - } - - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - - v8::Local<v8::Object> q = args[1]->ToObject(); - v8::Local<v8::Object> o = args[2]->ToObject(); - - bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value(); - bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value(); - - BSONObj q1 = scope->v8ToMongo(q); - BSONObj o1 = scope->v8ToMongo(o); - conn->update(ns, q1, o1, upsert, multi); - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> mongoAuth(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - if (NULL == conn) - return v8AssertionException("no connection"); - - BSONObj params; - switch (args.Length()) { - case 1: - params = scope->v8ToMongo(args[0]->ToObject()); - break; - case 3: - params = BSON(saslCommandMechanismFieldName - << "MONGODB-CR" << saslCommandUserDBFieldName << toSTLString(args[0]) - << saslCommandUserFieldName << toSTLString(args[1]) - << saslCommandPasswordFieldName << toSTLString(args[2])); - break; - default: - return v8AssertionException("mongoAuth takes 1 object or 3 string arguments"); - } - try { - conn->auth(params); - } catch (const DBException& ex) { - return v8AssertionException(ex.toString()); - } - return v8::Boolean::New(scope->getIsolate(), true); -} - -v8::Local<v8::Value> mongoLogout(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 1, "logout needs 1 arg") std::shared_ptr<DBClientBase> conn = - getConnection(scope, args); - const string db = toSTLString(args[0]); - BSONObj ret; - conn->logout(db, ret); - return scope->mongoToLZV8(ret, false); -} - -/** - * get cursor from v8 argument - */ -mongo::DBClientCursor* getCursor(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - verify(scope->InternalCursorFT()->HasInstance(args.This())); - verify(args.This()->InternalFieldCount() == 1); - v8::Local<v8::External> c = v8::Local<v8::External>::Cast(args.This()->GetInternalField(0)); - mongo::DBClientCursor* cursor = static_cast<mongo::DBClientCursor*>(c->Value()); - return cursor; -} - -v8::Local<v8::Value> internalCursorCons(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - return v8::Undefined(scope->getIsolate()); -} - -/** - * cursor.next() - */ -v8::Local<v8::Value> internalCursorNext(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - mongo::DBClientCursor* cursor = getCursor(scope, args); - if (!cursor) - return v8::Undefined(scope->getIsolate()); - BSONObj o = cursor->next(); - bool ro = false; - if (args.This()->Has(v8::String::NewFromUtf8(scope->getIsolate(), "_ro"))) - ro = args.This()->Get(v8::String::NewFromUtf8(scope->getIsolate(), "_ro"))->BooleanValue(); - return scope->mongoToLZV8(o, ro); -} - -/** - * cursor.hasNext() - */ -v8::Local<v8::Value> internalCursorHasNext(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - mongo::DBClientCursor* cursor = getCursor(scope, args); - if (!cursor) - return v8::Boolean::New(scope->getIsolate(), false); - return v8::Boolean::New(scope->getIsolate(), cursor->more()); -} - -/** - * cursor.objsLeftInBatch() - */ -v8::Local<v8::Value> internalCursorObjsLeftInBatch( - V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - mongo::DBClientCursor* cursor = getCursor(scope, args); - if (!cursor) - return v8::Number::New(scope->getIsolate(), 0.0); - return v8::Number::New(scope->getIsolate(), static_cast<double>(cursor->objsLeftInBatch())); -} - -/** - * cursor.readOnly() - */ -v8::Local<v8::Value> internalCursorReadOnly(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - verify(scope->InternalCursorFT()->HasInstance(args.This())); - - v8::Local<v8::Object> cursor = args.This(); - cursor->ForceSet(v8::String::NewFromUtf8(scope->getIsolate(), "_ro"), - v8::Boolean::New(scope->getIsolate(), true)); - return cursor; -} - -v8::Local<v8::Value> dbInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->DBFT()->GetFunction(); - return newInstance(f, args); - } - - verify(scope->DBFT()->HasInstance(args.This())); - - argumentCheck(args.Length() == 2, "db constructor requires 2 arguments") - - args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]); - args.This()->ForceSet(scope->v8StringData("_name"), args[1]); - - for (int i = 0; i < args.Length(); i++) { - argumentCheck(!args[i]->IsUndefined(), "db initializer called with undefined argument") - } - - string dbName = toSTLString(args[1]); - if (!NamespaceString::validDBName(dbName)) { - return v8AssertionException(str::stream() << "[" << dbName - << "] is not a valid database name"); - } - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> collectionInit(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->DBCollectionFT()->GetFunction(); - return newInstance(f, args); - } - - verify(scope->DBCollectionFT()->HasInstance(args.This())); - - argumentCheck(args.Length() == 4, "collection constructor requires 4 arguments") - - for (int i = 0; i < args.Length(); i++) { - argumentCheck(!args[i]->IsUndefined(), - "collection constructor called with undefined argument") - } - - args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]); - args.This()->ForceSet(scope->v8StringData("_db"), args[1]); - args.This()->ForceSet(scope->v8StringData("_shortName"), args[2]); - args.This()->ForceSet(v8::String::NewFromUtf8(scope->getIsolate(), "_fullName"), args[3]); - - auto context = scope->getOpContext(); - if (context && haveLocalShardingInfo(context->getClient(), toSTLString(args[3]))) { - return v8AssertionException("can't use sharded collection from db.eval"); - } - - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> dbQueryInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->DBQueryFT()->GetFunction(); - return newInstance(f, args); - } - - verify(scope->DBQueryFT()->HasInstance(args.This())); - - argumentCheck(args.Length() >= 4, "dbQuery constructor requires at least 4 arguments") - - v8::Local<v8::Object> t = args.This(); - t->ForceSet(scope->v8StringData("_mongo"), args[0]); - t->ForceSet(scope->v8StringData("_db"), args[1]); - t->ForceSet(scope->v8StringData("_collection"), args[2]); - t->ForceSet(scope->v8StringData("_ns"), args[3]); - - if (args.Length() > 4 && args[4]->IsObject()) - t->ForceSet(scope->v8StringData("_query"), args[4]); - else - t->ForceSet(scope->v8StringData("_query"), v8::Object::New(scope->getIsolate())); - - if (args.Length() > 5 && args[5]->IsObject()) - t->ForceSet(scope->v8StringData("_fields"), args[5]); - else - t->ForceSet(scope->v8StringData("_fields"), v8::Null(scope->getIsolate())); - - if (args.Length() > 6 && args[6]->IsNumber()) - t->ForceSet(scope->v8StringData("_limit"), args[6]); - else - t->ForceSet(scope->v8StringData("_limit"), v8::Number::New(scope->getIsolate(), 0)); - - if (args.Length() > 7 && args[7]->IsNumber()) - t->ForceSet(scope->v8StringData("_skip"), args[7]); - else - t->ForceSet(scope->v8StringData("_skip"), v8::Number::New(scope->getIsolate(), 0)); - - if (args.Length() > 8 && args[8]->IsNumber()) - t->ForceSet(scope->v8StringData("_batchSize"), args[8]); - else - t->ForceSet(scope->v8StringData("_batchSize"), v8::Number::New(scope->getIsolate(), 0)); - - if (args.Length() > 9 && args[9]->IsNumber()) - t->ForceSet(scope->v8StringData("_options"), args[9]); - else - t->ForceSet(scope->v8StringData("_options"), v8::Number::New(scope->getIsolate(), 0)); - - t->ForceSet(scope->v8StringData("_cursor"), v8::Null(scope->getIsolate())); - t->ForceSet(scope->v8StringData("_numReturned"), v8::Number::New(scope->getIsolate(), 0)); - t->ForceSet(scope->v8StringData("_special"), v8::Boolean::New(scope->getIsolate(), false)); - - return v8::Undefined(scope->getIsolate()); -} - -void collectionSetter(v8::Local<v8::String> name, - v8::Local<v8::Value> value, - const v8::PropertyCallbackInfo<v8::Value>& info) { - v8::Local<v8::Value> val; - v8::ReturnValue<v8::Value> result = info.GetReturnValue(); - result.Set(val); - try { - V8Scope* scope = getScope(info.GetIsolate()); - - // Both DB and Collection objects use this setter - verify(scope->DBCollectionFT()->HasInstance(info.This()) || - scope->DBFT()->HasInstance(info.This())); - - // a collection name cannot be overwritten by a variable - string sname = toSTLString(name); - if (sname.length() == 0 || sname[0] == '_') { - // if starts with '_' we allow overwrite - return; - } - // dont set - result.Set(value); - } catch (const DBException& dbEx) { - result.Set(v8AssertionException(dbEx.toString())); - } catch (...) { - result.Set(v8AssertionException("unknown error in collationSetter")); - } -} - -void collectionGetter(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info) { - v8::Local<v8::Value> val; - v8::ReturnValue<v8::Value> result = info.GetReturnValue(); - result.Set(val); - try { - V8Scope* scope = getScope(info.GetIsolate()); - - // Both DB and Collection objects use this getter - verify(scope->DBCollectionFT()->HasInstance(info.This()) || - scope->DBFT()->HasInstance(info.This())); - - v8::TryCatch tryCatch; - - // first look in prototype, may be a function - v8::Local<v8::Value> real = info.This()->GetPrototype()->ToObject()->Get(name); - if (!real->IsUndefined()) { - result.Set(real); - return; - } - - // 2nd look into real values, may be cached collection object - string sname = toSTLString(name); - if (info.This()->HasRealNamedProperty(name)) { - v8::Local<v8::Value> prop = info.This()->GetRealNamedProperty(name); - if (prop->IsObject() && - prop->ToObject()->HasRealNamedProperty( - v8::String::NewFromUtf8(scope->getIsolate(), "_fullName"))) { - // need to check every time that the collection did not get sharded - auto context = scope->getOpContext(); - if (context && - haveLocalShardingInfo( - context->getClient(), - toSTLString(prop->ToObject()->GetRealNamedProperty( - v8::String::NewFromUtf8(scope->getIsolate(), "_fullName"))))) { - result.Set(v8AssertionException("can't use sharded collection from db.eval")); - return; - } - } - result.Set(prop); - return; - } else if (sname.length() == 0 || sname[0] == '_') { - // if starts with '_' we dont return collection, one must use getCollection() - return; - } - - // no hit, create new collection - v8::Local<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get( - v8::String::NewFromUtf8(scope->getIsolate(), "getCollection")); - if (!getCollection->IsFunction()) { - result.Set(v8AssertionException("getCollection is not a function")); - return; - } - - v8::Local<v8::Function> f = getCollection.As<v8::Function>(); - v8::Local<v8::Value> argv[1]; - argv[0] = name; - v8::Local<v8::Value> coll = f->Call(info.This(), 1, argv); - if (coll.IsEmpty()) { - result.Set(tryCatch.ReThrow()); - return; - } - - uassert(16861, - "getCollection returned something other than a collection", - scope->DBCollectionFT()->HasInstance(coll)); - - // cache collection for reuse, don't enumerate - info.This()->ForceSet(name, coll, v8::DontEnum); - result.Set(coll); - } catch (const DBException& dbEx) { - result.Set(v8AssertionException(dbEx.toString())); - } catch (...) { - result.Set(v8AssertionException("unknown error in collectionGetter")); - } -} - -void dbQueryIndexAccess(unsigned int index, const v8::PropertyCallbackInfo<v8::Value>& info) { - v8::Local<v8::Value> val; - v8::ReturnValue<v8::Value> result = info.GetReturnValue(); - result.Set(val); - try { - V8Scope* scope = getScope(info.GetIsolate()); - verify(scope->DBQueryFT()->HasInstance(info.This())); - - v8::Local<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get( - v8::String::NewFromUtf8(scope->getIsolate(), "arrayAccess")); - massert(16660, "arrayAccess is not a function", arrayAccess->IsFunction()); - - v8::Local<v8::Function> f = arrayAccess.As<v8::Function>(); - v8::Local<v8::Value> argv[1]; - argv[0] = v8::Number::New(scope->getIsolate(), index); - - result.Set(f->Call(info.This(), 1, argv)); - } catch (const DBException& dbEx) { - result.Set(v8AssertionException(dbEx.toString())); - } catch (...) { - result.Set(v8AssertionException("unknown error in dbQueryIndexAccess")); - } -} - -v8::Local<v8::Value> objectIdInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->ObjectIdFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Local<v8::Object> it = args.This(); - verify(scope->ObjectIdFT()->HasInstance(it)); - - OID oid; - if (args.Length() == 0) { - oid.init(); - } else { - string s = toSTLString(args[0]); - try { - Scope::validateObjectIdString(s); - } catch (const MsgAssertionException& m) { - return v8AssertionException(m.toString()); - } - oid.init(s); - } - - it->ForceSet(scope->v8StringData("str"), - v8::String::NewFromUtf8(scope->getIsolate(), oid.toString().c_str())); - return it; -} - -v8::Local<v8::Value> dbRefInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->DBRefFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Local<v8::Object> it = args.This(); - verify(scope->DBRefFT()->HasInstance(it)); - - argumentCheck(args.Length() >= 2 && args.Length() <= 3, "DBRef needs 2 or 3 arguments") - argumentCheck(args[0]->IsString(), "DBRef 1st parameter must be a string") - it->ForceSet(scope->v8StringData("$ref"), args[0]); - it->ForceSet(scope->v8StringData("$id"), args[1]); - - if (args.Length() == 3) { - argumentCheck(args[2]->IsString(), "DBRef 3rd parameter must be a string") - it->ForceSet(scope->v8StringData("$db"), args[2]); - } - - return it; -} - -v8::Local<v8::Value> dbPointerInit(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->DBPointerFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Local<v8::Object> it = args.This(); - verify(scope->DBPointerFT()->HasInstance(it)); - - argumentCheck(args.Length() == 2, "DBPointer needs 2 arguments") - argumentCheck(args[0]->IsString(), "DBPointer 1st parameter must be a string") - argumentCheck(scope->ObjectIdFT()->HasInstance(args[1]), - "DBPointer 2nd parameter must be an ObjectId") - - it->ForceSet(scope->v8StringData("ns"), args[0]); - it->ForceSet(scope->v8StringData("id"), args[1]); - return it; -} - -v8::Local<v8::Value> dbTimestampInit(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->TimestampFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Local<v8::Object> it = args.This(); - verify(scope->TimestampFT()->HasInstance(it)); - - if (args.Length() == 0) { - it->ForceSet(scope->v8StringData("t"), v8::Number::New(scope->getIsolate(), 0)); - it->ForceSet(scope->v8StringData("i"), v8::Number::New(scope->getIsolate(), 0)); - } else if (args.Length() == 2) { - if (!args[0]->IsNumber()) { - return v8AssertionException("Timestamp time must be a number"); - } - if (!args[1]->IsNumber()) { - return v8AssertionException("Timestamp increment must be a number"); - } - int64_t t = args[0]->IntegerValue(); - int64_t largestVal = int64_t(OpTime::max().getSecs()); - if (t > largestVal) - return v8AssertionException(str::stream() << "The first argument must be in seconds; " - << t << " is too large (max " << largestVal - << ")"); - it->ForceSet(scope->v8StringData("t"), args[0]); - it->ForceSet(scope->v8StringData("i"), args[1]); - } else { - return v8AssertionException("Timestamp needs 0 or 2 arguments"); - } - - return it; -} - -v8::Local<v8::Value> binDataInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->BinDataFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Local<v8::Object> it = args.This(); - verify(scope->BinDataFT()->HasInstance(it)); - - argumentCheck(args.Length() == 2, "BinData takes 2 arguments -- BinData(subtype,data)"); - - // 2 args: type, base64 string - v8::Local<v8::Value> type = args[0]; - if (!type->IsNumber() || type->Int32Value() < 0 || type->Int32Value() > 255) { - return v8AssertionException( - "BinData subtype must be a Number between 0 and 255 inclusive)"); - } - v8::String::Utf8Value utf(args[1]); - // uassert if invalid base64 string - string tmpBase64 = base64::decode(*utf); - // length property stores the decoded length - it->ForceSet(scope->v8StringData("len"), - v8::Number::New(scope->getIsolate(), tmpBase64.length()), - v8::PropertyAttribute::ReadOnly); - it->ForceSet(scope->v8StringData("type"), type, v8::PropertyAttribute::ReadOnly); - it->SetInternalField(0, args[1]); - - return it; -} - -v8::Local<v8::Value> binDataToString(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Local<v8::Object> it = args.This(); - verify(scope->BinDataFT()->HasInstance(it)); - int type = it->Get(v8::String::NewFromUtf8(scope->getIsolate(), "type"))->Int32Value(); - - stringstream ss; - verify(it->InternalFieldCount() == 1); - ss << "BinData(" << type << ",\"" << toSTLString(it->GetInternalField(0)) << "\")"; - return v8::String::NewFromUtf8(scope->getIsolate(), ss.str().c_str()); -} - -v8::Local<v8::Value> binDataToBase64(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Local<v8::Object> it = args.This(); - verify(scope->BinDataFT()->HasInstance(it)); - verify(it->InternalFieldCount() == 1); - return it->GetInternalField(0); -} - -v8::Local<v8::Value> binDataToHex(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Local<v8::Object> it = args.This(); - verify(scope->BinDataFT()->HasInstance(it)); - verify(it->InternalFieldCount() == 1); - string data = base64::decode(toSTLString(it->GetInternalField(0))); - stringstream ss; - ss.setf(ios_base::hex, ios_base::basefield); - ss.fill('0'); - ss.setf(ios_base::right, ios_base::adjustfield); - for (std::string::iterator it = data.begin(); it != data.end(); ++it) { - unsigned v = (unsigned char)*it; - ss << setw(2) << v; - } - return v8::String::NewFromUtf8(scope->getIsolate(), ss.str().c_str()); -} - -static v8::Local<v8::Value> hexToBinData(V8Scope* scope, int type, string hexstr) { - // SERVER-9686: This function does not correctly check to make sure hexstr is actually made - // up of valid hex digits, and fails in the hex utility functions - - int len = hexstr.length() / 2; - unique_ptr<char[]> data(new char[len]); - const char* src = hexstr.c_str(); - for (int i = 0; i < len; i++) { - data[i] = fromHex(src + i * 2); - } - - string encoded = base64::encode(data.get(), len); - v8::Local<v8::Value> argv[2]; - argv[0] = v8::Number::New(scope->getIsolate(), type); - argv[1] = v8::String::NewFromUtf8(scope->getIsolate(), encoded.c_str()); - return scope->BinDataFT()->GetFunction()->NewInstance(2, argv); -} - -v8::Local<v8::Value> uuidInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 1, "UUID needs 1 argument") v8::String::Utf8Value utf(args[0]); - argumentCheck(utf.length() == 32, - "UUID string must have 32 characters") return hexToBinData(scope, bdtUUID, *utf); -} - -v8::Local<v8::Value> md5Init(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 1, "MD5 needs 1 argument") v8::String::Utf8Value utf(args[0]); - argumentCheck(utf.length() == 32, - "MD5 string must have 32 characters") return hexToBinData(scope, MD5Type, *utf); -} - -v8::Local<v8::Value> hexDataInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 2, "HexData needs 2 arguments") v8::Local<v8::Value> type = - args[0]; - if (!type->IsNumber() || type->Int32Value() < 0 || type->Int32Value() > 255) { - return v8AssertionException("HexData subtype must be a Number between 0 and 255 inclusive"); - } - v8::String::Utf8Value utf(args[1]); - return hexToBinData(scope, type->Int32Value(), *utf); -} - -v8::Local<v8::Value> numberLongInit(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->NumberLongFT()->GetFunction(); - return newInstance(f, args); - } - - argumentCheck(args.Length() == 0 || args.Length() == 1 || args.Length() == 3, - "NumberLong needs 0, 1 or 3 arguments") - - v8::Local<v8::Object> it = args.This(); - verify(scope->NumberLongFT()->HasInstance(it)); - - if (args.Length() == 0) { - it->ForceSet(scope->v8StringData("floatApprox"), v8::Number::New(scope->getIsolate(), 0)); - } else if (args.Length() == 1) { - if (args[0]->IsNumber()) { - it->ForceSet(scope->v8StringData("floatApprox"), args[0]); - } else { - v8::String::Utf8Value data(args[0]); - string num = *data; - const char* numStr = num.c_str(); - long long n; - try { - n = parseLL(numStr); - } catch (const AssertionException&) { - return v8AssertionException(string("could not convert \"") + num + - "\" to NumberLong"); - } - unsigned long long val = n; - // values above 2^53 are not accurately represented in JS - if ((long long)val == (long long)(double)(long long)(val) && - val < 9007199254740992ULL) { - it->ForceSet(scope->v8StringData("floatApprox"), - v8::Number::New(scope->getIsolate(), (double)(long long)(val))); - } else { - it->ForceSet(scope->v8StringData("floatApprox"), - v8::Number::New(scope->getIsolate(), (double)(long long)(val))); - it->ForceSet(scope->v8StringData("top"), - v8::Integer::New(scope->getIsolate(), val >> 32)); - it->ForceSet(scope->v8StringData("bottom"), - v8::Integer::New(scope->getIsolate(), - (unsigned long)(val & 0x00000000ffffffff))); - } - } - } else { - it->ForceSet(scope->v8StringData("floatApprox"), args[0]->ToNumber()); - it->ForceSet(scope->v8StringData("top"), args[1]->ToUint32()); - it->ForceSet(scope->v8StringData("bottom"), args[2]->ToUint32()); - } - return it; -} - -long long numberLongVal(V8Scope* scope, const v8::Local<v8::Object>& it) { - verify(scope->NumberLongFT()->HasInstance(it)); - if (!it->Has(v8::String::NewFromUtf8(scope->getIsolate(), "top"))) - return (long long)(it->Get(v8::String::NewFromUtf8(scope->getIsolate(), "floatApprox")) - ->NumberValue()); - return (long long)((unsigned long long)(it->Get(v8::String::NewFromUtf8(scope->getIsolate(), - "top")) - ->ToInt32() - ->Value()) - << 32) + - (unsigned)(it->Get(v8::String::NewFromUtf8(scope->getIsolate(), "bottom")) - ->ToInt32() - ->Value()); -} - -v8::Local<v8::Value> numberLongValueOf(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Local<v8::Object> it = args.This(); - long long val = numberLongVal(scope, it); - return v8::Number::New(scope->getIsolate(), double(val)); -} - -v8::Local<v8::Value> numberLongToNumber(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - return numberLongValueOf(scope, args); -} - -v8::Local<v8::Value> numberLongToString(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Local<v8::Object> it = args.This(); - - stringstream ss; - long long val = numberLongVal(scope, it); - const long long limit = 2LL << 30; - - if (val <= -limit || limit <= val) - ss << "NumberLong(\"" << val << "\")"; - else - ss << "NumberLong(" << val << ")"; - - string ret = ss.str(); - return v8::String::NewFromUtf8(scope->getIsolate(), ret.c_str()); -} - -v8::Local<v8::Value> numberIntInit(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - if (!args.IsConstructCall()) { - v8::Local<v8::Function> f = scope->NumberIntFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Local<v8::Object> it = args.This(); - verify(scope->NumberIntFT()->HasInstance(it)); - - argumentCheck(args.Length() == 0 || args.Length() == 1, - "NumberInt needs 0 or 1 arguments") if (args.Length() == 0) { - it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "__NumberInt"), - v8::Number::New(scope->getIsolate(), 0)); - } - else if (args.Length() == 1) { - it->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "__NumberInt"), - args[0]->ToInt32()); - } - return it; -} - -int numberIntVal(V8Scope* scope, const v8::Local<v8::Object>& it) { - verify(scope->NumberIntFT()->HasInstance(it)); - v8::Local<v8::Value> value = - it->GetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "__NumberInt")); - verify(!value.IsEmpty()); - return value->Int32Value(); -} - -v8::Local<v8::Value> numberIntValueOf(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Local<v8::Object> it = args.This(); - return v8::Integer::New(scope->getIsolate(), numberIntVal(scope, it)); -} - -v8::Local<v8::Value> numberIntToNumber(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - return numberIntValueOf(scope, args); -} - -v8::Local<v8::Value> numberIntToString(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Local<v8::Object> it = args.This(); - int val = numberIntVal(scope, it); - string ret = str::stream() << "NumberInt(" << val << ")"; - return v8::String::NewFromUtf8(scope->getIsolate(), ret.c_str()); -} - -v8::Local<v8::Value> v8ObjectInvalidForStorage(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 1, "invalidForStorage needs 1 argument") if (args[0]->IsNull()) { - return v8::Null(scope->getIsolate()); - } - argumentCheck(args[0]->IsObject(), "argument to invalidForStorage has to be an object") - Status validForStorage = scope->v8ToMongo(args[0]->ToObject()).storageValid(true); - if (validForStorage.isOK()) { - return v8::Null(scope->getIsolate()); - } - - std::string errmsg = str::stream() << validForStorage.codeString() << ": " - << validForStorage.reason(); - return v8::String::NewFromUtf8(scope->getIsolate(), errmsg.c_str()); -} - -v8::Local<v8::Value> bsonsize(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 1, "bsonsize needs 1 argument") if (args[0]->IsNull()) { - return v8::Number::New(scope->getIsolate(), 0); - } - argumentCheck(args[0]->IsObject(), "argument to bsonsize has to be an object") return v8:: - Number::New(scope->getIsolate(), scope->v8ToMongo(args[0]->ToObject()).objsize()); -} - -v8::Local<v8::Value> bsonWoCompare(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - argumentCheck(args.Length() == 2, "bsonWoCompare needs 2 argument"); - - argumentCheck(args[0]->IsObject(), "first argument to bsonWoCompare has to be an object"); - argumentCheck(args[1]->IsObject(), "second argument to bsonWoCompare has to be an object"); - - BSONObj firstObject(scope->v8ToMongo(args[0]->ToObject())); - BSONObj secondObject(scope->v8ToMongo(args[1]->ToObject())); - - return v8::Number::New(scope->getIsolate(), firstObject.woCompare(secondObject)); -} -} diff --git a/src/mongo/scripting/v8-3.25_db.h b/src/mongo/scripting/v8-3.25_db.h deleted file mode 100644 index 024efb55d26..00000000000 --- a/src/mongo/scripting/v8-3.25_db.h +++ /dev/null @@ -1,155 +0,0 @@ -// v8_db.h - -/* Copyright 2014 MongoDB Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#pragma once - -#include <v8.h> - -#include "mongo/scripting/engine_v8-3.25.h" -#include "mongo/stdx/functional.h" - -namespace mongo { - -// Internal Cursor -v8::Local<v8::FunctionTemplate> getInternalCursorFunctionTemplate(V8Scope* scope); - -// Mongo constructors -v8::Local<v8::Value> mongoConsLocal(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> mongoConsExternal(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::FunctionTemplate> getMongoFunctionTemplate(V8Scope* scope, bool local); - -// Mongo member functions -v8::Local<v8::Value> mongoFind(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> mongoInsert(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> mongoRemove(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> mongoUpdate(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> mongoAuth(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> mongoLogout(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> mongoCursorFromId(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - -// Cursor object -v8::Local<v8::Value> internalCursorCons(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> internalCursorNext(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> internalCursorHasNext(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> internalCursorReadOnly(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - -// BinData object -v8::Local<v8::Value> binDataInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> binDataToString(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> binDataToBase64(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> binDataToHex(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// NumberLong object -long long numberLongVal(V8Scope* scope, const v8::Local<v8::Object>& it); -v8::Local<v8::Value> numberLongInit(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> numberLongToNumber(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> numberLongValueOf(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> numberLongToString(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - -// NumberInt object -int numberIntVal(V8Scope* scope, const v8::Local<v8::Object>& it); -v8::Local<v8::Value> numberIntInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> numberIntToNumber(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> numberIntValueOf(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); -v8::Local<v8::Value> numberIntToString(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - -// DBQuery object -v8::Local<v8::Value> dbQueryInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); -void dbQueryIndexAccess(::uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info); - -// db constructor -v8::Local<v8::Value> dbInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// collection constructor -v8::Local<v8::Value> collectionInit(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - -// ObjectId constructor -v8::Local<v8::Value> objectIdInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// DBRef constructor -v8::Local<v8::Value> dbRefInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// DBPointer constructor -v8::Local<v8::Value> dbPointerInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// Timestamp constructor -v8::Local<v8::Value> dbTimestampInit(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - -// UUID constructor -v8::Local<v8::Value> uuidInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// MD5 constructor -v8::Local<v8::Value> md5Init(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// HexData constructor -v8::Local<v8::Value> hexDataInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// Object.invalidForStorage() -v8::Local<v8::Value> v8ObjectInvalidForStorage(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args); - -// Object.bsonsize() -v8::Local<v8::Value> bsonsize(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// global method -// Accepts 2 objects, converts them to BSONObj and calls woCompare on the first against the -// second. -v8::Local<v8::Value> bsonWoCompare(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args); - -// 'db.collection' property handlers -void collectionGetter(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info); -void collectionSetter(v8::Local<v8::String> name, - v8::Local<v8::Value> value, - const v8::PropertyCallbackInfo<v8::Value>& info); - -typedef stdx::function<void(V8Scope*, const v8::Local<v8::FunctionTemplate>&)> - V8FunctionPrototypeManipulatorFn; - -void v8RegisterMongoPrototypeManipulator(const V8FunctionPrototypeManipulatorFn& manipulator); -} diff --git a/src/mongo/scripting/v8-3.25_profiler.cpp b/src/mongo/scripting/v8-3.25_profiler.cpp deleted file mode 100644 index d559c1e2ed1..00000000000 --- a/src/mongo/scripting/v8-3.25_profiler.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright 2013 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault - -#include "mongo/scripting/v8-3.25_profiler.h" - -#include "mongo/util/log.h" -#include "mongo/util/mongoutils/str.h" - -namespace mongo { -void V8CpuProfiler::start(v8::Isolate* isolate, const StringData name) { - isolate->GetCpuProfiler()->StartCpuProfiling( - v8::String::NewFromUtf8(isolate, name.toString().c_str())); -} - -void V8CpuProfiler::stop(v8::Isolate* isolate, const StringData name) { - _cpuProfiles.insert( - make_pair(name.toString(), - isolate->GetCpuProfiler()->StopCpuProfiling(v8::String::NewFromUtf8( - isolate, name.toString().c_str(), v8::String::kNormalString, name.size())))); -} - -void V8CpuProfiler::traverseDepthFirst(const v8::CpuProfileNode* cpuProfileNode, - BSONArrayBuilder& arrayBuilder) { - if (cpuProfileNode == NULL) - return; - BSONObjBuilder frameObjBuilder; - frameObjBuilder.append("Function", *v8::String::Utf8Value(cpuProfileNode->GetFunctionName())); - frameObjBuilder.append("Source", - *v8::String::Utf8Value(cpuProfileNode->GetScriptResourceName())); - frameObjBuilder.appendNumber("Line", cpuProfileNode->GetLineNumber()); - if (cpuProfileNode->GetChildrenCount()) { - BSONArrayBuilder subArrayBuilder(frameObjBuilder.subarrayStart("Children")); - for (int i = 0; i < cpuProfileNode->GetChildrenCount(); ++i) { - traverseDepthFirst(cpuProfileNode->GetChild(i), subArrayBuilder); - } - subArrayBuilder.done(); - } - arrayBuilder << frameObjBuilder.obj(); -} - -const BSONArray V8CpuProfiler::fetch(const StringData name) { - BSONArrayBuilder arrayBuilder; - CpuProfileMap::const_iterator iProf = _cpuProfiles.find(name.toString()); - if (iProf == _cpuProfiles.end()) - return arrayBuilder.arr(); - const v8::CpuProfile* cpuProfile = iProf->second; - if (cpuProfile == NULL) - return arrayBuilder.arr(); - traverseDepthFirst(cpuProfile->GetTopDownRoot(), arrayBuilder); - return arrayBuilder.arr(); -} -} diff --git a/src/mongo/scripting/v8-3.25_profiler.h b/src/mongo/scripting/v8-3.25_profiler.h deleted file mode 100644 index 99d045662ff..00000000000 --- a/src/mongo/scripting/v8-3.25_profiler.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright 2013 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#pragma once - -#include <v8.h> -#include <v8-profiler.h> -#include <map> - -#include "mongo/base/string_data.h" -#include "mongo/db/jsobj.h" - -namespace mongo { - -/** Collect CPU Profiling data from v8. */ -class V8CpuProfiler { -public: - /** Start the CPU profiler */ - void start(v8::Isolate* isolate, const StringData name); - - /** Stop the CPU profiler */ - void stop(v8::Isolate* isolate, const StringData name); - - /** Get the current cpu profile */ - const BSONArray fetch(const StringData name); - -private: - void traverseDepthFirst(const v8::CpuProfileNode* cpuProfileNode, - BSONArrayBuilder& arrayBuilder); - - typedef std::map<std::string, const v8::CpuProfile*> CpuProfileMap; - CpuProfileMap _cpuProfiles; -}; -} diff --git a/src/mongo/scripting/v8-3.25_utils.cpp b/src/mongo/scripting/v8-3.25_utils.cpp deleted file mode 100644 index a2f745b0d1a..00000000000 --- a/src/mongo/scripting/v8-3.25_utils.cpp +++ /dev/null @@ -1,420 +0,0 @@ -// v8_utils.cpp - -/* Copyright 2014 MongoDB Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault - -#include "mongo/scripting/v8-3.25_utils.h" - -#include <cstdint> -#include <iostream> -#include <map> -#include <sstream> -#include <vector> - -#include "mongo/scripting/engine_v8-3.25.h" -#include "mongo/scripting/v8-3.25_db.h" -#include "mongo/stdx/condition_variable.h" -#include "mongo/stdx/mutex.h" -#include "mongo/stdx/thread.h" -#include "mongo/util/log.h" -#include "mongo/util/mongoutils/str.h" - -using namespace std; -using std::unique_ptr; - -namespace mongo { - -std::string toSTLString(const v8::Local<v8::Value>& o) { - return StringData(V8String(o)).toString(); -} - -/** Get the properties of an object (and its prototype) as a comma-delimited string */ -std::string v8ObjectToString(const v8::Local<v8::Object>& o) { - v8::Local<v8::Array> properties = o->GetPropertyNames(); - v8::String::Utf8Value str(properties); - massert(16696, "error converting js type to Utf8Value", *str); - return std::string(*str, str.length()); -} - -std::ostream& operator<<(std::ostream& s, const v8::Local<v8::Value>& o) { - v8::String::Utf8Value str(o); - s << *str; - return s; -} - -std::ostream& operator<<(std::ostream& s, const v8::TryCatch* try_catch) { - v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); - v8::String::Utf8Value exceptionText(try_catch->Exception()); - v8::Local<v8::Message> message = try_catch->Message(); - - if (message.IsEmpty()) { - s << *exceptionText << endl; - } else { - v8::String::Utf8Value filename(message->GetScriptResourceName()); - int linenum = message->GetLineNumber(); - cout << *filename << ":" << linenum << " " << *exceptionText << endl; - - v8::String::Utf8Value sourceline(message->GetSourceLine()); - cout << *sourceline << endl; - - int start = message->GetStartColumn(); - for (int i = 0; i < start; i++) - cout << " "; - - int end = message->GetEndColumn(); - for (int i = start; i < end; i++) - cout << "^"; - - cout << endl; - } - return s; -} - -class JSThreadConfig { -public: - JSThreadConfig(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args, - bool newScope = false) - : _started(), _done(), _sharedData(new SharedData()) { - jsassert(args.Length() > 0, "need at least one argument"); - jsassert(args[0]->IsFunction(), "first argument must be a function"); - - // arguments need to be copied into the isolate, go through bson - BSONObjBuilder b; - for (int i = 0; i < args.Length(); ++i) { - scope->v8ToMongoElement(b, "arg" + BSONObjBuilder::numStr(i), args[i]); - } - _sharedData->_args = b.obj(); - } - - ~JSThreadConfig() {} - - void start() { - jsassert(!_started, "Thread already started"); - JSThread jt(*this); - _thread.reset(new stdx::thread(jt)); - _started = true; - } - void join() { - jsassert(_started && !_done, "Thread not running"); - _thread->join(); - _done = true; - } - - /** - * Returns true if the JSThread terminated as a result of an error - * during its execution, and false otherwise. This operation does - * not block, nor does it require join() to have been called. - */ - bool hasFailed() const { - jsassert(_started, "Thread not started"); - return _sharedData->getErrored(); - } - - BSONObj returnData() { - if (!_done) - join(); - return _sharedData->_returnData; - } - -private: - /* - * JSThreadConfig doesn't always outlive its JSThread (for example, if the parent thread - * garbage collects the JSThreadConfig before the JSThread has finished running), so any - * data shared between them has to go in a shared_ptr. - */ - class SharedData { - public: - SharedData() : _errored(false) {} - BSONObj _args; - BSONObj _returnData; - void setErrored(bool value) { - stdx::lock_guard<stdx::mutex> lck(_erroredMutex); - _errored = value; - } - bool getErrored() { - stdx::lock_guard<stdx::mutex> lck(_erroredMutex); - return _errored; - } - - private: - stdx::mutex _erroredMutex; - bool _errored; - }; - - class JSThread { - public: - JSThread(JSThreadConfig& config) : _sharedData(config._sharedData) {} - - void operator()() { - try { - unique_ptr<V8Scope> scope(static_cast<V8Scope*>(globalScriptEngine->newScope())); - v8::Locker v8lock(scope->getIsolate()); - v8::Isolate::Scope iscope(scope->getIsolate()); - v8::HandleScope handle_scope(scope->getIsolate()); - v8::Context::Scope context_scope(scope->getContext()); - - BSONObj args = _sharedData->_args; - v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast( - v8::Local<v8::Value>(scope->mongoToV8Element(args.firstElement(), true))); - int argc = args.nFields() - 1; - - // TODO SERVER-8016: properly allocate handles on the stack - v8::Local<v8::Value> argv[24]; - BSONObjIterator it(args); - it.next(); - for (int i = 0; i < argc && i < 24; ++i) { - argv[i] = v8::Local<v8::Value>::New(scope->getIsolate(), - scope->mongoToV8Element(*it, true)); - it.next(); - } - v8::TryCatch try_catch; - v8::Local<v8::Value> ret = f->Call(scope->getGlobal(), argc, argv); - if (ret.IsEmpty() || try_catch.HasCaught()) { - string e = scope->v8ExceptionToSTLString(&try_catch); - log() << "js thread raised js exception: " << e << endl; - ret = v8::Undefined(scope->getIsolate()); - _sharedData->setErrored(true); - } - // ret is translated to BSON to switch isolate - BSONObjBuilder b; - scope->v8ToMongoElement(b, "ret", ret); - _sharedData->_returnData = b.obj(); - } catch (const DBException& e) { - // Keeping behavior the same as for js exceptions. - log() << "js thread threw c++ exception: " << e.toString(); - _sharedData->setErrored(true); - _sharedData->_returnData = BSON("ret" << BSONUndefined); - } catch (const std::exception& e) { - log() << "js thread threw c++ exception: " << e.what(); - _sharedData->setErrored(true); - _sharedData->_returnData = BSON("ret" << BSONUndefined); - } catch (...) { - log() << "js thread threw c++ non-exception"; - _sharedData->setErrored(true); - _sharedData->_returnData = BSON("ret" << BSONUndefined); - } - } - - private: - std::shared_ptr<SharedData> _sharedData; - }; - - bool _started; - bool _done; - unique_ptr<stdx::thread> _thread; - std::shared_ptr<SharedData> _sharedData; -}; - -class CountDownLatchHolder { -private: - struct Latch { - Latch(int32_t count) : count(count) {} - stdx::condition_variable cv; - stdx::mutex mutex; - int32_t count; - }; - - std::shared_ptr<Latch> get(int32_t desc) { - stdx::lock_guard<stdx::mutex> lock(_mutex); - Map::iterator iter = _latches.find(desc); - jsassert(iter != _latches.end(), "not a valid CountDownLatch descriptor"); - return iter->second; - } - - typedef std::map<int32_t, std::shared_ptr<Latch>> Map; - Map _latches; - stdx::mutex _mutex; - int32_t _counter; - -public: - CountDownLatchHolder() : _counter(0) {} - int32_t make(int32_t count) { - jsassert(count >= 0, "argument must be >= 0"); - stdx::lock_guard<stdx::mutex> lock(_mutex); - int32_t desc = ++_counter; - _latches.insert(std::make_pair(desc, std::make_shared<Latch>(count))); - return desc; - } - void await(int32_t desc) { - std::shared_ptr<Latch> latch = get(desc); - stdx::unique_lock<stdx::mutex> lock(latch->mutex); - while (latch->count != 0) { - latch->cv.wait(lock); - } - } - void countDown(int32_t desc) { - std::shared_ptr<Latch> latch = get(desc); - stdx::unique_lock<stdx::mutex> lock(latch->mutex); - if (latch->count > 0) { - latch->count--; - } - if (latch->count == 0) { - latch->cv.notify_all(); - } - } - int32_t getCount(int32_t desc) { - std::shared_ptr<Latch> latch = get(desc); - stdx::unique_lock<stdx::mutex> lock(latch->mutex); - return latch->count; - } -}; -namespace { -CountDownLatchHolder globalCountDownLatchHolder; -} - -v8::Local<v8::Value> CountDownLatchNew(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - jsassert(args.Length() == 1, "need exactly one argument"); - jsassert(args[0]->IsInt32(), "argument must be an integer"); - int32_t count = v8::Local<v8::Integer>::Cast(args[0])->Value(); - return v8::Int32::New(scope->getIsolate(), globalCountDownLatchHolder.make(count)); -} - -v8::Local<v8::Value> CountDownLatchAwait(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - jsassert(args.Length() == 1, "need exactly one argument"); - jsassert(args[0]->IsInt32(), "argument must be an integer"); - globalCountDownLatchHolder.await(args[0]->ToInt32()->Value()); - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> CountDownLatchCountDown(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - jsassert(args.Length() == 1, "need exactly one argument"); - jsassert(args[0]->IsInt32(), "argument must be an integer"); - globalCountDownLatchHolder.countDown(args[0]->ToInt32()->Value()); - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> CountDownLatchGetCount(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - jsassert(args.Length() == 1, "need exactly one argument"); - jsassert(args[0]->IsInt32(), "argument must be an integer"); - return v8::Int32::New(scope->getIsolate(), - globalCountDownLatchHolder.getCount(args[0]->ToInt32()->Value())); -} - -JSThreadConfig* thisConfig(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Local<v8::External> c = v8::Local<v8::External>::Cast(args.This()->GetHiddenValue( - v8::String::NewFromUtf8(scope->getIsolate(), "_JSThreadConfig"))); - JSThreadConfig* config = static_cast<std::shared_ptr<JSThreadConfig>*>(c->Value())->get(); - return config; -} - -v8::Local<v8::Value> ThreadInit(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - JSThreadConfig* config = new JSThreadConfig(scope, args); - v8::Local<v8::External> handle = - scope->jsThreadConfigTracker.track(scope->getIsolate(), args.This(), config); - args.This()->SetHiddenValue(v8::String::NewFromUtf8(scope->getIsolate(), "_JSThreadConfig"), - handle); - - invariant(thisConfig(scope, args) == config); - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> ScopedThreadInit(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - // NOTE: ScopedThread and Thread behave identically because a new V8 Isolate is always - // created. - return ThreadInit(scope, args); -} - -v8::Local<v8::Value> ThreadStart(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - thisConfig(scope, args)->start(); - return v8::Undefined(scope->getIsolate()); -} - -v8::Local<v8::Value> ThreadJoin(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - thisConfig(scope, args)->join(); - return v8::Undefined(scope->getIsolate()); -} - -// Indicates to the caller that the thread terminated as a result of an error. -v8::Local<v8::Value> ThreadHasFailed(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - bool hasFailed = thisConfig(scope, args)->hasFailed(); - return v8::Boolean::New(scope->getIsolate(), hasFailed); -} - -v8::Local<v8::Value> ThreadReturnData(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - BSONObj data = thisConfig(scope, args)->returnData(); - return scope->mongoToV8Element(data.firstElement(), true); -} - -v8::Local<v8::Value> ThreadInject(V8Scope* scope, const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::EscapableHandleScope handle_scope(args.GetIsolate()); - jsassert(args.Length() == 1, "threadInject takes exactly 1 argument"); - jsassert(args[0]->IsObject(), "threadInject needs to be passed a prototype"); - v8::Local<v8::Object> o = args[0]->ToObject(); - - // install method on the Thread object - scope->injectV8Function("init", ThreadInit, o); - scope->injectV8Function("start", ThreadStart, o); - scope->injectV8Function("join", ThreadJoin, o); - scope->injectV8Function("hasFailed", ThreadHasFailed, o); - scope->injectV8Function("returnData", ThreadReturnData, o); - return handle_scope.Escape(v8::Local<v8::Value>()); -} - -v8::Local<v8::Value> ScopedThreadInject(V8Scope* scope, - const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::EscapableHandleScope handle_scope(args.GetIsolate()); - jsassert(args.Length() == 1, "threadInject takes exactly 1 argument"); - jsassert(args[0]->IsObject(), "threadInject needs to be passed a prototype"); - v8::Local<v8::Object> o = args[0]->ToObject(); - - scope->injectV8Function("init", ScopedThreadInit, o); - // inheritance takes care of other member functions - - return handle_scope.Escape(v8::Local<v8::Value>()); -} - -void installFork(V8Scope* scope, v8::Local<v8::Object> global, v8::Local<v8::Context> context) { - scope->injectV8Function("_threadInject", ThreadInject, global); - scope->injectV8Function("_scopedThreadInject", ScopedThreadInject, global); - - scope->setObject("CountDownLatch", BSONObj(), false); - v8::Handle<v8::Object> cdl = scope->get("CountDownLatch").As<v8::Object>(); - scope->injectV8Function("_new", CountDownLatchNew, cdl); - scope->injectV8Function("_await", CountDownLatchAwait, cdl); - scope->injectV8Function("_countDown", CountDownLatchCountDown, cdl); - scope->injectV8Function("_getCount", CountDownLatchGetCount, cdl); -} - -v8::Local<v8::Value> v8AssertionException(const char* errorMessage) { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate->ThrowException( - v8::Exception::Error(v8::String::NewFromUtf8(isolate, errorMessage))); -} -v8::Local<v8::Value> v8AssertionException(const std::string& errorMessage) { - return v8AssertionException(errorMessage.c_str()); -} -} diff --git a/src/mongo/scripting/v8-3.25_utils.h b/src/mongo/scripting/v8-3.25_utils.h deleted file mode 100644 index 3e13af74b1e..00000000000 --- a/src/mongo/scripting/v8-3.25_utils.h +++ /dev/null @@ -1,117 +0,0 @@ -// v8_utils.h - -/* Copyright 2014 MongoDB Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#pragma once - -#include <cstdio> -#include <cstdlib> -#include <string> -#include <v8.h> - -#include <mongo/base/string_data.h> -#include <mongo/util/assert_util.h> - -namespace mongo { - -#define jsassert(x, msg) uassert(16664, (msg), (x)) - -#define argumentCheck(mustBeTrue, errorMessage) \ - if (!(mustBeTrue)) { \ - return v8AssertionException((errorMessage)); \ - } - -std::ostream& operator<<(std::ostream& s, const v8::Local<v8::Value>& o); -std::ostream& operator<<(std::ostream& s, const v8::Local<v8::TryCatch>* try_catch); - -/** Simple v8 object to string conversion helper */ -std::string toSTLString(const v8::Local<v8::Value>& o); - -/** Like toSTLString but doesn't allocate a new std::string - * - * This owns the string's memory so you need to be careful not to let the - * converted StringDatas outlive the V8Scope object. These rules are the - * same as converting from a std::string into a StringData. - * - * Safe: - * void someFunction(StringData argument); - * v8::Local<v8::String> aString; - * - * someFunction(V8String(aString)); // passing down stack as temporary - * - * V8String named (aString); - * someFunction(named); // passing up stack as named value - * - * StringData sd = named; // scope of sd is less than named - * - * Unsafe: - * StringData _member; - * - * StringData returningFunction() { - * StringData sd = V8String(aString); // sd outlives the temporary - * - * V8String named(aString) - * _member = named; // _member outlives named scope - * - * return V8String(aString); // passing up stack - * } - */ -class V8String { -public: - explicit V8String(const v8::Local<v8::Value>& o) : _str(o) { - massert(16686, "error converting js type to Utf8Value", *_str); - } - operator StringData() const { - return StringData(*_str, _str.length()); - } - -private: - v8::String::Utf8Value _str; -}; - -/** Get the properties of an object (and it's prototype) as a comma-delimited string */ -std::string v8ObjectToString(const v8::Local<v8::Object>& o); - -class V8Scope; -void installFork(V8Scope* scope, v8::Local<v8::Object> global, v8::Local<v8::Context> context); - -/** Throw a V8 exception from Mongo callback code; message text will be preceded by "Error: ". - * Note: this function should be used for text that did not originate from the JavaScript - * engine. Errors from the JavaScript engine will already have a prefix such as - * ReferenceError, TypeError or SyntaxError. - * Note: call only from a native function called from JavaScript (a callback). - * The V8 ThrowException routine will note a JavaScript exception that will be - * "thrown" in JavaScript when we return from the native function. - * Note: it's required to return immediately to V8's execution control without calling any - * V8 API functions. In this state, an empty handle may (will) be returned. - * @param errorMessage Error message text. - * @return Empty handle to be returned from callback function. - */ -v8::Local<v8::Value> v8AssertionException(const char* errorMessage); -v8::Local<v8::Value> v8AssertionException(const std::string& errorMessage); -} diff --git a/src/mongo/scripting/v8_db.cpp b/src/mongo/scripting/v8_db.cpp deleted file mode 100644 index d0f533d316b..00000000000 --- a/src/mongo/scripting/v8_db.cpp +++ /dev/null @@ -1,1195 +0,0 @@ -// v8_db.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. - */ - -#include "mongo/scripting/v8_db.h" - -#include <iostream> -#include <iomanip> - -#include "mongo/base/init.h" -#include "mongo/base/status_with.h" -#include "mongo/client/native_sasl_client_session.h" -#include "mongo/client/sasl_client_authenticate.h" -#include "mongo/client/sasl_scramsha1_client_conversation.h" -#include "mongo/db/namespace_string.h" -#include "mongo/db/operation_context.h" -#include "mongo/s/d_state.h" -#include "mongo/scripting/engine_v8.h" -#include "mongo/scripting/v8_utils.h" -#include "mongo/util/assert_util.h" -#include "mongo/util/base64.h" -#include "mongo/util/hex.h" -#include "mongo/util/text.h" - -using namespace std; -using std::unique_ptr; -using std::shared_ptr; - -namespace mongo { - -namespace { -std::vector<V8FunctionPrototypeManipulatorFn> _mongoPrototypeManipulators; -bool _mongoPrototypeManipulatorsFrozen = false; - -MONGO_INITIALIZER(V8MongoPrototypeManipulatorRegistry)(InitializerContext* context) { - return Status::OK(); -} - -MONGO_INITIALIZER_WITH_PREREQUISITES(V8MongoPrototypeManipulatorRegistrationDone, - ("V8MongoPrototypeManipulatorRegistry")) -(InitializerContext* context) { - _mongoPrototypeManipulatorsFrozen = true; - return Status::OK(); -} - -} // namespace - -void v8RegisterMongoPrototypeManipulator(const V8FunctionPrototypeManipulatorFn& manipulator) { - fassert(16467, !_mongoPrototypeManipulatorsFrozen); - _mongoPrototypeManipulators.push_back(manipulator); -} - -static v8::Handle<v8::Value> newInstance(v8::Handle<v8::Function> f, const v8::Arguments& args) { - // need to translate arguments into an array - v8::HandleScope handle_scope; - const int argc = args.Length(); - static const int MAX_ARGC = 24; - uassert(16858, "Too many arguments. Max is 24", argc <= MAX_ARGC); - - // TODO SERVER-8016: properly allocate handles on the stack - v8::Handle<v8::Value> argv[MAX_ARGC]; - for (int i = 0; i < argc; ++i) { - argv[i] = args[i]; - } - return handle_scope.Close(f->NewInstance(argc, argv)); -} - -v8::Handle<v8::FunctionTemplate> getInternalCursorFunctionTemplate(V8Scope* scope) { - v8::Handle<v8::FunctionTemplate> ic = scope->createV8Function(internalCursorCons); - ic->InstanceTemplate()->SetInternalFieldCount(1); - v8::Handle<v8::ObjectTemplate> icproto = ic->PrototypeTemplate(); - scope->injectV8Method("next", internalCursorNext, icproto); - scope->injectV8Method("hasNext", internalCursorHasNext, icproto); - scope->injectV8Method("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto); - scope->injectV8Method("readOnly", internalCursorReadOnly, icproto); - return ic; -} - -v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate(V8Scope* scope, bool local) { - v8::Handle<v8::FunctionTemplate> mongo; - if (local) - mongo = scope->createV8Function(mongoConsLocal); - else - mongo = scope->createV8Function(mongoConsExternal); - mongo->InstanceTemplate()->SetInternalFieldCount(1); - v8::Handle<v8::ObjectTemplate> proto = mongo->PrototypeTemplate(); - scope->injectV8Method("runCommand", mongoRunCommand, proto); - scope->injectV8Method("find", mongoFind, proto); - scope->injectV8Method("insert", mongoInsert, proto); - scope->injectV8Method("remove", mongoRemove, proto); - scope->injectV8Method("update", mongoUpdate, proto); - scope->injectV8Method("auth", mongoAuth, proto); - scope->injectV8Method("logout", mongoLogout, proto); - scope->injectV8Method("cursorFromId", mongoCursorFromId, proto); - scope->injectV8Method("copyDatabaseWithSCRAM", mongoCopyDatabaseWithSCRAM, proto); - scope->injectV8Method("getClientRPCProtocols", mongoGetClientRPCProtocols, proto); - scope->injectV8Method("setClientRPCProtocols", mongoSetClientRPCProtocols, proto); - scope->injectV8Method("getServerRPCProtocols", mongoGetServerRPCProtocols, proto); - - fassert(16468, _mongoPrototypeManipulatorsFrozen); - for (size_t i = 0; i < _mongoPrototypeManipulators.size(); ++i) - _mongoPrototypeManipulators[i](scope, mongo); - - return mongo; -} - - -v8::Handle<v8::Value> mongoConsExternal(V8Scope* scope, const v8::Arguments& args) { - string host = "127.0.0.1"; - if (args.Length() > 0 && args[0]->IsString()) { - v8::String::Utf8Value utf(args[0]); - host = string(*utf); - } - - // only allow function template to be used by a constructor - uassert(16859, "Mongo function is only usable as a constructor", args.IsConstructCall()); - verify(scope->MongoFT()->HasInstance(args.This())); - - auto statusWithHost = ConnectionString::parse(host); - if (!statusWithHost.isOK()) { - return v8AssertionException(statusWithHost.getStatus().reason()); - } - - const ConnectionString cs(statusWithHost.getValue()); - - string errmsg; - DBClientBase* conn; - conn = cs.connect(errmsg); - if (!conn) { - return v8AssertionException(errmsg); - } - - v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.This()); - v8::Local<v8::External> connHandle = scope->dbClientBaseTracker.track(self, conn); - - ScriptEngine::runConnectCallback(*conn); - - args.This()->SetInternalField(0, connHandle); - args.This()->ForceSet(scope->v8StringData("slaveOk"), v8::Boolean::New(false)); - args.This()->ForceSet(scope->v8StringData("host"), scope->v8StringData(host)); - - return v8::Undefined(); -} - -v8::Handle<v8::Value> mongoConsLocal(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 0, "local Mongo constructor takes no args") - - // only allow function template to be used by a constructor - uassert(16860, "Mongo function is only usable as a constructor", args.IsConstructCall()); - verify(scope->MongoFT()->HasInstance(args.This())); - - DBClientBase* conn = createDirectClient(scope->getOpContext()); - v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.This()); - v8::Local<v8::External> connHandle = scope->dbClientBaseTracker.track(self, conn); - - args.This()->SetInternalField(0, connHandle); - args.This()->ForceSet(scope->v8StringData("slaveOk"), v8::Boolean::New(false)); - args.This()->ForceSet(scope->v8StringData("host"), scope->v8StringData("EMBEDDED")); - - return v8::Undefined(); -} - -std::shared_ptr<DBClientBase> getConnection(V8Scope* scope, const v8::Arguments& args) { - verify(scope->MongoFT()->HasInstance(args.This())); - verify(args.This()->InternalFieldCount() == 1); - v8::Local<v8::External> c = v8::External::Cast(*(args.This()->GetInternalField(0))); - std::shared_ptr<DBClientBase>* conn = static_cast<std::shared_ptr<DBClientBase>*>(c->Value()); - massert(16667, "Unable to get db client connection", conn && conn->get()); - return *conn; -} - -/** - * JavaScript binding for Mongo.prototype.runCommand(database, cmdObj, queryOptions) - */ -v8::Handle<v8::Value> mongoRunCommand(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 3, "runCommand needs 3 args"); - argumentCheck(args[0]->IsString(), "the database parameter to runCommand must be a string"); - argumentCheck(args[1]->IsObject(), "the cmdObj parameter to runCommand must be an object"); - argumentCheck(args[2]->IsNumber(), "the options parameter to runCommand must be a number"); - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string database = toSTLString(args[0]); - BSONObj cmdObj = scope->v8ToMongo(args[1]->ToObject()); - int queryOptions = args[2]->Int32Value(); - BSONObj cmdRes; - conn->runCommand(database, cmdObj, cmdRes, queryOptions); - // the returned object is not read only as some of our tests depend on modifying it. - return scope->mongoToLZV8(cmdRes, false /* read only */); -} - -/** - * JavaScript binding for Mongo.prototype.find(namespace, query, fields, limit, skip) - */ -v8::Handle<v8::Value> mongoFind(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 7, "find needs 7 args") - argumentCheck(args[1]->IsObject(), "needs to be an object") - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - BSONObj fields; - BSONObj q = scope->v8ToMongo(args[1]->ToObject()); - bool haveFields = args[2]->IsObject() && args[2]->ToObject()->GetPropertyNames()->Length() > 0; - if (haveFields) - fields = scope->v8ToMongo(args[2]->ToObject()); - - std::shared_ptr<mongo::DBClientCursor> cursor; - int nToReturn = args[3]->Int32Value(); - int nToSkip = args[4]->Int32Value(); - int batchSize = args[5]->Int32Value(); - int options = args[6]->Int32Value(); - cursor = - conn->query(ns, q, nToReturn, nToSkip, haveFields ? &fields : NULL, options, batchSize); - if (!cursor.get()) { - return v8AssertionException("error doing query: failed"); - } - - v8::Handle<v8::Function> cons = scope->InternalCursorFT()->GetFunction(); - v8::Handle<v8::Object> c = cons->NewInstance(); - c->SetInternalField(0, v8::External::New(cursor.get())); - scope->dbConnectionAndCursor.track(v8::Persistent<v8::Value>::New(c), - new V8Scope::DBConnectionAndCursor(conn, cursor)); - return c; -} - -v8::Handle<v8::Value> mongoCursorFromId(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 2 || args.Length() == 3, "cursorFromId needs 2 or 3 args") - argumentCheck(scope->NumberLongFT()->HasInstance(args[1]), "2nd arg must be a NumberLong") - argumentCheck(args[2]->IsUndefined() || args[2]->IsNumber(), - "3rd arg must be a js Number") - - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - long long cursorId = numberLongVal(scope, args[1]->ToObject()); - - std::shared_ptr<mongo::DBClientCursor> cursor( - new DBClientCursor(conn.get(), ns, cursorId, 0, 0)); - - if (!args[2]->IsUndefined()) - cursor->setBatchSize(args[2]->Int32Value()); - - v8::Handle<v8::Function> cons = scope->InternalCursorFT()->GetFunction(); - v8::Handle<v8::Object> c = cons->NewInstance(); - c->SetInternalField(0, v8::External::New(cursor.get())); - scope->dbConnectionAndCursor.track(v8::Persistent<v8::Value>::New(c), - new V8Scope::DBConnectionAndCursor(conn, cursor)); - return c; -} - -v8::Handle<v8::Value> mongoInsert(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 3, "insert needs 3 args") - argumentCheck(args[1]->IsObject(), "attempted to insert a non-object") - - verify(scope->MongoFT()->HasInstance(args.This())); - - if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) { - return v8AssertionException("js db in read only mode"); - } - - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - - v8::Handle<v8::Integer> flags = args[2]->ToInteger(); - - if (args[1]->IsArray()) { - v8::Local<v8::Array> arr = v8::Array::Cast(*args[1]); - vector<BSONObj> bos; - uint32_t len = arr->Length(); - argumentCheck(len > 0, "attempted to insert an empty array") - - for (uint32_t i = 0; i < len; i++) { - v8::Local<v8::Object> el = arr->CloneElementAt(i); - argumentCheck(!el.IsEmpty(), "attempted to insert an array of non-object types") - - // Set ID on the element if necessary - if (!el->Has(scope->v8StringData("_id"))) { - v8::Handle<v8::Value> argv[1]; - el->ForceSet(scope->v8StringData("_id"), - scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv)); - } - bos.push_back(scope->v8ToMongo(el)); - } - conn->insert(ns, bos, flags->Int32Value()); - } else { - v8::Handle<v8::Object> in = args[1]->ToObject(); - if (!in->Has(scope->v8StringData("_id"))) { - v8::Handle<v8::Value> argv[1]; - in->ForceSet(scope->v8StringData("_id"), - scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv)); - } - BSONObj o = scope->v8ToMongo(in); - conn->insert(ns, o); - } - return v8::Undefined(); -} - -v8::Handle<v8::Value> mongoRemove(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 2 || args.Length() == 3, "remove needs 2 or 3 args") - argumentCheck(args[1]->IsObject(), "attempted to remove a non-object") - - verify(scope->MongoFT()->HasInstance(args.This())); - - if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) { - return v8AssertionException("js db in read only mode"); - } - - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - - v8::Handle<v8::Object> in = args[1]->ToObject(); - BSONObj o = scope->v8ToMongo(in); - - bool justOne = false; - if (args.Length() > 2) { - justOne = args[2]->BooleanValue(); - } - - conn->remove(ns, o, justOne); - return v8::Undefined(); -} - -v8::Handle<v8::Value> mongoUpdate(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() >= 3, "update needs at least 3 args") - argumentCheck(args[1]->IsObject(), "1st param to update has to be an object") - argumentCheck(args[2]->IsObject(), "2nd param to update has to be an object") - - verify(scope->MongoFT()->HasInstance(args.This())); - - if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) { - return v8AssertionException("js db in read only mode"); - } - - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - const string ns = toSTLString(args[0]); - - v8::Handle<v8::Object> q = args[1]->ToObject(); - v8::Handle<v8::Object> o = args[2]->ToObject(); - - bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value(); - bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value(); - - BSONObj q1 = scope->v8ToMongo(q); - BSONObj o1 = scope->v8ToMongo(o); - conn->update(ns, q1, o1, upsert, multi); - return v8::Undefined(); -} - -v8::Handle<v8::Value> mongoAuth(V8Scope* scope, const v8::Arguments& args) { - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - if (NULL == conn) - return v8AssertionException("no connection"); - - BSONObj params; - switch (args.Length()) { - case 1: - params = scope->v8ToMongo(args[0]->ToObject()); - break; - case 3: - params = BSON(saslCommandMechanismFieldName - << "MONGODB-CR" << saslCommandUserDBFieldName << toSTLString(args[0]) - << saslCommandUserFieldName << toSTLString(args[1]) - << saslCommandPasswordFieldName << toSTLString(args[2])); - break; - default: - return v8AssertionException("mongoAuth takes 1 object or 3 string arguments"); - } - try { - conn->auth(params); - } catch (const DBException& ex) { - return v8AssertionException(ex.toString()); - } - return v8::Boolean::New(true); -} - -v8::Handle<v8::Value> mongoLogout(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 1, "logout needs 1 arg") std::shared_ptr<DBClientBase> conn = - getConnection(scope, args); - const string db = toSTLString(args[0]); - BSONObj ret; - conn->logout(db, ret); - return scope->mongoToLZV8(ret, false); -} - -v8::Handle<v8::Value> mongoCopyDatabaseWithSCRAM(V8Scope* scope, const v8::Arguments& args) { - std::shared_ptr<DBClientBase> conn = getConnection(scope, args); - if (NULL == conn) - return v8AssertionException("no connection"); - - argumentCheck(args.Length() == 5, "copyDatabase needs 5 arg"); - - // copyDatabase(fromdb, todb, fromhost, username, password); - std::string fromDb = toSTLString(args[0]); - std::string toDb = toSTLString(args[1]); - std::string fromHost = toSTLString(args[2]); - std::string user = toSTLString(args[3]); - std::string hashedPwd = DBClientWithCommands::createPasswordDigest(user, toSTLString(args[4])); - - std::unique_ptr<SaslClientSession> session(new NativeSaslClientSession()); - - session->setParameter(SaslClientSession::parameterMechanism, "SCRAM-SHA-1"); - session->setParameter(SaslClientSession::parameterUser, user); - session->setParameter(SaslClientSession::parameterPassword, hashedPwd); - session->initialize(); - - BSONObj saslFirstCommandPrefix = - BSON("copydbsaslstart" << 1 << "fromhost" << fromHost << "fromdb" << fromDb - << saslCommandMechanismFieldName << "SCRAM-SHA-1"); - - BSONObj saslFollowupCommandPrefix = - BSON("copydb" << 1 << "fromhost" << fromHost << "fromdb" << fromDb << "todb" << toDb); - - BSONObj saslCommandPrefix = saslFirstCommandPrefix; - BSONObj inputObj = BSON(saslCommandPayloadFieldName << ""); - bool isServerDone = false; - - while (!session->isDone()) { - std::string payload; - BSONType type; - - Status status = saslExtractPayload(inputObj, &payload, &type); - if (!status.isOK()) { - return v8AssertionException(status.reason()); - } - - std::string responsePayload; - status = session->step(payload, &responsePayload); - if (!status.isOK()) { - return v8AssertionException(status.reason()); - } - - BSONObjBuilder commandBuilder; - - commandBuilder.appendElements(saslCommandPrefix); - commandBuilder.appendBinData(saslCommandPayloadFieldName, - int(responsePayload.size()), - BinDataGeneral, - responsePayload.c_str()); - BSONElement conversationId = inputObj[saslCommandConversationIdFieldName]; - if (!conversationId.eoo()) - commandBuilder.append(conversationId); - - BSONObj command = commandBuilder.obj(); - - bool ok = conn->runCommand("admin", command, inputObj); - - ErrorCodes::Error code = - ErrorCodes::fromInt(inputObj[saslCommandCodeFieldName].numberInt()); - - if (!ok || code != ErrorCodes::OK) { - if (code == ErrorCodes::OK) - code = ErrorCodes::UnknownError; - - return scope->mongoToLZV8(inputObj, true); - } - - isServerDone = inputObj[saslCommandDoneFieldName].trueValue(); - saslCommandPrefix = saslFollowupCommandPrefix; - } - - if (!isServerDone) { - return v8AssertionException("copydb client finished before server."); - } - - return scope->mongoToLZV8(inputObj, true); -} - -v8::Handle<v8::Value> mongoGetClientRPCProtocols(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 0, "getClientRPCProtocols takes no args"); - auto conn = getConnection(scope, args); - auto clientRPCProtocols = rpc::toString(conn->getClientRPCProtocols()); - if (!clientRPCProtocols.isOK()) { - return v8AssertionException(clientRPCProtocols.getStatus().reason()); - } - - // make an owned copy so we can safely pass a null-terminated string to v8 - auto protoStr = clientRPCProtocols.getValue().toString(); - return v8::String::New(protoStr.c_str()); -} - -v8::Handle<v8::Value> mongoSetClientRPCProtocols(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 1, "setClientRPCProtocols needs 1 arg"); - argumentCheck(args[0]->IsString(), "first argument to setClientRPCProtocols must be a string"); - auto conn = getConnection(scope, args); - const auto rpcProtosStr = toSTLString(args[0]); - auto clientRPCProtocols = rpc::parseProtocolSet(rpcProtosStr); - if (!clientRPCProtocols.isOK()) { - return v8AssertionException(clientRPCProtocols.getStatus().reason()); - } - conn->setClientRPCProtocols(clientRPCProtocols.getValue()); - return v8::Undefined(); -} - -v8::Handle<v8::Value> mongoGetServerRPCProtocols(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 0, "getServerRPCProtocols takes no args"); - auto conn = getConnection(scope, args); - auto serverRPCProtocols = rpc::toString(conn->getServerRPCProtocols()); - if (!serverRPCProtocols.isOK()) { - return v8AssertionException(serverRPCProtocols.getStatus().reason()); - } - auto protoStr = serverRPCProtocols.getValue().toString(); - return v8::String::New(protoStr.c_str()); -} - -/** - * get cursor from v8 argument - */ -mongo::DBClientCursor* getCursor(V8Scope* scope, const v8::Arguments& args) { - verify(scope->InternalCursorFT()->HasInstance(args.This())); - verify(args.This()->InternalFieldCount() == 1); - v8::Local<v8::External> c = v8::External::Cast(*(args.This()->GetInternalField(0))); - mongo::DBClientCursor* cursor = static_cast<mongo::DBClientCursor*>(c->Value()); - return cursor; -} - -v8::Handle<v8::Value> internalCursorCons(V8Scope* scope, const v8::Arguments& args) { - return v8::Undefined(); -} - -/** - * cursor.next() - */ -v8::Handle<v8::Value> internalCursorNext(V8Scope* scope, const v8::Arguments& args) { - mongo::DBClientCursor* cursor = getCursor(scope, args); - if (!cursor) - return v8::Undefined(); - BSONObj o = cursor->next(); - bool ro = false; - if (args.This()->Has(v8::String::New("_ro"))) - ro = args.This()->Get(v8::String::New("_ro"))->BooleanValue(); - return scope->mongoToLZV8(o, ro); -} - -/** - * cursor.hasNext() - */ -v8::Handle<v8::Value> internalCursorHasNext(V8Scope* scope, const v8::Arguments& args) { - mongo::DBClientCursor* cursor = getCursor(scope, args); - if (!cursor) - return v8::Boolean::New(false); - return v8::Boolean::New(cursor->more()); -} - -/** - * cursor.objsLeftInBatch() - */ -v8::Handle<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, const v8::Arguments& args) { - mongo::DBClientCursor* cursor = getCursor(scope, args); - if (!cursor) - return v8::Number::New(0.0); - return v8::Number::New(static_cast<double>(cursor->objsLeftInBatch())); -} - -/** - * cursor.readOnly() - */ -v8::Handle<v8::Value> internalCursorReadOnly(V8Scope* scope, const v8::Arguments& args) { - verify(scope->InternalCursorFT()->HasInstance(args.This())); - - v8::Local<v8::Object> cursor = args.This(); - cursor->ForceSet(v8::String::New("_ro"), v8::Boolean::New(true)); - return cursor; -} - -v8::Handle<v8::Value> dbInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->DBFT()->GetFunction(); - return newInstance(f, args); - } - - verify(scope->DBFT()->HasInstance(args.This())); - - argumentCheck(args.Length() == 2, "db constructor requires 2 arguments") - - args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]); - args.This()->ForceSet(scope->v8StringData("_name"), args[1]); - - for (int i = 0; i < args.Length(); i++) { - argumentCheck(!args[i]->IsUndefined(), "db initializer called with undefined argument") - } - - string dbName = toSTLString(args[1]); - if (!NamespaceString::validDBName(dbName)) { - return v8AssertionException(str::stream() << "[" << dbName - << "] is not a valid database name"); - } - return v8::Undefined(); -} - -v8::Handle<v8::Value> collectionInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->DBCollectionFT()->GetFunction(); - return newInstance(f, args); - } - - verify(scope->DBCollectionFT()->HasInstance(args.This())); - - argumentCheck(args.Length() == 4, "collection constructor requires 4 arguments") - - for (int i = 0; i < args.Length(); i++) { - argumentCheck(!args[i]->IsUndefined(), - "collection constructor called with undefined argument") - } - - args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]); - args.This()->ForceSet(scope->v8StringData("_db"), args[1]); - args.This()->ForceSet(scope->v8StringData("_shortName"), args[2]); - args.This()->ForceSet(v8::String::New("_fullName"), args[3]); - - auto context = scope->getOpContext(); - if (context && haveLocalShardingInfo(context->getClient(), toSTLString(args[3]))) { - return v8AssertionException("can't use sharded collection from db.eval"); - } - - return v8::Undefined(); -} - -v8::Handle<v8::Value> dbQueryInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->DBQueryFT()->GetFunction(); - return newInstance(f, args); - } - - verify(scope->DBQueryFT()->HasInstance(args.This())); - - argumentCheck(args.Length() >= 4, "dbQuery constructor requires at least 4 arguments") - - v8::Handle<v8::Object> t = args.This(); - t->ForceSet(scope->v8StringData("_mongo"), args[0]); - t->ForceSet(scope->v8StringData("_db"), args[1]); - t->ForceSet(scope->v8StringData("_collection"), args[2]); - t->ForceSet(scope->v8StringData("_ns"), args[3]); - - if (args.Length() > 4 && args[4]->IsObject()) - t->ForceSet(scope->v8StringData("_query"), args[4]); - else - t->ForceSet(scope->v8StringData("_query"), v8::Object::New()); - - if (args.Length() > 5 && args[5]->IsObject()) - t->ForceSet(scope->v8StringData("_fields"), args[5]); - else - t->ForceSet(scope->v8StringData("_fields"), v8::Null()); - - if (args.Length() > 6 && args[6]->IsNumber()) - t->ForceSet(scope->v8StringData("_limit"), args[6]); - else - t->ForceSet(scope->v8StringData("_limit"), v8::Number::New(0)); - - if (args.Length() > 7 && args[7]->IsNumber()) - t->ForceSet(scope->v8StringData("_skip"), args[7]); - else - t->ForceSet(scope->v8StringData("_skip"), v8::Number::New(0)); - - if (args.Length() > 8 && args[8]->IsNumber()) - t->ForceSet(scope->v8StringData("_batchSize"), args[8]); - else - t->ForceSet(scope->v8StringData("_batchSize"), v8::Number::New(0)); - - if (args.Length() > 9 && args[9]->IsNumber()) - t->ForceSet(scope->v8StringData("_options"), args[9]); - else - t->ForceSet(scope->v8StringData("_options"), v8::Number::New(0)); - - t->ForceSet(scope->v8StringData("_cursor"), v8::Null()); - t->ForceSet(scope->v8StringData("_numReturned"), v8::Number::New(0)); - t->ForceSet(scope->v8StringData("_special"), v8::Boolean::New(false)); - - return v8::Undefined(); -} - -v8::Handle<v8::Value> collectionSetter(v8::Local<v8::String> name, - v8::Local<v8::Value> value, - const v8::AccessorInfo& info) { - try { - V8Scope* scope = getScope(info.GetIsolate()); - - // Both DB and Collection objects use this setter - verify(scope->DBCollectionFT()->HasInstance(info.This()) || - scope->DBFT()->HasInstance(info.This())); - - // a collection name cannot be overwritten by a variable - string sname = toSTLString(name); - if (sname.length() == 0 || sname[0] == '_') { - // if starts with '_' we allow overwrite - return v8::Handle<v8::Value>(); - } - // dont set - return value; - } catch (const DBException& dbEx) { - return v8AssertionException(dbEx.toString()); - } catch (...) { - return v8AssertionException("unknown error in collationSetter"); - } -} - -v8::Handle<v8::Value> collectionGetter(v8::Local<v8::String> name, const v8::AccessorInfo& info) { - try { - V8Scope* scope = getScope(info.GetIsolate()); - - // Both DB and Collection objects use this getter - verify(scope->DBCollectionFT()->HasInstance(info.This()) || - scope->DBFT()->HasInstance(info.This())); - - v8::TryCatch tryCatch; - - // first look in prototype, may be a function - v8::Handle<v8::Value> real = info.This()->GetPrototype()->ToObject()->Get(name); - if (!real->IsUndefined()) - return real; - - // 2nd look into real values, may be cached collection object - string sname = toSTLString(name); - if (info.This()->HasRealNamedProperty(name)) { - v8::Local<v8::Value> prop = info.This()->GetRealNamedProperty(name); - if (prop->IsObject() && - prop->ToObject()->HasRealNamedProperty(v8::String::New("_fullName"))) { - // need to check every time that the collection did not get sharded - auto context = scope->getOpContext(); - if (context && - haveLocalShardingInfo(context->getClient(), - toSTLString(prop->ToObject()->GetRealNamedProperty( - v8::String::New("_fullName"))))) { - return v8AssertionException("can't use sharded collection from db.eval"); - } - } - return prop; - } else if (sname.length() == 0 || sname[0] == '_') { - // if starts with '_' we dont return collection, one must use getCollection() - return v8::Handle<v8::Value>(); - } - - // no hit, create new collection - v8::Handle<v8::Value> getCollection = - info.This()->GetPrototype()->ToObject()->Get(v8::String::New("getCollection")); - if (!getCollection->IsFunction()) { - return v8AssertionException("getCollection is not a function"); - } - - v8::Handle<v8::Function> f = getCollection.As<v8::Function>(); - v8::Handle<v8::Value> argv[1]; - argv[0] = name; - v8::Local<v8::Value> coll = f->Call(info.This(), 1, argv); - if (coll.IsEmpty()) - return tryCatch.ReThrow(); - - uassert(16861, - "getCollection returned something other than a collection", - scope->DBCollectionFT()->HasInstance(coll)); - - // cache collection for reuse, don't enumerate - info.This()->ForceSet(name, coll, v8::DontEnum); - return coll; - } catch (const DBException& dbEx) { - return v8AssertionException(dbEx.toString()); - } catch (...) { - return v8AssertionException("unknown error in collectionGetter"); - } -} - -v8::Handle<v8::Value> dbQueryIndexAccess(unsigned int index, const v8::AccessorInfo& info) { - try { - V8Scope* scope = getScope(info.GetIsolate()); - verify(scope->DBQueryFT()->HasInstance(info.This())); - - v8::Handle<v8::Value> arrayAccess = - info.This()->GetPrototype()->ToObject()->Get(v8::String::New("arrayAccess")); - massert(16660, "arrayAccess is not a function", arrayAccess->IsFunction()); - - v8::Handle<v8::Function> f = arrayAccess.As<v8::Function>(); - v8::Handle<v8::Value> argv[1]; - argv[0] = v8::Number::New(index); - - return f->Call(info.This(), 1, argv); - } catch (const DBException& dbEx) { - return v8AssertionException(dbEx.toString()); - } catch (...) { - return v8AssertionException("unknown error in dbQueryIndexAccess"); - } -} - -v8::Handle<v8::Value> objectIdInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->ObjectIdFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Handle<v8::Object> it = args.This(); - verify(scope->ObjectIdFT()->HasInstance(it)); - - OID oid; - if (args.Length() == 0) { - oid.init(); - } else { - string s = toSTLString(args[0]); - try { - Scope::validateObjectIdString(s); - } catch (const MsgAssertionException& m) { - return v8AssertionException(m.toString()); - } - oid.init(s); - } - - it->ForceSet(scope->v8StringData("str"), v8::String::New(oid.toString().c_str())); - return it; -} - -v8::Handle<v8::Value> dbRefInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->DBRefFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Handle<v8::Object> it = args.This(); - verify(scope->DBRefFT()->HasInstance(it)); - - argumentCheck(args.Length() >= 2 && args.Length() <= 3, "DBRef needs 2 or 3 arguments") - argumentCheck(args[0]->IsString(), "DBRef 1st parameter must be a string") - it->ForceSet(scope->v8StringData("$ref"), args[0]); - it->ForceSet(scope->v8StringData("$id"), args[1]); - - if (args.Length() == 3) { - argumentCheck(args[2]->IsString(), "DBRef 3rd parameter must be a string") - it->ForceSet(scope->v8StringData("$db"), args[2]); - } - - return it; -} - -v8::Handle<v8::Value> dbPointerInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->DBPointerFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Handle<v8::Object> it = args.This(); - verify(scope->DBPointerFT()->HasInstance(it)); - - argumentCheck(args.Length() == 2, "DBPointer needs 2 arguments") - argumentCheck(args[0]->IsString(), "DBPointer 1st parameter must be a string") - argumentCheck(scope->ObjectIdFT()->HasInstance(args[1]), - "DBPointer 2nd parameter must be an ObjectId") - - it->ForceSet(scope->v8StringData("ns"), args[0]); - it->ForceSet(scope->v8StringData("id"), args[1]); - return it; -} - -v8::Handle<v8::Value> dbTimestampInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->TimestampFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Handle<v8::Object> it = args.This(); - verify(scope->TimestampFT()->HasInstance(it)); - - if (args.Length() == 0) { - it->ForceSet(scope->v8StringData("t"), v8::Number::New(0)); - it->ForceSet(scope->v8StringData("i"), v8::Number::New(0)); - } else if (args.Length() == 2) { - if (!args[0]->IsNumber()) { - return v8AssertionException("Timestamp time must be a number"); - } - if (!args[1]->IsNumber()) { - return v8AssertionException("Timestamp increment must be a number"); - } - int64_t t = args[0]->IntegerValue(); - int64_t largestVal = int64_t(Timestamp::max().getSecs()); - if (t > largestVal) - return v8AssertionException(str::stream() << "The first argument must be in seconds; " - << t << " is too large (max " << largestVal - << ")"); - it->ForceSet(scope->v8StringData("t"), args[0]); - it->ForceSet(scope->v8StringData("i"), args[1]); - } else { - return v8AssertionException("Timestamp needs 0 or 2 arguments"); - } - - return it; -} - -v8::Handle<v8::Value> binDataInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->BinDataFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Local<v8::Object> it = args.This(); - verify(scope->BinDataFT()->HasInstance(it)); - - argumentCheck(args.Length() == 2, "BinData takes 2 arguments -- BinData(subtype,data)"); - - // 2 args: type, base64 string - v8::Handle<v8::Value> type = args[0]; - if (!type->IsNumber() || type->Int32Value() < 0 || type->Int32Value() > 255) { - return v8AssertionException( - "BinData subtype must be a Number between 0 and 255 inclusive)"); - } - v8::String::Utf8Value utf(args[1]); - // uassert if invalid base64 string - string tmpBase64 = base64::decode(*utf); - // length property stores the decoded length - it->ForceSet(scope->v8StringData("len"), - v8::Number::New(tmpBase64.length()), - v8::PropertyAttribute::ReadOnly); - it->ForceSet(scope->v8StringData("type"), type, v8::PropertyAttribute::ReadOnly); - it->SetInternalField(0, args[1]); - - return it; -} - -v8::Handle<v8::Value> binDataToString(V8Scope* scope, const v8::Arguments& args) { - v8::Handle<v8::Object> it = args.This(); - verify(scope->BinDataFT()->HasInstance(it)); - int type = it->Get(v8::String::New("type"))->Int32Value(); - - stringstream ss; - verify(it->InternalFieldCount() == 1); - ss << "BinData(" << type << ",\"" << toSTLString(it->GetInternalField(0)) << "\")"; - return v8::String::New(ss.str().c_str()); -} - -v8::Handle<v8::Value> binDataToBase64(V8Scope* scope, const v8::Arguments& args) { - v8::Handle<v8::Object> it = args.This(); - verify(scope->BinDataFT()->HasInstance(it)); - verify(it->InternalFieldCount() == 1); - return it->GetInternalField(0); -} - -v8::Handle<v8::Value> binDataToHex(V8Scope* scope, const v8::Arguments& args) { - v8::Handle<v8::Object> it = args.This(); - verify(scope->BinDataFT()->HasInstance(it)); - verify(it->InternalFieldCount() == 1); - string data = base64::decode(toSTLString(it->GetInternalField(0))); - stringstream ss; - ss.setf(ios_base::hex, ios_base::basefield); - ss.fill('0'); - ss.setf(ios_base::right, ios_base::adjustfield); - for (std::string::iterator it = data.begin(); it != data.end(); ++it) { - unsigned v = (unsigned char)*it; - ss << setw(2) << v; - } - return v8::String::New(ss.str().c_str()); -} - -static v8::Handle<v8::Value> hexToBinData(V8Scope* scope, int type, string hexstr) { - // SERVER-9686: This function does not correctly check to make sure hexstr is actually made - // up of valid hex digits, and fails in the hex utility functions - - int len = hexstr.length() / 2; - unique_ptr<char[]> data(new char[len]); - const char* src = hexstr.c_str(); - for (int i = 0; i < len; i++) { - data[i] = fromHex(src + i * 2); - } - - string encoded = base64::encode(data.get(), len); - v8::Handle<v8::Value> argv[2]; - argv[0] = v8::Number::New(type); - argv[1] = v8::String::New(encoded.c_str()); - return scope->BinDataFT()->GetFunction()->NewInstance(2, argv); -} - -v8::Handle<v8::Value> uuidInit(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 1, "UUID needs 1 argument") v8::String::Utf8Value utf(args[0]); - argumentCheck(utf.length() == 32, - "UUID string must have 32 characters") return hexToBinData(scope, bdtUUID, *utf); -} - -v8::Handle<v8::Value> md5Init(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 1, "MD5 needs 1 argument") v8::String::Utf8Value utf(args[0]); - argumentCheck(utf.length() == 32, - "MD5 string must have 32 characters") return hexToBinData(scope, MD5Type, *utf); -} - -v8::Handle<v8::Value> hexDataInit(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 2, "HexData needs 2 arguments") v8::Handle<v8::Value> type = - args[0]; - if (!type->IsNumber() || type->Int32Value() < 0 || type->Int32Value() > 255) { - return v8AssertionException("HexData subtype must be a Number between 0 and 255 inclusive"); - } - v8::String::Utf8Value utf(args[1]); - return hexToBinData(scope, type->Int32Value(), *utf); -} - -v8::Handle<v8::Value> numberLongInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->NumberLongFT()->GetFunction(); - return newInstance(f, args); - } - - argumentCheck(args.Length() == 0 || args.Length() == 1 || args.Length() == 3, - "NumberLong needs 0, 1 or 3 arguments") - - v8::Handle<v8::Object> it = args.This(); - verify(scope->NumberLongFT()->HasInstance(it)); - - if (args.Length() == 0) { - it->ForceSet(scope->v8StringData("floatApprox"), v8::Number::New(0)); - } else if (args.Length() == 1) { - if (args[0]->IsNumber()) { - it->ForceSet(scope->v8StringData("floatApprox"), args[0]); - } else { - v8::String::Utf8Value data(args[0]); - string num = *data; - const char* numStr = num.c_str(); - long long n; - try { - n = parseLL(numStr); - } catch (const AssertionException&) { - return v8AssertionException(string("could not convert \"") + num + - "\" to NumberLong"); - } - unsigned long long val = n; - // values above 2^53 are not accurately represented in JS - if ((long long)val == (long long)(double)(long long)(val) && - val < 9007199254740992ULL) { - it->ForceSet(scope->v8StringData("floatApprox"), - v8::Number::New((double)(long long)(val))); - } else { - it->ForceSet(scope->v8StringData("floatApprox"), - v8::Number::New((double)(long long)(val))); - it->ForceSet(scope->v8StringData("top"), v8::Integer::New(val >> 32)); - it->ForceSet(scope->v8StringData("bottom"), - v8::Integer::New((unsigned long)(val & 0x00000000ffffffff))); - } - } - } else { - it->ForceSet(scope->v8StringData("floatApprox"), args[0]->ToNumber()); - it->ForceSet(scope->v8StringData("top"), args[1]->ToUint32()); - it->ForceSet(scope->v8StringData("bottom"), args[2]->ToUint32()); - } - return it; -} - -long long numberLongVal(V8Scope* scope, const v8::Handle<v8::Object>& it) { - verify(scope->NumberLongFT()->HasInstance(it)); - if (!it->Has(v8::String::New("top"))) - return (long long)(it->Get(v8::String::New("floatApprox"))->NumberValue()); - return (long long)((unsigned long long)(it->Get(v8::String::New("top"))->ToInt32()->Value()) - << 32) + - (unsigned)(it->Get(v8::String::New("bottom"))->ToInt32()->Value()); -} - -v8::Handle<v8::Value> numberLongValueOf(V8Scope* scope, const v8::Arguments& args) { - v8::Handle<v8::Object> it = args.This(); - long long val = numberLongVal(scope, it); - return v8::Number::New(double(val)); -} - -v8::Handle<v8::Value> numberLongToNumber(V8Scope* scope, const v8::Arguments& args) { - return numberLongValueOf(scope, args); -} - -v8::Handle<v8::Value> numberLongToString(V8Scope* scope, const v8::Arguments& args) { - v8::Handle<v8::Object> it = args.This(); - - stringstream ss; - long long val = numberLongVal(scope, it); - const long long limit = 2LL << 30; - - if (val <= -limit || limit <= val) - ss << "NumberLong(\"" << val << "\")"; - else - ss << "NumberLong(" << val << ")"; - - string ret = ss.str(); - return v8::String::New(ret.c_str()); -} - -v8::Handle<v8::Value> numberIntInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->NumberIntFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Handle<v8::Object> it = args.This(); - verify(scope->NumberIntFT()->HasInstance(it)); - - argumentCheck(args.Length() == 0 || args.Length() == 1, - "NumberInt needs 0 or 1 arguments") if (args.Length() == 0) { - it->SetHiddenValue(v8::String::New("__NumberInt"), v8::Number::New(0)); - } - else if (args.Length() == 1) { - it->SetHiddenValue(v8::String::New("__NumberInt"), args[0]->ToInt32()); - } - return it; -} - -int numberIntVal(V8Scope* scope, const v8::Handle<v8::Object>& it) { - verify(scope->NumberIntFT()->HasInstance(it)); - v8::Handle<v8::Value> value = it->GetHiddenValue(v8::String::New("__NumberInt")); - verify(!value.IsEmpty()); - return value->Int32Value(); -} - -v8::Handle<v8::Value> numberIntValueOf(V8Scope* scope, const v8::Arguments& args) { - v8::Handle<v8::Object> it = args.This(); - return v8::Integer::New(numberIntVal(scope, it)); -} - -v8::Handle<v8::Value> numberIntToNumber(V8Scope* scope, const v8::Arguments& args) { - return numberIntValueOf(scope, args); -} - -v8::Handle<v8::Value> numberIntToString(V8Scope* scope, const v8::Arguments& args) { - v8::Handle<v8::Object> it = args.This(); - int val = numberIntVal(scope, it); - string ret = str::stream() << "NumberInt(" << val << ")"; - return v8::String::New(ret.c_str()); -} - -Decimal128 numberDecimalVal(V8Scope* scope, const v8::Handle<v8::Object>& it) { - verify(scope->NumberDecimalFT()->HasInstance(it)); - return Decimal128(toSTLString(it->Get(v8::String::New("val")))); -} - -v8::Handle<v8::Value> numberDecimalInit(V8Scope* scope, const v8::Arguments& args) { - if (!args.IsConstructCall()) { - v8::Handle<v8::Function> f = scope->NumberDecimalFT()->GetFunction(); - return newInstance(f, args); - } - - v8::Handle<v8::Object> it = args.This(); - verify(scope->NumberDecimalFT()->HasInstance(it)); - argumentCheck(args.Length() == 1, "NumberDecimal needs 1 argument"); - argumentCheck(args[0]->IsString(), "NumberDecimal 1st parameter must be a string"); - - it->ForceSet(scope->v8StringData("val"), args[0]); - return it; -} - -v8::Handle<v8::Value> numberDecimalToString(V8Scope* scope, const v8::Arguments& args) { - v8::Handle<v8::Object> it = args.This(); - Decimal128 val = numberDecimalVal(scope, it); - string ret = str::stream() << "NumberDecimal(\"" << val.toString() << "\")"; - return v8::String::New(ret.c_str()); -} - -v8::Handle<v8::Value> v8ObjectInvalidForStorage(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 1, "invalidForStorage needs 1 argument") if (args[0]->IsNull()) { - return v8::Null(); - } - argumentCheck(args[0]->IsObject(), "argument to invalidForStorage has to be an object") - Status validForStorage = scope->v8ToMongo(args[0]->ToObject()).storageValid(true); - if (validForStorage.isOK()) { - return v8::Null(); - } - - std::string errmsg = str::stream() << validForStorage.codeString() << ": " - << validForStorage.reason(); - return v8::String::New(errmsg.c_str()); -} - -v8::Handle<v8::Value> bsonsize(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 1, "bsonsize needs 1 argument") if (args[0]->IsNull()) { - return v8::Number::New(0); - } - argumentCheck(args[0]->IsObject(), "argument to bsonsize has to be an object") return v8:: - Number::New(scope->v8ToMongo(args[0]->ToObject()).objsize()); -} - -v8::Handle<v8::Value> bsonWoCompare(V8Scope* scope, const v8::Arguments& args) { - argumentCheck(args.Length() == 2, "bsonWoCompare needs 2 argument"); - - argumentCheck(args[0]->IsObject(), "first argument to bsonWoCompare has to be an object"); - argumentCheck(args[1]->IsObject(), "second argument to bsonWoCompare has to be an object"); - - BSONObj firstObject(scope->v8ToMongo(args[0]->ToObject())); - BSONObj secondObject(scope->v8ToMongo(args[1]->ToObject())); - - return v8::Number::New(firstObject.woCompare(secondObject)); -} -} diff --git a/src/mongo/scripting/v8_db.h b/src/mongo/scripting/v8_db.h deleted file mode 100644 index 8bd965d9182..00000000000 --- a/src/mongo/scripting/v8_db.h +++ /dev/null @@ -1,146 +0,0 @@ -// v8_db.h - -/* Copyright 2009 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <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. - */ - -#pragma once - -#include <v8.h> - -#include "mongo/scripting/engine_v8.h" -#include "mongo/stdx/functional.h" - -namespace mongo { - -// Internal Cursor -v8::Handle<v8::FunctionTemplate> getInternalCursorFunctionTemplate(V8Scope* scope); - -// Mongo constructors -v8::Handle<v8::Value> mongoConsLocal(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoConsExternal(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate(V8Scope* scope, bool local); - -// Mongo member functions -v8::Handle<v8::Value> mongoRunCommand(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoFind(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoInsert(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoRemove(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoUpdate(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoAuth(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoLogout(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoCursorFromId(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoCopyDatabaseWithSCRAM(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoGetClientRPCProtocols(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoSetClientRPCProtocols(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> mongoGetServerRPCProtocols(V8Scope* scope, const v8::Arguments& args); - - -// Cursor object -v8::Handle<v8::Value> internalCursorCons(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> internalCursorNext(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> internalCursorHasNext(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> internalCursorReadOnly(V8Scope* scope, const v8::Arguments& args); - -// BinData object -v8::Handle<v8::Value> binDataInit(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> binDataToString(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> binDataToBase64(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> binDataToHex(V8Scope* scope, const v8::Arguments& args); - -// NumberLong object -long long numberLongVal(V8Scope* scope, const v8::Handle<v8::Object>& it); -v8::Handle<v8::Value> numberLongInit(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> numberLongToNumber(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> numberLongValueOf(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> numberLongToString(V8Scope* scope, const v8::Arguments& args); - -// NumberInt object -int numberIntVal(V8Scope* scope, const v8::Handle<v8::Object>& it); -v8::Handle<v8::Value> numberIntInit(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> numberIntToNumber(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> numberIntValueOf(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> numberIntToString(V8Scope* scope, const v8::Arguments& args); - -// NumberDecimal object -Decimal128 numberDecimalVal(V8Scope* scope, const v8::Handle<v8::Object>& it); -v8::Handle<v8::Value> numberDecimalInit(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> numberDecimalToString(V8Scope* scope, const v8::Arguments& args); - -// DBQuery object -v8::Handle<v8::Value> dbQueryInit(V8Scope* scope, const v8::Arguments& args); -v8::Handle<v8::Value> dbQueryIndexAccess(::uint32_t index, const v8::AccessorInfo& info); - -// db constructor -v8::Handle<v8::Value> dbInit(V8Scope* scope, const v8::Arguments& args); - -// collection constructor -v8::Handle<v8::Value> collectionInit(V8Scope* scope, const v8::Arguments& args); - -// ObjectId constructor -v8::Handle<v8::Value> objectIdInit(V8Scope* scope, const v8::Arguments& args); - -// DBRef constructor -v8::Handle<v8::Value> dbRefInit(V8Scope* scope, const v8::Arguments& args); - -// DBPointer constructor -v8::Handle<v8::Value> dbPointerInit(V8Scope* scope, const v8::Arguments& args); - -// Timestamp constructor -v8::Handle<v8::Value> dbTimestampInit(V8Scope* scope, const v8::Arguments& args); - -// UUID constructor -v8::Handle<v8::Value> uuidInit(V8Scope* scope, const v8::Arguments& args); - -// MD5 constructor -v8::Handle<v8::Value> md5Init(V8Scope* scope, const v8::Arguments& args); - -// HexData constructor -v8::Handle<v8::Value> hexDataInit(V8Scope* scope, const v8::Arguments& args); - -// Object.invalidForStorage() -v8::Handle<v8::Value> v8ObjectInvalidForStorage(V8Scope* scope, const v8::Arguments& args); - -// Object.bsonsize() -v8::Handle<v8::Value> bsonsize(V8Scope* scope, const v8::Arguments& args); - -// global method -// Accepts 2 objects, converts them to BSONObj and calls woCompare on the first against the -// second. -v8::Handle<v8::Value> bsonWoCompare(V8Scope* scope, const v8::Arguments& args); - -// 'db.collection' property handlers -v8::Handle<v8::Value> collectionGetter(v8::Local<v8::String> name, const v8::AccessorInfo& info); -v8::Handle<v8::Value> collectionSetter(v8::Local<v8::String> name, - v8::Local<v8::Value> value, - const v8::AccessorInfo& info); - -typedef stdx::function<void(V8Scope*, const v8::Handle<v8::FunctionTemplate>&)> - V8FunctionPrototypeManipulatorFn; - -void v8RegisterMongoPrototypeManipulator(const V8FunctionPrototypeManipulatorFn& manipulator); -} diff --git a/src/mongo/scripting/v8_profiler.cpp b/src/mongo/scripting/v8_profiler.cpp deleted file mode 100644 index 761cb2bf332..00000000000 --- a/src/mongo/scripting/v8_profiler.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright 2013 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault - -#include "mongo/scripting/v8_profiler.h" - -#include "mongo/util/log.h" -#include "mongo/util/mongoutils/str.h" - -namespace mongo { -void V8CpuProfiler::start(const StringData name) { - v8::CpuProfiler::StartProfiling(v8::String::New(name.toString().c_str())); -} - -void V8CpuProfiler::stop(const StringData name) { - _cpuProfiles.insert(make_pair( - name.toString(), - v8::CpuProfiler::StopProfiling(v8::String::New(name.toString().c_str(), name.size())))); -} - -void V8CpuProfiler::traverseDepthFirst(const v8::CpuProfileNode* cpuProfileNode, - BSONArrayBuilder& arrayBuilder) { - if (cpuProfileNode == NULL) - return; - BSONObjBuilder frameObjBuilder; - frameObjBuilder.append("Function", *v8::String::Utf8Value(cpuProfileNode->GetFunctionName())); - frameObjBuilder.append("Source", - *v8::String::Utf8Value(cpuProfileNode->GetScriptResourceName())); - frameObjBuilder.appendNumber("Line", cpuProfileNode->GetLineNumber()); - frameObjBuilder.appendNumber("SelfTime", cpuProfileNode->GetSelfTime()); - frameObjBuilder.appendNumber("TotalTime", cpuProfileNode->GetTotalTime()); - if (cpuProfileNode->GetChildrenCount()) { - BSONArrayBuilder subArrayBuilder(frameObjBuilder.subarrayStart("Children")); - for (int i = 0; i < cpuProfileNode->GetChildrenCount(); ++i) { - traverseDepthFirst(cpuProfileNode->GetChild(i), subArrayBuilder); - } - subArrayBuilder.done(); - } - arrayBuilder << frameObjBuilder.obj(); -} - -const BSONArray V8CpuProfiler::fetch(const StringData name) { - BSONArrayBuilder arrayBuilder; - CpuProfileMap::const_iterator iProf = _cpuProfiles.find(name.toString()); - if (iProf == _cpuProfiles.end()) - return arrayBuilder.arr(); - const v8::CpuProfile* cpuProfile = iProf->second; - if (cpuProfile == NULL) - return arrayBuilder.arr(); - traverseDepthFirst(cpuProfile->GetTopDownRoot(), arrayBuilder); - return arrayBuilder.arr(); -} -} diff --git a/src/mongo/scripting/v8_profiler.h b/src/mongo/scripting/v8_profiler.h deleted file mode 100644 index 41efce4f42a..00000000000 --- a/src/mongo/scripting/v8_profiler.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright 2013 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#pragma once - -#include <v8.h> -#include <v8-profiler.h> -#include <map> - -#include "mongo/base/string_data.h" -#include "mongo/db/jsobj.h" - -namespace mongo { - -/** Collect CPU Profiling data from v8. */ -class V8CpuProfiler { -public: - /** Start the CPU profiler */ - void start(const StringData name); - - /** Stop the CPU profiler */ - void stop(const StringData name); - - /** Get the current cpu profile */ - const BSONArray fetch(const StringData name); - -private: - void traverseDepthFirst(const v8::CpuProfileNode* cpuProfileNode, - BSONArrayBuilder& arrayBuilder); - - typedef std::map<std::string, const v8::CpuProfile*> CpuProfileMap; - CpuProfileMap _cpuProfiles; -}; -} diff --git a/src/mongo/scripting/v8_utils.cpp b/src/mongo/scripting/v8_utils.cpp deleted file mode 100644 index c7610fb0c20..00000000000 --- a/src/mongo/scripting/v8_utils.cpp +++ /dev/null @@ -1,406 +0,0 @@ -// v8_utils.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::kDefault - -#include "mongo/scripting/v8_utils.h" - -#include <cstdint> -#include <iostream> -#include <map> -#include <sstream> -#include <vector> - -#include "mongo/scripting/engine_v8.h" -#include "mongo/scripting/v8_db.h" -#include "mongo/stdx/condition_variable.h" -#include "mongo/stdx/mutex.h" -#include "mongo/stdx/thread.h" -#include "mongo/util/log.h" -#include "mongo/util/mongoutils/str.h" - -using namespace std; -using std::unique_ptr; - -namespace mongo { - -std::string toSTLString(const v8::Handle<v8::Value>& o) { - return StringData(V8String(o)).toString(); -} - -/** Get the properties of an object (and its prototype) as a comma-delimited string */ -std::string v8ObjectToString(const v8::Handle<v8::Object>& o) { - v8::Local<v8::Array> properties = o->GetPropertyNames(); - v8::String::Utf8Value str(properties); - massert(16696, "error converting js type to Utf8Value", *str); - return std::string(*str, str.length()); -} - -std::ostream& operator<<(std::ostream& s, const v8::Handle<v8::Value>& o) { - v8::String::Utf8Value str(o); - s << *str; - return s; -} - -std::ostream& operator<<(std::ostream& s, const v8::TryCatch* try_catch) { - v8::HandleScope handle_scope; - v8::String::Utf8Value exceptionText(try_catch->Exception()); - v8::Handle<v8::Message> message = try_catch->Message(); - - if (message.IsEmpty()) { - s << *exceptionText << endl; - } else { - v8::String::Utf8Value filename(message->GetScriptResourceName()); - int linenum = message->GetLineNumber(); - cout << *filename << ":" << linenum << " " << *exceptionText << endl; - - v8::String::Utf8Value sourceline(message->GetSourceLine()); - cout << *sourceline << endl; - - int start = message->GetStartColumn(); - for (int i = 0; i < start; i++) - cout << " "; - - int end = message->GetEndColumn(); - for (int i = start; i < end; i++) - cout << "^"; - - cout << endl; - } - return s; -} - -class JSThreadConfig { -public: - JSThreadConfig(V8Scope* scope, const v8::Arguments& args) - : _started(), _done(), _sharedData(new SharedData()) { - jsassert(args.Length() > 0, "need at least one argument"); - jsassert(args[0]->IsFunction(), "first argument must be a function"); - - // arguments need to be copied into the isolate, go through bson - BSONObjBuilder b; - for (int i = 0; i < args.Length(); ++i) { - scope->v8ToMongoElement(b, "arg" + BSONObjBuilder::numStr(i), args[i]); - } - _sharedData->_args = b.obj(); - } - - ~JSThreadConfig() {} - - void start() { - jsassert(!_started, "Thread already started"); - JSThread jt(*this); - _thread.reset(new stdx::thread(jt)); - _started = true; - } - void join() { - jsassert(_started && !_done, "Thread not running"); - _thread->join(); - _done = true; - } - - /** - * Returns true if the JSThread terminated as a result of an error - * during its execution, and false otherwise. This operation does - * not block, nor does it require join() to have been called. - */ - bool hasFailed() const { - jsassert(_started, "Thread not started"); - return _sharedData->getErrored(); - } - - BSONObj returnData() { - if (!_done) - join(); - return _sharedData->_returnData; - } - -private: - /* - * JSThreadConfig doesn't always outlive its JSThread (for example, if the parent thread - * garbage collects the JSThreadConfig before the JSThread has finished running), so any - * data shared between them has to go in a shared_ptr. - */ - class SharedData { - public: - SharedData() : _errored(false) {} - BSONObj _args; - BSONObj _returnData; - void setErrored(bool value) { - stdx::lock_guard<stdx::mutex> lck(_erroredMutex); - _errored = value; - } - bool getErrored() { - stdx::lock_guard<stdx::mutex> lck(_erroredMutex); - return _errored; - } - - private: - stdx::mutex _erroredMutex; - bool _errored; - }; - - class JSThread { - public: - JSThread(JSThreadConfig& config) : _sharedData(config._sharedData) {} - - void operator()() { - try { - unique_ptr<V8Scope> scope(static_cast<V8Scope*>(globalScriptEngine->newScope())); - v8::Locker v8lock(scope->getIsolate()); - v8::Isolate::Scope iscope(scope->getIsolate()); - v8::HandleScope handle_scope; - v8::Context::Scope context_scope(scope->getContext()); - - BSONObj args = _sharedData->_args; - v8::Local<v8::Function> f = - v8::Function::Cast(*(scope->mongoToV8Element(args.firstElement(), true))); - int argc = args.nFields() - 1; - - // TODO SERVER-8016: properly allocate handles on the stack - v8::Local<v8::Value> argv[24]; - BSONObjIterator it(args); - it.next(); - for (int i = 0; i < argc && i < 24; ++i) { - argv[i] = v8::Local<v8::Value>::New(scope->mongoToV8Element(*it, true)); - it.next(); - } - v8::TryCatch try_catch; - v8::Handle<v8::Value> ret = f->Call(scope->getContext()->Global(), argc, argv); - if (ret.IsEmpty() || try_catch.HasCaught()) { - string e = scope->v8ExceptionToSTLString(&try_catch); - log() << "js thread raised js exception: " << e << endl; - ret = v8::Undefined(); - _sharedData->setErrored(true); - } - // ret is translated to BSON to switch isolate - BSONObjBuilder b; - scope->v8ToMongoElement(b, "ret", ret); - _sharedData->_returnData = b.obj(); - } catch (const DBException& e) { - // Keeping behavior the same as for js exceptions. - log() << "js thread threw c++ exception: " << e.toString(); - _sharedData->setErrored(true); - _sharedData->_returnData = BSON("ret" << BSONUndefined); - } catch (const std::exception& e) { - log() << "js thread threw c++ exception: " << e.what(); - _sharedData->setErrored(true); - _sharedData->_returnData = BSON("ret" << BSONUndefined); - } catch (...) { - log() << "js thread threw c++ non-exception"; - _sharedData->setErrored(true); - _sharedData->_returnData = BSON("ret" << BSONUndefined); - } - } - - private: - std::shared_ptr<SharedData> _sharedData; - }; - - bool _started; - bool _done; - unique_ptr<stdx::thread> _thread; - std::shared_ptr<SharedData> _sharedData; -}; - -class CountDownLatchHolder { -private: - struct Latch { - Latch(int32_t count) : count(count) {} - stdx::condition_variable cv; - stdx::mutex mutex; - int32_t count; - }; - - std::shared_ptr<Latch> get(int32_t desc) { - stdx::lock_guard<stdx::mutex> lock(_mutex); - Map::iterator iter = _latches.find(desc); - jsassert(iter != _latches.end(), "not a valid CountDownLatch descriptor"); - return iter->second; - } - - typedef std::map<int32_t, std::shared_ptr<Latch>> Map; - Map _latches; - stdx::mutex _mutex; - int32_t _counter; - -public: - CountDownLatchHolder() : _counter(0) {} - int32_t make(int32_t count) { - jsassert(count >= 0, "argument must be >= 0"); - stdx::lock_guard<stdx::mutex> lock(_mutex); - int32_t desc = ++_counter; - _latches.insert(std::make_pair(desc, std::make_shared<Latch>(count))); - return desc; - } - void await(int32_t desc) { - std::shared_ptr<Latch> latch = get(desc); - stdx::unique_lock<stdx::mutex> lock(latch->mutex); - while (latch->count != 0) { - latch->cv.wait(lock); - } - } - void countDown(int32_t desc) { - std::shared_ptr<Latch> latch = get(desc); - stdx::unique_lock<stdx::mutex> lock(latch->mutex); - if (latch->count > 0) { - latch->count--; - } - if (latch->count == 0) { - latch->cv.notify_all(); - } - } - int32_t getCount(int32_t desc) { - std::shared_ptr<Latch> latch = get(desc); - stdx::unique_lock<stdx::mutex> lock(latch->mutex); - return latch->count; - } -}; -namespace { -CountDownLatchHolder globalCountDownLatchHolder; -} - -v8::Handle<v8::Value> CountDownLatchNew(V8Scope* scope, const v8::Arguments& args) { - jsassert(args.Length() == 1, "need exactly one argument"); - jsassert(args[0]->IsInt32(), "argument must be an integer"); - int32_t count = v8::Local<v8::Integer>::Cast(args[0])->Value(); - return v8::Int32::New(globalCountDownLatchHolder.make(count)); -} - -v8::Handle<v8::Value> CountDownLatchAwait(V8Scope* scope, const v8::Arguments& args) { - jsassert(args.Length() == 1, "need exactly one argument"); - jsassert(args[0]->IsInt32(), "argument must be an integer"); - globalCountDownLatchHolder.await(args[0]->ToInt32()->Value()); - return v8::Undefined(); -} - -v8::Handle<v8::Value> CountDownLatchCountDown(V8Scope* scope, const v8::Arguments& args) { - jsassert(args.Length() == 1, "need exactly one argument"); - jsassert(args[0]->IsInt32(), "argument must be an integer"); - globalCountDownLatchHolder.countDown(args[0]->ToInt32()->Value()); - return v8::Undefined(); -} - -v8::Handle<v8::Value> CountDownLatchGetCount(V8Scope* scope, const v8::Arguments& args) { - jsassert(args.Length() == 1, "need exactly one argument"); - jsassert(args[0]->IsInt32(), "argument must be an integer"); - return v8::Int32::New(globalCountDownLatchHolder.getCount(args[0]->ToInt32()->Value())); -} - -JSThreadConfig* thisConfig(V8Scope* scope, const v8::Arguments& args) { - v8::Local<v8::External> c = - v8::External::Cast(*(args.This()->GetHiddenValue(v8::String::New("_JSThreadConfig")))); - JSThreadConfig* config = static_cast<std::shared_ptr<JSThreadConfig>*>(c->Value())->get(); - return config; -} - -v8::Handle<v8::Value> ThreadInit(V8Scope* scope, const v8::Arguments& args) { - v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.This()); - - JSThreadConfig* config = new JSThreadConfig(scope, args); - v8::Local<v8::External> handle = scope->jsThreadConfigTracker.track(self, config); - args.This()->SetHiddenValue(v8::String::New("_JSThreadConfig"), handle); - - invariant(thisConfig(scope, args) == config); - return v8::Undefined(); -} - -v8::Handle<v8::Value> ScopedThreadInit(V8Scope* scope, const v8::Arguments& args) { - // NOTE: ScopedThread and Thread behave identically because a new V8 Isolate is always - // created. - return ThreadInit(scope, args); -} - -v8::Handle<v8::Value> ThreadStart(V8Scope* scope, const v8::Arguments& args) { - thisConfig(scope, args)->start(); - return v8::Undefined(); -} - -v8::Handle<v8::Value> ThreadJoin(V8Scope* scope, const v8::Arguments& args) { - thisConfig(scope, args)->join(); - return v8::Undefined(); -} - -// Indicates to the caller that the thread terminated as a result of an error. -v8::Handle<v8::Value> ThreadHasFailed(V8Scope* scope, const v8::Arguments& args) { - bool hasFailed = thisConfig(scope, args)->hasFailed(); - return v8::Boolean::New(hasFailed); -} - -v8::Handle<v8::Value> ThreadReturnData(V8Scope* scope, const v8::Arguments& args) { - BSONObj data = thisConfig(scope, args)->returnData(); - return scope->mongoToV8Element(data.firstElement(), true); -} - -v8::Handle<v8::Value> ThreadInject(V8Scope* scope, const v8::Arguments& args) { - v8::HandleScope handle_scope; - jsassert(args.Length() == 1, "threadInject takes exactly 1 argument"); - jsassert(args[0]->IsObject(), "threadInject needs to be passed a prototype"); - v8::Local<v8::Object> o = args[0]->ToObject(); - - // install method on the Thread object - scope->injectV8Function("init", ThreadInit, o); - scope->injectV8Function("start", ThreadStart, o); - scope->injectV8Function("join", ThreadJoin, o); - scope->injectV8Function("hasFailed", ThreadHasFailed, o); - scope->injectV8Function("returnData", ThreadReturnData, o); - return handle_scope.Close(v8::Handle<v8::Value>()); -} - -v8::Handle<v8::Value> ScopedThreadInject(V8Scope* scope, const v8::Arguments& args) { - v8::HandleScope handle_scope; - jsassert(args.Length() == 1, "threadInject takes exactly 1 argument"); - jsassert(args[0]->IsObject(), "threadInject needs to be passed a prototype"); - v8::Local<v8::Object> o = args[0]->ToObject(); - - scope->injectV8Function("init", ScopedThreadInit, o); - // inheritance takes care of other member functions - - return handle_scope.Close(v8::Handle<v8::Value>()); -} - -void installFork(V8Scope* scope, v8::Handle<v8::Object>& global, v8::Handle<v8::Context>& context) { - scope->injectV8Function("_threadInject", ThreadInject, global); - scope->injectV8Function("_scopedThreadInject", ScopedThreadInject, global); - - scope->setObject("CountDownLatch", BSONObj(), false); - v8::Handle<v8::Object> cdl = scope->get("CountDownLatch").As<v8::Object>(); - scope->injectV8Function("_new", CountDownLatchNew, cdl); - scope->injectV8Function("_await", CountDownLatchAwait, cdl); - scope->injectV8Function("_countDown", CountDownLatchCountDown, cdl); - scope->injectV8Function("_getCount", CountDownLatchGetCount, cdl); -} - -v8::Handle<v8::Value> v8AssertionException(const char* errorMessage) { - return v8::ThrowException(v8::Exception::Error(v8::String::New(errorMessage))); -} -v8::Handle<v8::Value> v8AssertionException(const std::string& errorMessage) { - return v8AssertionException(errorMessage.c_str()); -} -} diff --git a/src/mongo/scripting/v8_utils.h b/src/mongo/scripting/v8_utils.h deleted file mode 100644 index 47c7a9dfccb..00000000000 --- a/src/mongo/scripting/v8_utils.h +++ /dev/null @@ -1,117 +0,0 @@ -// v8_utils.h - -/* Copyright 2009 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <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. - */ - -#pragma once - -#include <cstdio> -#include <cstdlib> -#include <string> -#include <v8.h> - -#include <mongo/base/string_data.h> -#include <mongo/util/assert_util.h> - -namespace mongo { - -#define jsassert(x, msg) uassert(16664, (msg), (x)) - -#define argumentCheck(mustBeTrue, errorMessage) \ - if (!(mustBeTrue)) { \ - return v8AssertionException((errorMessage)); \ - } - -std::ostream& operator<<(std::ostream& s, const v8::Handle<v8::Value>& o); -std::ostream& operator<<(std::ostream& s, const v8::Handle<v8::TryCatch>* try_catch); - -/** Simple v8 object to std::string conversion helper */ -std::string toSTLString(const v8::Handle<v8::Value>& o); - -/** Like toSTLString but doesn't allocate a new std::string - * - * This owns the string's memory so you need to be careful not to let the - * converted StringDatas outlive the V8Scope object. These rules are the - * same as converting from a std::string into a StringData. - * - * Safe: - * void someFunction(StringData argument); - * v8::Handle<v8::String> aString; - * - * someFunction(V8String(aString)); // passing down stack as temporary - * - * V8String named (aString); - * someFunction(named); // passing up stack as named value - * - * StringData sd = named; // scope of sd is less than named - * - * Unsafe: - * StringData _member; - * - * StringData returningFunction() { - * StringData sd = V8String(aString); // sd outlives the temporary - * - * V8String named(aString) - * _member = named; // _member outlives named scope - * - * return V8String(aString); // passing up stack - * } - */ -class V8String { -public: - explicit V8String(const v8::Handle<v8::Value>& o) : _str(o) { - massert(16686, "error converting js type to Utf8Value", *_str); - } - operator StringData() const { - return StringData(*_str, _str.length()); - } - -private: - v8::String::Utf8Value _str; -}; - -/** Get the properties of an object (and it's prototype) as a comma-delimited std::string */ -std::string v8ObjectToString(const v8::Handle<v8::Object>& o); - -class V8Scope; -void installFork(V8Scope* scope, v8::Handle<v8::Object>& global, v8::Handle<v8::Context>& context); - -/** Throw a V8 exception from Mongo callback code; message text will be preceded by "Error: ". - * Note: this function should be used for text that did not originate from the JavaScript - * engine. Errors from the JavaScript engine will already have a prefix such as - * ReferenceError, TypeError or SyntaxError. - * Note: call only from a native function called from JavaScript (a callback). - * The V8 ThrowException routine will note a JavaScript exception that will be - * "thrown" in JavaScript when we return from the native function. - * Note: it's required to return immediately to V8's execution control without calling any - * V8 API functions. In this state, an empty handle may (will) be returned. - * @param errorMessage Error message text. - * @return Empty handle to be returned from callback function. - */ -v8::Handle<v8::Value> v8AssertionException(const char* errorMessage); -v8::Handle<v8::Value> v8AssertionException(const std::string& errorMessage); -} diff --git a/src/third_party/SConscript b/src/third_party/SConscript index 5b533e84967..3c6a9b0d869 100644 --- a/src/third_party/SConscript +++ b/src/third_party/SConscript @@ -1,6 +1,6 @@ # -*- mode: python -*- -Import("env use_system_version_of_library usemozjs usev8 v8suffix boostSuffix") +Import("env use_system_version_of_library usemozjs boostSuffix") Import("wiredtiger") snappySuffix = '-1.1.2' @@ -33,10 +33,6 @@ if not use_system_version_of_library('zlib'): thirdPartyIncludePathList.append( ('zlib', '#/src/third_party/zlib' + zlibSuffix)) -if not use_system_version_of_library('v8'): - thirdPartyIncludePathList.append( - ('v8', '#/src/third_party/v8' + v8suffix + '/include')) - # TODO: figure out if we want to offer system versions of mozjs. Mozilla # hasn't offered a source tarball since 24, but in theory they could. # @@ -194,27 +190,6 @@ zlibEnv.Library( 'shim_zlib.cpp', ]) -if usev8: - if use_system_version_of_library("v8"): - v8Env = env.Clone( - SYSLIBDEPS=[ - env['LIBDEPS_V8_SYSLIBDEP'], - ]) - else: - v8Env = env.Clone() - v8Env.InjectThirdPartyIncludePaths(libraries=['v8']) - v8Env.SConscript('v8' + v8suffix + '/SConscript', exports={'env' : v8Env }) - v8Env = v8Env.Clone( - LIBDEPS=[ - 'v8' + v8suffix + '/v8' - ]) - - v8Env.Library( - target="shim_v8", - source=[ - 'shim_v8.cpp', - ]) - if usemozjs: mozjsEnv = env.Clone() mozjsEnv.SConscript('mozjs' + mozjsSuffix + '/SConscript', exports={'env' : mozjsEnv }) |