summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathias Stearn <mathias@10gen.com>2013-06-05 18:23:46 -0400
committerDan Pasette <dan@10gen.com>2013-06-18 12:40:48 -0400
commit7c1b35e0b2cc69c93074c6d1d76879b3ed525f56 (patch)
tree6cd18cb6c7e000104308166fdfee725938b7c078
parentd3f27364cb97bac645f5c1c2c7d7dae247bfe974 (diff)
downloadmongo-7c1b35e0b2cc69c93074c6d1d76879b3ed525f56.tar.gz
SERVER-9878 Add type checks to V8 C++ bindings
The main focus of this ticket is tightening up input validation in our V8 bindings. Doing this required normalizing the way we create custom types in JS that have special C++-driven behavior. All special types now use FunctionTemplates that are attached to the V8Scope object. This allows us to test if an object is of the correct type before using it. Other related tickets partially addressed: SERVER-8961 Differences in argument validation of custom types between v8 and Spidermonkey SERVER-9803 Handle regular expression parse errors without seg faulting Conflicts: jstests/constructors.js src/mongo/scripting/engine_v8.cpp
-rw-r--r--jstests/constructors.js230
-rw-r--r--jstests/dbref3.js22
-rw-r--r--src/mongo/dbtests/jstests.cpp13
-rw-r--r--src/mongo/scripting/engine_v8.cpp727
-rw-r--r--src/mongo/scripting/engine_v8.h150
-rw-r--r--src/mongo/scripting/v8_db.cpp437
-rw-r--r--src/mongo/scripting/v8_db.h19
7 files changed, 979 insertions, 619 deletions
diff --git a/jstests/constructors.js b/jstests/constructors.js
new file mode 100644
index 00000000000..6d9e982787d
--- /dev/null
+++ b/jstests/constructors.js
@@ -0,0 +1,230 @@
+// Tests to see what validity checks are done for 10gen specific object construction
+
+function clientEvalConstructorTest (constructorList) {
+ var i;
+ constructorList.valid.forEach(function (constructor) {
+ try {
+ eval(constructor);
+ }
+ catch (e) {
+ throw ("valid constructor: " + constructor + " failed in eval context: " + e);
+ }
+ });
+ constructorList.invalid.forEach(function (constructor) {
+ assert.throws(function () { eval(constructor) },
+ [], "invalid constructor did not throw error in eval context: " + constructor);
+ });
+}
+
+function dbEvalConstructorTest (constructorList) {
+ var i;
+ constructorList.valid.forEach(function (constructor) {
+ try {
+ db.eval(constructor);
+ }
+ catch (e) {
+ throw ("valid constructor: " + constructor + " failed in db.eval context: " + e);
+ }
+ });
+ constructorList.invalid.forEach(function (constructor) {
+ assert.throws(function () { db.eval(constructor) },
+ [], "invalid constructor did not throw error in db.eval context: " + constructor);
+ });
+}
+
+function mapReduceConstructorTest (constructorList) {
+ t = db.mr_constructors;
+ t.drop();
+
+ t.save( { "partner" : 1, "visits" : 9 } )
+ t.save( { "partner" : 2, "visits" : 9 } )
+ t.save( { "partner" : 1, "visits" : 11 } )
+ t.save( { "partner" : 1, "visits" : 30 } )
+ t.save( { "partner" : 2, "visits" : 41 } )
+ t.save( { "partner" : 2, "visits" : 41 } )
+
+ constructorList.valid.forEach(function (constructor) {
+ try {
+ m = eval("dummy = function(){ emit( \"test\" , " + constructor + " ) }");
+
+ r = eval("dummy = function( k , v ){ return { test : " + constructor + " } }");
+
+ res = t.mapReduce( m , r , { out : "mr_constructors_out" , scope : { xx : 1 } } );
+ }
+ catch (e) {
+ throw ("valid constructor: " + constructor + " failed in mapReduce context: " + e);
+ }
+ });
+ constructorList.invalid.forEach(function (constructor) {
+ m = eval("dummy = function(){ emit( \"test\" , " + constructor + " ) }");
+
+ r = eval("dummy = function( k , v ){ return { test : " + constructor + " } }");
+
+ assert.throws(function () { res = t.mapReduce( m , r ,
+ { out : "mr_constructors_out" , scope : { xx : 1 } } ) },
+ [], "invalid constructor did not throw error in mapReduce context: " + constructor);
+ });
+
+ db.mr_constructors_out.drop();
+ t.drop();
+}
+
+function whereConstructorTest (constructorList) {
+ t = db.where_constructors;
+ t.drop();
+ t.insert({ x : 1 });
+ assert(!db.getLastError());
+
+ constructorList.valid.forEach(function (constructor) {
+ try {
+ t.findOne({ $where : constructor });
+ }
+ catch (e) {
+ throw ("valid constructor: " + constructor + " failed in $where query: " + e);
+ }
+ });
+ constructorList.invalid.forEach(function (constructor) {
+ assert.throws(function () { t.findOne({ $where : constructor }) },
+ [], "invalid constructor did not throw error in $where query: " + constructor);
+ });
+}
+
+var dbrefConstructors = {
+ "valid" : [
+ "DBRef(\"namespace\", 0)",
+ "DBRef(\"namespace\", \"test\")",
+ "DBRef(\"namespace\", ObjectId())",
+ "DBRef(\"namespace\", ObjectId(\"000000000000000000000000\"))",
+ ],
+ "invalid" : [
+ "DBRef()",
+ "DBRef(true, ObjectId())",
+ "DBRef(\"namespace\")",
+ "DBRef(\"namespace\", ObjectId(), true)"
+ ]
+}
+
+var dbpointerConstructors = {
+ "valid" : [
+ "DBPointer(\"namespace\", ObjectId())",
+ "DBPointer(\"namespace\", ObjectId(\"000000000000000000000000\"))",
+ ],
+ "invalid" : [
+ "DBPointer()",
+ "DBPointer(true, ObjectId())",
+ "DBPointer(\"namespace\", 0)",
+ "DBPointer(\"namespace\", \"test\")",
+ "DBPointer(\"namespace\")",
+ "DBPointer(\"namespace\", ObjectId(), true)"
+ ]
+}
+
+
+var objectidConstructors = {
+ "valid" : [
+ 'ObjectId()',
+ 'ObjectId("FFFFFFFFFFFFFFFFFFFFFFFF")',
+ 'new ObjectId()',
+ 'new ObjectId("FFFFFFFFFFFFFFFFFFFFFFFF")'
+ ],
+ "invalid" : [
+ 'ObjectId(5)',
+ 'ObjectId("FFFFFFFFFFFFFFFFFFFFFFFQ")',
+ 'new ObjectId(5)',
+ 'new ObjectId("FFFFFFFFFFFFFFFFFFFFFFFQ")'
+ ]
+}
+
+var timestampConstructors = {
+ "valid" : [
+ 'Timestamp()',
+ 'Timestamp(0,0)',
+ 'new Timestamp()',
+ 'new Timestamp(0,0)',
+ 'Timestamp(1.0,1.0)',
+ 'new Timestamp(1.0,1.0)',
+ ],
+ "invalid" : [
+ 'Timestamp(0)',
+ 'Timestamp(0,0,0)',
+ 'new Timestamp(0)',
+ 'new Timestamp(0,0,0)',
+ 'Timestamp("test","test")',
+ 'Timestamp("test",0)',
+ 'Timestamp(0,"test")',
+ 'new Timestamp("test","test")',
+ 'new Timestamp("test",0)',
+ 'new Timestamp(0,"test")',
+ 'Timestamp(true,true)',
+ 'Timestamp(true,0)',
+ 'Timestamp(0,true)',
+ 'new Timestamp(true,true)',
+ 'new Timestamp(true,0)',
+ 'new Timestamp(0,true)'
+ ]
+}
+
+var bindataConstructors = {
+ "valid" : [
+ 'BinData(0,"test")',
+ //'BinData()', SERVER-8961
+ 'new BinData(0,"test")',
+ //'new BinData()' SERVER-8961
+ ],
+ "invalid" : [
+ 'BinData(0,"test", "test")',
+ 'new BinData(0,"test", "test")'
+ ]
+}
+
+var dateConstructors = {
+ "valid" : [
+ 'Date()',
+ 'Date(0)',
+ 'Date(0,0)',
+ 'Date(0,0,0)',
+ 'Date("foo")',
+ 'new Date()',
+ 'new Date(0)',
+ 'new Date(0,0)',
+ 'new Date(0,0,0)',
+ 'new Date(0,0,0,0)',
+ 'new Date("foo")'
+ ],
+ "invalid" : [
+ ]
+}
+
+clientEvalConstructorTest(dbrefConstructors);
+clientEvalConstructorTest(dbpointerConstructors);
+clientEvalConstructorTest(objectidConstructors);
+clientEvalConstructorTest(timestampConstructors);
+clientEvalConstructorTest(bindataConstructors);
+clientEvalConstructorTest(dateConstructors);
+
+dbEvalConstructorTest(dbrefConstructors);
+dbEvalConstructorTest(dbpointerConstructors);
+dbEvalConstructorTest(objectidConstructors);
+dbEvalConstructorTest(timestampConstructors);
+dbEvalConstructorTest(bindataConstructors);
+dbEvalConstructorTest(dateConstructors);
+
+// SERVER-8963
+if (db.runCommand({buildinfo:1}).javascriptEngine == "V8") {
+ mapReduceConstructorTest(dbrefConstructors);
+ mapReduceConstructorTest(dbpointerConstructors);
+ mapReduceConstructorTest(objectidConstructors);
+ mapReduceConstructorTest(timestampConstructors);
+ mapReduceConstructorTest(bindataConstructors);
+}
+mapReduceConstructorTest(dateConstructors);
+
+// SERVER-8963
+if (db.runCommand({buildinfo:1}).javascriptEngine == "V8") {
+ whereConstructorTest(dbrefConstructors);
+ whereConstructorTest(dbpointerConstructors);
+ whereConstructorTest(objectidConstructors);
+ whereConstructorTest(timestampConstructors);
+ whereConstructorTest(bindataConstructors);
+}
+whereConstructorTest(dateConstructors);
diff --git a/jstests/dbref3.js b/jstests/dbref3.js
new file mode 100644
index 00000000000..72991aff7ef
--- /dev/null
+++ b/jstests/dbref3.js
@@ -0,0 +1,22 @@
+// Make sure we only make a DBRef object for objects where the first field is a string named $ref
+// and the second field is $id with any type. Only the first two fields matter for deciding if it
+// is a DBRef. See http://docs.mongodb.org/manual/reference/database-references/#dbrefs.
+
+var t = db.dbref3
+
+// true cases
+t.insert({sub: {$ref: "foo", $id: "bar"}, dbref: true});
+t.insert({sub: {$ref: "foo", $id: "bar", $db: "baz"}, dbref: true});
+t.insert({sub: {$ref: "foo", $id: "bar", db: "baz"}, dbref: true}); // out of spec but accepted
+t.insert({sub: {$ref: "foo", $id: ObjectId()}, dbref: true});
+t.insert({sub: {$ref: "foo", $id: 1}, dbref: true});
+
+t.insert({sub: {$ref: 123/*not a string*/, $id: "bar"}, dbref: false});
+t.insert({sub: {$id: "bar", $ref: "foo"}, dbref: false});
+t.insert({sub: {$ref: "foo"}, dbref: false});
+t.insert({sub: {$id: "foo"}, dbref: false});
+t.insert({sub: {other: 1, $ref: "foo", $id: "bar"}, dbref: false});
+
+t.find().forEach(function(obj) {
+ assert.eq(obj.sub.constructor == DBRef, obj.dbref, tojson(obj));
+});
diff --git a/src/mongo/dbtests/jstests.cpp b/src/mongo/dbtests/jstests.cpp
index 67262de778b..b1ba53cc1f1 100644
--- a/src/mongo/dbtests/jstests.cpp
+++ b/src/mongo/dbtests/jstests.cpp
@@ -438,6 +438,19 @@ namespace JSTests {
ASSERT_EQUALS( (string)"^a" , out["a"].regex() );
ASSERT_EQUALS( (string)"i" , out["a"].regexFlags() );
+ // This regex used to cause a segfault because x isn't a valid flag for a js RegExp.
+ // Now it throws a JS exception.
+ BSONObj invalidRegex = BSON_ARRAY(BSON("regex" << BSONRegEx("asdf", "x")));
+ const char* code = "function (obj) {"
+ " var threw = false;"
+ " try {"
+ " obj.regex;" // should throw
+ " } catch(e) {"
+ " threw = true;"
+ " }"
+ " assert(threw);"
+ "}";
+ ASSERT_EQUALS(s->invoke(code, &invalidRegex, NULL), 0);
}
// array
diff --git a/src/mongo/scripting/engine_v8.cpp b/src/mongo/scripting/engine_v8.cpp
index 5f9c8eb7330..7ffebec383e 100644
--- a/src/mongo/scripting/engine_v8.cpp
+++ b/src/mongo/scripting/engine_v8.cpp
@@ -33,42 +33,47 @@ namespace mongo {
extern const JSFile assert;
}
- static V8Scope* getScope(v8::Isolate* isolate) {
- return static_cast<V8Scope*>(isolate->GetData());
- }
+ // 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;
- /**
- * Unwraps a BSONObj from the JS wrapper
- */
- static BSONObj unwrapBSONObj(const v8::Handle<v8::Object>& obj) {
v8::Handle<v8::External> field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0));
- if (field.IsEmpty() || !field->IsExternal()) {
- return BSONObj();
- }
+ if (field.IsEmpty() || !field->IsExternal())
+ return 0;
void* ptr = field->Value();
- return ((BSONHolder*)ptr)->_obj;
+ return (BSONHolder*)ptr;
}
- static BSONHolder* unwrapHolder(const v8::Handle<v8::Object>& obj) {
- 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(const v8::Handle<v8::Object>& obj) {
- return obj->GetInternalField(1).As<v8::Object>();
+ 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>();
}
- v8::Persistent<v8::Object> V8Scope::wrapBSONObject(v8::Local<v8::Object> obj,
- BSONHolder* data) {
- data->_scope = this;
- obj->SetInternalField(0, v8::External::New(data)); // Holder
+ 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, data);
- return p;
+ bsonHolderTracker.track(p, holder);
}
static v8::Handle<v8::Value> namedGet(v8::Local<v8::String> name,
@@ -76,14 +81,16 @@ namespace mongo {
v8::HandleScope handle_scope;
v8::Handle<v8::Value> val;
try {
- v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ 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(info.Holder());
+ BSONHolder* holder = unwrapHolder(scope, info.Holder());
if (!holder || holder->_removed.count(key))
return handle_scope.Close(v8::Handle<v8::Value>());
@@ -92,7 +99,6 @@ namespace mongo {
if (elmt.eoo())
return handle_scope.Close(v8::Handle<v8::Value>());
- V8Scope* scope = getScope(info.GetIsolate());
val = scope->mongoToV8Element(elmt, holder->_readOnly);
if (obj.objsize() > 128 || val->IsObject()) {
@@ -118,50 +124,32 @@ namespace mongo {
static v8::Handle<v8::Value> namedGetRO(v8::Local<v8::String> name,
const v8::AccessorInfo &info) {
return namedGet(name, info);
- // Rest of function is unused but left in to ease backporting of SERVER-9267
-
- v8::HandleScope handle_scope;
- v8::Handle<v8::Value> val;
- string key = toSTLString(name);
- try {
- BSONObj obj = unwrapBSONObj(info.Holder());
- BSONElement elmt = obj.getField(key.c_str());
- if (elmt.eoo())
- return handle_scope.Close(v8::Handle<v8::Value>());
- V8Scope* scope = getScope(info.GetIsolate());
- val = scope->mongoToV8Element(elmt, true);
- }
- catch (const DBException &dbEx) {
- return v8AssertionException(dbEx.toString());
- }
- catch (...) {
- return v8AssertionException(string("error getting read-only property ") + key);
- }
- return handle_scope.Close(val);
}
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);
- BSONHolder* holder = unwrapHolder(info.Holder());
+ 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(info.Holder());
+ 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;
- BSONHolder* holder = unwrapHolder(info.Holder());
+ 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;
- V8Scope* scope = getScope(info.GetIsolate());
unordered_set<StringData, StringData::Hasher> added;
// note here that if keys are parseable number, v8 will access them using index
@@ -177,7 +165,8 @@ namespace mongo {
}
- v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ 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++) {
@@ -193,12 +182,14 @@ namespace mongo {
v8::Handle<v8::Boolean> namedDelete(v8::Local<v8::String> name, const v8::AccessorInfo& info) {
v8::HandleScope handle_scope;
string key = toSTLString(name);
- BSONHolder* holder = unwrapHolder(info.Holder());
+ 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(info.Holder());
+ v8::Handle<v8::Object> realObject = unwrapObject(scope, info.Holder());
+ if (realObject.IsEmpty()) return v8::Handle<v8::Boolean>();
realObject->Delete(name);
return v8::True();
}
@@ -207,15 +198,16 @@ namespace mongo {
v8::HandleScope handle_scope;
v8::Handle<v8::Value> val;
try {
- v8::Handle<v8::Object> realObject = unwrapObject(info.Holder());
+ 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;
- V8Scope* scope = getScope(info.GetIsolate());
- BSONHolder* holder = unwrapHolder(info.Holder());
+ 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>());
@@ -245,55 +237,34 @@ namespace mongo {
v8::Handle<v8::Boolean> indexedDelete(uint32_t index, const v8::AccessorInfo& info) {
string key = str::stream() << index;
- BSONHolder* holder = unwrapHolder(info.Holder());
+ 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(info.Holder());
+ 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);
- // Rest of function is unused but left in-place to ease backporting of SERVER-9267
-
- v8::HandleScope handle_scope;
- v8::Handle<v8::Value> val;
- try {
- string key = str::stream() << index;
- V8Scope* scope = getScope(info.GetIsolate());
-
- BSONObj obj = unwrapBSONObj(info.Holder());
- BSONElement elmt = obj.getField(key);
-
- if (elmt.eoo())
- return handle_scope.Close(v8::Handle<v8::Value>());
-
- val = scope->mongoToV8Element(elmt, true);
- }
- catch (const DBException &dbEx) {
- return v8AssertionException(dbEx.toString());
- }
- catch (...) {
- return v8AssertionException(str::stream()
- << "error getting read-only indexed property "
- << index);
- }
- return handle_scope.Close(val);
}
static v8::Handle<v8::Value> indexedSet(uint32_t index, v8::Local<v8::Value> value_obj,
const v8::AccessorInfo& info) {
string key = str::stream() << index;
- BSONHolder* holder = unwrapHolder(info.Holder());
+ 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(info.Holder());
+ 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;
}
@@ -448,7 +419,8 @@ namespace mongo {
void V8Scope::kill() {
mongo::mutex::scoped_lock interruptLock(_interruptLock);
if (!_inNativeExecution) {
- // set the TERMINATE flag on the stack guard for this isolate
+ // 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: " << _isolate << endl;
}
@@ -508,43 +480,33 @@ namespace mongo {
// 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
- lzObjectTemplate = v8::Persistent<v8::ObjectTemplate>::New(v8::ObjectTemplate::New());
- lzObjectTemplate->SetInternalFieldCount(2);
- lzObjectTemplate->SetNamedPropertyHandler(namedGet, namedSet, 0, namedDelete,
- namedEnumerator);
- lzObjectTemplate->SetIndexedPropertyHandler(indexedGet, indexedSet, 0, indexedDelete,
- namedEnumerator);
- lzObjectTemplate->NewInstance()->GetPrototype()->ToObject()->ForceSet(
- strLitToV8("_bson"),
- v8::Boolean::New(true),
- v8::DontEnum);
-
- roObjectTemplate = v8::Persistent<v8::ObjectTemplate>::New(v8::ObjectTemplate::New());
- roObjectTemplate->SetInternalFieldCount(2);
- roObjectTemplate->SetNamedPropertyHandler(namedGetRO, NamedReadOnlySet, 0,
- NamedReadOnlyDelete, namedEnumerator);
- roObjectTemplate->SetIndexedPropertyHandler(indexedGetRO, IndexedReadOnlySet, 0,
- IndexedReadOnlyDelete, 0);
- roObjectTemplate->NewInstance()->GetPrototype()->ToObject()->ForceSet(
- strLitToV8("_bson"),
- v8::Boolean::New(true),
- v8::DontEnum);
-
- // initialize lazy array template
- // unfortunately it is not possible to create true v8 array from a template
- // this means we use an object template and copy methods over
- // this it creates issues when calling certain methods that check array type
- lzArrayTemplate = v8::Persistent<v8::ObjectTemplate>::New(v8::ObjectTemplate::New());
- lzArrayTemplate->SetInternalFieldCount(1);
- lzArrayTemplate->SetIndexedPropertyHandler(indexedGet, 0, 0, 0, 0);
- lzArrayTemplate->NewInstance()->GetPrototype()->ToObject()->ForceSet(
- strLitToV8("_bson"),
- v8::Boolean::New(true),
- v8::DontEnum);
-
- internalFieldObjects = v8::Persistent<v8::ObjectTemplate>::New(v8::ObjectTemplate::New());
- internalFieldObjects->SetInternalFieldCount(1);
+ _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
@@ -578,11 +540,9 @@ namespace mongo {
_funcs[ i ].Dispose();
_funcs.clear();
_global.Dispose();
- lzObjectTemplate.Dispose();
- lzArrayTemplate.Dispose();
- roObjectTemplate.Dispose();
- internalFieldObjects.Dispose();
_context.Dispose();
+ // Note: This block is unnecessary since we destroy the v8 Heap (Isolate) immediately
+ // after. Leaving in for now, but nothing new should be added.
}
_isolate->Dispose();
// set the isolate to NULL so ObjTracker destructors know that v8 is no longer reachable
@@ -779,38 +739,105 @@ namespace mongo {
v8::Handle<v8::FunctionTemplate> getNumberLongFunctionTemplate(V8Scope* scope) {
v8::Handle<v8::FunctionTemplate> numberLong = scope->createV8Function(numberLongInit);
- v8::Local<v8::Template> proto = numberLong->PrototypeTemplate();
- scope->injectV8Function("valueOf", numberLongValueOf, proto);
- scope->injectV8Function("toNumber", numberLongToNumber, proto);
- scope->injectV8Function("toString", numberLongToString, proto);
+ 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::Local<v8::Template> proto = numberInt->PrototypeTemplate();
- scope->injectV8Function("valueOf", numberIntValueOf, proto);
- scope->injectV8Function("toNumber", numberIntToNumber, proto);
- scope->injectV8Function("toString", numberIntToString, proto);
+ 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> getBinDataFunctionTemplate(V8Scope* scope) {
v8::Handle<v8::FunctionTemplate> binData = scope->createV8Function(binDataInit);
binData->InstanceTemplate()->SetInternalFieldCount(1);
- v8::Local<v8::Template> proto = binData->PrototypeTemplate();
- scope->injectV8Function("toString", binDataToString, proto);
- scope->injectV8Function("base64", binDataToBase64, proto);
- scope->injectV8Function("hex", binDataToHex, proto);
+ 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);
- ts->InstanceTemplate()->SetInternalFieldCount(1);
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::String::Utf8Value exceptionText(try_catch->Exception());
@@ -968,13 +995,17 @@ namespace mongo {
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
- v8::Handle<v8::Value> args[24];
+ // 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<24; i++) {
+ for (int i=0; i<nargs; i++) {
BSONElement next = it.next();
args[i] = mongoToV8Element(next, readOnlyArgs);
}
@@ -1089,30 +1120,46 @@ namespace mongo {
void V8Scope::injectNative(const char *field, NativeFunction func, v8::Handle<v8::Object>& obj,
void* data) {
v8::Handle<v8::FunctionTemplate> ft = createV8Function(nativeCallback);
- ft->Set(strLitToV8("_native_function"), v8::External::New((void*)func));
- ft->Set(strLitToV8("_native_data"), v8::External::New(data));
- ft->SetClassName(v8StringData(field));
- obj->ForceSet(v8StringData(field), ft->GetFunction());
+ ft->Set(strLitToV8("_native_function"),
+ v8::External::New((void*)func),
+ v8::PropertyAttribute(v8::DontEnum | v8::ReadOnly));
+ ft->Set(strLitToV8("_native_data"),
+ v8::External::New(data),
+ v8::PropertyAttribute(v8::DontEnum | v8::ReadOnly));
+ injectV8Function(field, ft, obj);
}
- void V8Scope::injectV8Function(const char *field, v8Function func) {
- injectV8Function(field, func, _global);
+ v8::Handle<v8::FunctionTemplate> V8Scope::injectV8Function(const char *field, v8Function func) {
+ return injectV8Function(field, func, _global);
}
- void V8Scope::injectV8Function(const char *field, v8Function func,
- v8::Handle<v8::Object>& obj) {
- v8::Handle<v8::FunctionTemplate> ft = createV8Function(func);
- ft->SetClassName(v8StringData(field));
- v8::Handle<v8::Function> f = ft->GetFunction();
- obj->ForceSet(v8StringData(field), f);
+ v8::Handle<v8::FunctionTemplate> V8Scope::injectV8Function(const char *field,
+ v8Function func,
+ v8::Handle<v8::Object>& obj) {
+ return injectV8Function(field, createV8Function(func), obj);
}
- void V8Scope::injectV8Function(const char *field, v8Function func,
- v8::Handle<v8::Template>& t) {
+ 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);
- ft->SetClassName(v8StringData(field));
v8::Handle<v8::Function> f = ft->GetFunction();
- t->Set(v8StringData(field), f);
+ f->SetName(field);
+ proto->Set(field, f);
+ return ft;
}
v8::Handle<v8::FunctionTemplate> V8Scope::createV8Function(v8Function func) {
@@ -1130,6 +1177,7 @@ namespace mongo {
}
void V8Scope::localConnect(const char * dbName) {
+ typedef v8::Persistent<v8::FunctionTemplate> FTPtr;
{
V8_SIMPLE_HEADER
if (_connectState == EXTERNAL)
@@ -1152,8 +1200,8 @@ namespace mongo {
injectV8Function("load", load);
// install the Mongo function object and instantiate the 'db' global
- _global->ForceSet(strLitToV8("Mongo"),
- getMongoFunctionTemplate(this, true)->GetFunction());
+ _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",
@@ -1165,6 +1213,7 @@ namespace mongo {
}
void V8Scope::externalSetup() {
+ typedef v8::Persistent<v8::FunctionTemplate> FTPtr;
V8_SIMPLE_HEADER
if (_connectState == EXTERNAL)
return;
@@ -1181,54 +1230,59 @@ namespace mongo {
injectV8Function("load", load);
// install the Mongo function object
- _global->ForceSet(strLitToV8("Mongo"),
- getMongoFunctionTemplate(this, false)->GetFunction());
+ _MongoFT = FTPtr::New(getMongoFunctionTemplate(this, false));
+ injectV8Function("Mongo", MongoFT(), _global);
execCoreFiles();
_connectState = EXTERNAL;
}
void V8Scope::installDBAccess() {
- v8::Handle<v8::FunctionTemplate> db = createV8Function(dbInit);
- db->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter, collectionSetter);
- _global->ForceSet(strLitToV8("DB"), db->GetFunction());
+ typedef v8::Persistent<v8::FunctionTemplate> FTPtr;
+ _DBFT = FTPtr::New(createV8Function(dbInit));
+ _DBQueryFT = FTPtr::New(createV8Function(dbQueryInit));
+ _DBCollectionFT = FTPtr::New(createV8Function(collectionInit));
- v8::Handle<v8::FunctionTemplate> dbCollection = createV8Function(collectionInit);
- dbCollection->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter,
- collectionSetter);
- _global->ForceSet(strLitToV8("DBCollection"), dbCollection->GetFunction());
+ // These must be done before calling injectV8Function
+ DBFT()->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter, collectionSetter);
+ DBQueryFT()->InstanceTemplate()->SetIndexedPropertyHandler(dbQueryIndexAccess);
+ DBCollectionFT()->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter,
+ collectionSetter);
- v8::Handle<v8::FunctionTemplate> dbQuery = createV8Function(dbQueryInit);
- dbQuery->InstanceTemplate()->SetIndexedPropertyHandler(dbQueryIndexAccess);
- _global->ForceSet(strLitToV8("DBQuery"), dbQuery->GetFunction());
+ 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() {
- injectV8Function("ObjectId", objectIdInit, _global);
- injectV8Function("DBRef", dbRefInit, _global);
- injectV8Function("DBPointer", dbPointerInit, _global);
-
- _global->ForceSet(strLitToV8("BinData"),
- getBinDataFunctionTemplate(this)->GetFunction());
- _global->ForceSet(strLitToV8("UUID"),
- createV8Function(uuidInit)->GetFunction());
- _global->ForceSet(strLitToV8("MD5"),
- createV8Function(md5Init)->GetFunction());
- _global->ForceSet(strLitToV8("HexData"),
- createV8Function(hexDataInit)->GetFunction());
- _global->ForceSet(strLitToV8("NumberLong"),
- getNumberLongFunctionTemplate(this)->GetFunction());
- _global->ForceSet(strLitToV8("NumberInt"),
- getNumberIntFunctionTemplate(this)->GetFunction());
- _global->ForceSet(strLitToV8("Timestamp"),
- getTimestampFunctionTemplate(this)->GetFunction());
+ 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));
+ _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);
+ 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);
- BSONObjBuilder b;
- b.appendMaxKey("");
- b.appendMinKey("");
- BSONObj o = b.obj();
- BSONObjIterator i(o);
- _global->ForceSet(strLitToV8("MaxKey"), mongoToV8Element(i.next()), v8::ReadOnly);
- _global->ForceSet(strLitToV8("MinKey"), mongoToV8Element(i.next()), v8::ReadOnly);
_global->Get(strLitToV8("Object"))->ToObject()->ForceSet(
strLitToV8("bsonsize"),
createV8Function(bsonsize)->GetFunction());
@@ -1266,7 +1320,7 @@ namespace mongo {
v8::Local<v8::Value> V8Scope::newId(const OID &id) {
v8::HandleScope handle_scope;
- v8::Function* idCons = this->getObjectIdCons();
+ v8::Handle<v8::Function> idCons = ObjectIdFT()->GetFunction();
v8::Handle<v8::Value> argv[1];
argv[0] = v8::String::New(id.str().c_str());
return handle_scope.Close(idCons->NewInstance(1, argv));
@@ -1275,83 +1329,35 @@ namespace mongo {
/**
* converts a BSONObj to a Lazy V8 object
*/
- v8::Persistent<v8::Object> V8Scope::mongoToLZV8(const BSONObj& m, bool readOnly) {
- v8::Local<v8::Object> o;
- BSONHolder* own = new BSONHolder(m);
- own->_readOnly = readOnly;
-
- if (readOnly) {
- o = roObjectTemplate->NewInstance();
- massert(16497, str::stream() << "V8: NULL RO Object template instantiated. "
- << (v8::V8::IsExecutionTerminating() ?
- "v8 execution is terminating." :
- "v8 still executing."),
- *o != NULL);
- } else {
- o = lzObjectTemplate->NewInstance();
- massert(16496, str::stream() << "V8: NULL Object template instantiated. "
- << (v8::V8::IsExecutionTerminating() ?
- "v8 execution is terminating." :
- "v8 still executing."),
- *o != NULL);
- static string ref = "$ref";
- if (ref == m.firstElement().fieldName()) {
- const BSONElement& id = m["$id"];
- if (!id.eoo()) {
- v8::Function* dbRef = getNamedCons("DBRef");
- o->SetPrototype(dbRef->NewInstance()->GetPrototype());
+ 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;
}
}
- return wrapBSONObject(o, own);
-
- }
-
- v8::Handle<v8::Value> minKeyToJson(const v8::Arguments& args) {
- return v8::String::New("{ \"$minKey\" : 1 }");
- }
-
- v8::Handle<v8::Value> minKeyToString(const v8::Arguments& args) {
- return v8::String::New("[object MinKey]");
- }
-
- v8::Local<v8::Object> V8Scope::newMinKeyInstance() {
- v8::Local<v8::ObjectTemplate> myTemplate = v8::Local<v8::ObjectTemplate>::New(
- v8::ObjectTemplate::New());
- myTemplate->SetInternalFieldCount(1);
- myTemplate->SetCallAsFunctionHandler(minKeyToJson);
-
- v8::Local<v8::Object> instance = myTemplate->NewInstance();
- instance->ForceSet(strLitToV8("tojson"),
- v8::FunctionTemplate::New(minKeyToJson)->GetFunction(), v8::ReadOnly);
- instance->ForceSet(strLitToV8("toString"),
- v8::FunctionTemplate::New(minKeyToJson)->GetFunction(), v8::ReadOnly);
- instance->SetInternalField(0, v8::Uint32::New( mongo::MinKey ));
- return instance;
- }
-
- v8::Handle<v8::Value> maxKeyToJson(const v8::Arguments& args) {
- return v8::String::New("{ \"$maxKey\" : 1 }");
- }
-
- v8::Handle<v8::Value> maxKeyToString(const v8::Arguments& args) {
- return v8::String::New("[object MaxKey]");
- }
-
- v8::Local<v8::Object> V8Scope::newMaxKeyInstance() {
- v8::Local<v8::ObjectTemplate> myTemplate = v8::Local<v8::ObjectTemplate>::New(
- v8::ObjectTemplate::New());
- myTemplate->SetInternalFieldCount(1);
- myTemplate->SetCallAsFunctionHandler(maxKeyToJson);
+ 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);
- v8::Local<v8::Object> instance = myTemplate->NewInstance();
- instance->ForceSet(strLitToV8("tojson"),
- v8::FunctionTemplate::New(maxKeyToJson)->GetFunction(), v8::ReadOnly);
- instance->ForceSet(strLitToV8("toString"),
- v8::FunctionTemplate::New(maxKeyToJson)->GetFunction(), v8::ReadOnly);
- instance->SetInternalField(0, v8::Uint32::New( mongo::MaxKey ));
- return instance;
+ wrapBSONObject(o, m, readOnly);
+ return o;
}
v8::Handle<v8::Value> V8Scope::mongoToV8Element(const BSONElement &elem, bool readOnly) {
@@ -1400,10 +1406,24 @@ namespace mongo {
case mongo::jstNULL:
case mongo::Undefined: // duplicate sm behavior
return v8::Null();
- case mongo::RegEx:
- argv[0] = v8::String::New(elem.regex());
- argv[1] = v8::String::New(elem.regexFlags());
- return getNamedCons("RegExp")->NewInstance(2, argv);
+ 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);
@@ -1411,14 +1431,12 @@ namespace mongo {
base64::encode(ss, data, len);
argv[0] = v8::Number::New(elem.binDataType());
argv[1] = v8::String::New(ss.str().c_str());
- return getNamedCons("BinData")->NewInstance(2, argv);
+ return BinDataFT()->GetFunction()->NewInstance(2, argv);
}
case mongo::Timestamp:
- instance = internalFieldObjects->NewInstance();
- instance->ForceSet(v8::String::New("t"), v8::Number::New(elem.timestampTime() / 1000 ));
- instance->ForceSet(v8::String::New("i"), v8::Number::New(elem.timestampInc()));
- instance->SetInternalField(0, v8::Uint32::New(elem.type()));
- return instance;
+ argv[0] = v8::Number::New(elem.timestampTime() / 1000);
+ argv[1] = v8::Number::New(elem.timestampInc());
+ return TimestampFT()->GetFunction()->NewInstance(2,argv);
case mongo::NumberLong:
nativeUnsignedLong = elem.numberLong();
// values above 2^53 are not accurately represented in JS
@@ -1426,23 +1444,23 @@ namespace mongo {
(long long)(double)(long long)(nativeUnsignedLong) &&
nativeUnsignedLong < 9007199254740992ULL) {
argv[0] = v8::Number::New((double)(long long)(nativeUnsignedLong));
- return getNamedCons("NumberLong")->NewInstance(1, argv);
+ 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 getNamedCons("NumberLong")->NewInstance(3, argv);
+ return NumberLongFT()->GetFunction()->NewInstance(3, argv);
}
case mongo::MinKey:
- return newMinKeyInstance();
+ return MinKeyFT()->GetFunction()->NewInstance();
case mongo::MaxKey:
- return newMaxKeyInstance();
+ return MaxKeyFT()->GetFunction()->NewInstance();
case mongo::DBRef:
argv[0] = v8StringData(elem.dbrefNS());
argv[1] = newId(elem.dbrefOID());
- return getNamedCons("DBPointer")->NewInstance(2, argv);
+ return DBPointerFT()->GetFunction()->NewInstance(2, argv);
default:
massert(16661, str::stream() << "can't handle type: " << elem.type()
<< " " << elem.toString(), false);
@@ -1453,12 +1471,13 @@ namespace mongo {
void V8Scope::v8ToMongoNumber(BSONObjBuilder& b,
const StringData& elementName,
- v8::Handle<v8::Value> value,
+ v8::Handle<v8::Number> value,
BSONObj* originalParent) {
- double val = value->ToNumber()->Value();
+ 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);
@@ -1468,49 +1487,9 @@ namespace mongo {
b.append(elementName, val);
}
- void V8Scope::v8ToMongoNumberLong(BSONObjBuilder& b,
- const StringData& elementName,
- v8::Handle<v8::Object> obj) {
- // TODO might be nice to potentially speed this up with an indexed internal
- // field, but I don't yet know how to use an ObjectTemplate with a
- // constructor.
- long long val;
- if (!obj->Has(strLitToV8("top"))) {
- val = static_cast<int64_t>(obj->Get(strLitToV8("floatApprox"))->NumberValue());
- }
- else {
- val = static_cast<int64_t>((
- static_cast<uint64_t>(obj->Get(strLitToV8("top"))->ToInt32()->Value()) << 32) +
- static_cast<uint32_t>(obj->Get(strLitToV8("bottom"))->ToInt32()->Value()));
- }
- b.append(elementName, val);
- }
-
- void V8Scope::v8ToMongoInternal(BSONObjBuilder& b,
- const StringData& elementName,
- v8::Handle<v8::Object> obj) {
- uint32_t bsonType = obj->GetInternalField(0)->ToUint32()->Value();
- switch(bsonType) {
- case Timestamp:
- b.appendTimestamp(elementName,
- Date_t(static_cast<uint64_t>(
- obj->Get(strLitToV8("t"))->ToNumber()->Value() * 1000 )),
- obj->Get(strLitToV8("i"))->ToInt32()->Value());
- return;
- case MinKey:
- b.appendMinKey(elementName);
- return;
- case MaxKey:
- b.appendMaxKey(elementName);
- return;
- default:
- massert(16665, "invalid internal field", false);
- }
- }
-
void V8Scope::v8ToMongoRegex(BSONObjBuilder& b,
const StringData& elementName,
- v8::Handle<v8::Object> v8Regex) {
+ v8::Handle<v8::RegExp> v8Regex) {
V8String v8RegexString (v8Regex);
StringData regex = v8RegexString;
regex = regex.substr(1);
@@ -1522,9 +1501,9 @@ namespace mongo {
void V8Scope::v8ToMongoDBRef(BSONObjBuilder& b,
const StringData& elementName,
v8::Handle<v8::Object> obj) {
- OID oid;
+ verify(DBPointerFT()->HasInstance(obj));
v8::Local<v8::Value> theid = obj->Get(strLitToV8("id"));
- oid.init(toSTLString(theid->ToObject()->Get(strLitToV8("str"))));
+ OID oid = v8ToMongoObjectID(theid->ToObject());
string ns = toSTLString(obj->Get(strLitToV8("ns")));
b.appendDBRef(elementName, ns, oid);
}
@@ -1532,6 +1511,9 @@ namespace mongo {
void V8Scope::v8ToMongoBinData(BSONObjBuilder& b,
const StringData& elementName,
v8::Handle<v8::Object> obj) {
+
+ verify(BinDataFT()->HasInstance(obj));
+ verify(obj->InternalFieldCount() == 1);
int len = obj->Get(strLitToV8("len"))->ToInt32()->Value();
b.appendBinData(elementName,
len,
@@ -1539,12 +1521,17 @@ namespace mongo {
base64::decode(toSTLString(obj->GetInternalField(0))).c_str());
}
- void V8Scope::v8ToMongoObjectID(BSONObjBuilder& b,
- const StringData& elementName,
- v8::Handle<v8::Object> obj) {
- OID oid;
- oid.init(toSTLString(obj->Get(strLitToV8("str"))));
- b.appendOID(elementName, &oid);
+ 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,
@@ -1552,32 +1539,30 @@ namespace mongo {
v8::Handle<v8::Value> value,
int depth,
BSONObj* originalParent) {
- // The user could potentially modify the fields of these special objects,
- // wreaking havoc when we attempt to reinterpret them. Not doing any validation
- // for now...
- v8::Local<v8::Object> obj = value->ToObject();
- v8::Local<v8::Value> proto = obj->GetPrototype();
-
- if (obj->InternalFieldCount() && obj->GetInternalField(0)->IsNumber()) {
- v8ToMongoInternal(b, elementName, obj);
- return;
- }
-
- if (proto->IsRegExp())
- v8ToMongoRegex(b, elementName, obj);
- else if (proto->IsObject() &&
- proto->ToObject()->HasRealNamedProperty(strLitToV8("isObjectId")))
- v8ToMongoObjectID(b, elementName, obj);
- else if (!obj->GetHiddenValue(strLitToV8("__NumberLong")).IsEmpty())
- v8ToMongoNumberLong(b, elementName, obj);
- else if (!obj->GetHiddenValue(strLitToV8("__NumberInt")).IsEmpty())
- b.append(elementName,
- obj->GetHiddenValue(strLitToV8("__NumberInt"))->Int32Value());
- else if (!value->ToObject()->GetHiddenValue(strLitToV8("__DBPointer")).IsEmpty())
+ 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 (DBPointerFT()->HasInstance(value)) {
v8ToMongoDBRef(b, elementName, obj);
- else if (!value->ToObject()->GetHiddenValue(strLitToV8("__BinData")).IsEmpty())
+ } else if (BinDataFT()->HasInstance(value)) {
v8ToMongoBinData(b, elementName, obj);
- else {
+ } else if (TimestampFT()->HasInstance(value)) {
+ OpTime ot (obj->Get(strLitToV8("t"))->Uint32Value(),
+ obj->Get(strLitToV8("i"))->Uint32Value());
+ b.append(elementName, ot);
+ } else if (MinKeyFT()->HasInstance(value)) {
+ b.appendMinKey(elementName);
+ } else if (MaxKeyFT()->HasInstance(value)) {
+ b.appendMaxKey(elementName);
+ } else {
// nested object or array
BSONObj sub = v8ToMongo(obj, depth);
b.append(elementName, sub);
@@ -1598,7 +1583,7 @@ namespace mongo {
return;
}
if (value->IsNumber()) {
- v8ToMongoNumber(b, sname, value, originalParent);
+ v8ToMongoNumber(b, sname, value.As<v8::Number>(), originalParent);
return;
}
if (value->IsArray()) {
@@ -1642,9 +1627,9 @@ namespace mongo {
BSONObj V8Scope::v8ToMongo(v8::Handle<v8::Object> o, int depth) {
BSONObj originalBSON;
- if (o->Has(strLitToV8("_bson"))) {
- originalBSON = unwrapBSONObj(o);
- BSONHolder* holder = unwrapHolder(o);
+ 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;
@@ -1677,14 +1662,6 @@ namespace mongo {
// --- random utils ----
- v8::Function * V8Scope::getNamedCons(const char * name) {
- return v8::Function::Cast(*(v8::Context::GetCurrent()->Global()->Get(v8StringData(name))));
- }
-
- v8::Function * V8Scope::getObjectIdCons() {
- return getNamedCons("ObjectId");
- }
-
v8::Handle<v8::Value> V8Scope::Print(V8Scope* scope, const v8::Arguments& args) {
stringstream ss;
v8::HandleScope handle_scope;
@@ -1695,7 +1672,7 @@ namespace mongo {
else
ss << " ";
- if (!*args[i]) {
+ if (args[i].IsEmpty()) {
// failed to get object to convert
ss << "[unknown type]";
continue;
diff --git a/src/mongo/scripting/engine_v8.h b/src/mongo/scripting/engine_v8.h
index a28160a1ef3..448ab38af26 100644
--- a/src/mongo/scripting/engine_v8.h
+++ b/src/mongo/scripting/engine_v8.h
@@ -202,9 +202,23 @@ namespace mongo {
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);
- void injectV8Function(const char* name, v8Function func);
- void injectV8Function(const char* name, v8Function func, v8::Handle<v8::Object>& obj);
- void injectV8Function(const char* name, v8Function func, v8::Handle<v8::Template>& t);
+
+ // 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);
@@ -214,7 +228,7 @@ namespace mongo {
/**
* Convert BSON types to v8 Javascript types
*/
- v8::Persistent<v8::Object> mongoToLZV8(const mongo::BSONObj& m, bool readOnly = false);
+ v8::Handle<v8::Object> mongoToLZV8(const mongo::BSONObj& m, bool readOnly = false);
v8::Handle<v8::Value> mongoToV8Element(const BSONElement& f, bool readOnly = false);
/**
@@ -233,30 +247,18 @@ namespace mongo {
BSONObj* originalParent);
void v8ToMongoNumber(BSONObjBuilder& b,
const StringData& elementName,
- v8::Handle<v8::Value> value,
+ v8::Handle<v8::Number> value,
BSONObj* originalParent);
- void v8ToMongoNumberLong(BSONObjBuilder& b,
- const StringData& elementName,
- v8::Handle<v8::Object> obj);
- void v8ToMongoInternal(BSONObjBuilder& b,
- const StringData& elementName,
- v8::Handle<v8::Object> obj);
void v8ToMongoRegex(BSONObjBuilder& b,
const StringData& elementName,
- v8::Handle<v8::Object> v8Regex);
+ v8::Handle<v8::RegExp> v8Regex);
void v8ToMongoDBRef(BSONObjBuilder& b,
const StringData& elementName,
v8::Handle<v8::Object> obj);
void v8ToMongoBinData(BSONObjBuilder& b,
const StringData& elementName,
v8::Handle<v8::Object> obj);
- void v8ToMongoObjectID(BSONObjBuilder& b,
- const StringData& elementName,
- v8::Handle<v8::Object> obj);
-
- v8::Function* getNamedCons(const char* name);
-
- v8::Function* getObjectIdCons();
+ OID v8ToMongoObjectID(v8::Handle<v8::Object> obj);
v8::Local<v8::Value> newId(const OID& id);
@@ -267,11 +269,6 @@ namespace mongo {
std::string v8ExceptionToSTLString(const v8::TryCatch* try_catch);
/**
- * GC callback for weak references to BSON objects (via BSONHolder)
- */
- v8::Persistent<v8::Object> wrapBSONObject(v8::Local<v8::Object> obj, BSONHolder* data);
-
- /**
* Create a V8 string with a local handle
*/
static inline v8::Handle<v8::String> v8StringData(StringData str) {
@@ -299,9 +296,54 @@ namespace mongo {
ObjTracker<DBClientBase> dbClientBaseTracker;
ObjTracker<DBClientCursor> dbClientCursorTracker;
+ // 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> 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 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;
+
+ 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:
/**
+ * 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.
*/
@@ -348,16 +390,6 @@ namespace mongo {
void unregisterOpId();
/**
- * Creates a new instance of the MinKey object
- */
- v8::Local<v8::Object> newMinKeyInstance();
-
- /**
- * Creates a new instance of the MaxKey object
- */
- v8::Local<v8::Object> newMaxKeyInstance();
-
- /**
* Create a new function; primarily used for BSON/V8 conversion.
*/
v8::Local<v8::Value> newFunction(const char *code);
@@ -368,27 +400,6 @@ namespace mongo {
bool reportError = true,
bool assertOnError = true);
- template <size_t N>
- v8::Handle<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;
-
- 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;
- }
-
V8ScriptEngine* _engine;
v8::Persistent<v8::Context> _context;
@@ -399,11 +410,25 @@ namespace mongo {
enum ConnectState { NOT, LOCAL, EXTERNAL };
ConnectState _connectState;
- v8::Persistent<v8::FunctionTemplate> lzFunctionTemplate;
- v8::Persistent<v8::ObjectTemplate> lzObjectTemplate;
- v8::Persistent<v8::ObjectTemplate> roObjectTemplate;
- v8::Persistent<v8::ObjectTemplate> lzArrayTemplate;
- v8::Persistent<v8::ObjectTemplate> internalFieldObjects;
+ // 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> _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;
v8::Isolate* _isolate;
V8CpuProfiler _cpuProfiler;
@@ -418,6 +443,11 @@ namespace mongo {
int _opId; // op id for this scope
};
+ /// 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();
diff --git a/src/mongo/scripting/v8_db.cpp b/src/mongo/scripting/v8_db.cpp
index f6201c7ca94..ea6cfe33591 100644
--- a/src/mongo/scripting/v8_db.cpp
+++ b/src/mongo/scripting/v8_db.cpp
@@ -33,9 +33,6 @@
using namespace std;
-#define GETNS boost::scoped_array<char> ns(new char[args[0]->ToString()->Utf8Length()+1]); \
- args[0]->ToString()->WriteUtf8(ns.get());
-
namespace mongo {
namespace {
@@ -61,18 +58,33 @@ namespace mongo {
_mongoPrototypeManipulators.push_back(manipulator);
}
- static v8::Handle<v8::Value> newInstance(v8::Function* f, const v8::Arguments& args) {
+ 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;
- int argc = args.Length();
+ 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[24];
- for (int i = 0; i < argc && i < 24; ++i) {
+ 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)
@@ -80,26 +92,18 @@ namespace mongo {
else
mongo = scope->createV8Function(mongoConsExternal);
mongo->InstanceTemplate()->SetInternalFieldCount(1);
- v8::Handle<v8::Template> proto = mongo->PrototypeTemplate();
- scope->injectV8Function("find", mongoFind, proto);
- scope->injectV8Function("insert", mongoInsert, proto);
- scope->injectV8Function("remove", mongoRemove, proto);
- scope->injectV8Function("update", mongoUpdate, proto);
- scope->injectV8Function("auth", mongoAuth, proto);
- scope->injectV8Function("logout", mongoLogout, proto);
+ v8::Handle<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);
fassert(16468, _mongoPrototypeManipulatorsFrozen);
for (size_t i = 0; i < _mongoPrototypeManipulators.size(); ++i)
_mongoPrototypeManipulators[i](scope, mongo);
- v8::Handle<v8::FunctionTemplate> ic = scope->createV8Function(internalCursorCons);
- ic->InstanceTemplate()->SetInternalFieldCount(1);
- v8::Handle<v8::Template> icproto = ic->PrototypeTemplate();
- scope->injectV8Function("next", internalCursorNext, icproto);
- scope->injectV8Function("hasNext", internalCursorHasNext, icproto);
- scope->injectV8Function("objsLeftInBatch", internalCursorObjsLeftInBatch, icproto);
- scope->injectV8Function("readOnly", internalCursorReadOnly, icproto);
- proto->Set(scope->v8StringData("internalCursor"), ic);
return mongo;
}
@@ -115,8 +119,9 @@ namespace mongo {
}
// only allow function template to be used by a constructor
- if (args.This()->Equals(scope->getGlobal()))
- return v8::Undefined();
+ uassert(16859, "Mongo function is only usable as a constructor",
+ args.IsConstructCall());
+ verify(scope->MongoFT()->HasInstance(args.This()));
string errmsg;
ConnectionString cs = ConnectionString::parse(host, errmsg);
@@ -130,7 +135,7 @@ namespace mongo {
return v8AssertionException(errmsg);
}
- v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.Holder());
+ v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.This());
scope->dbClientWithCommandsTracker.track(self, conn);
ScriptEngine::runConnectCallback(*conn);
@@ -146,8 +151,9 @@ namespace mongo {
argumentCheck(args.Length() == 0, "local Mongo constructor takes no args")
// only allow function template to be used by a constructor
- if (args.This()->Equals(scope->getGlobal()))
- return v8::Undefined();
+ uassert(16860, "Mongo function is only usable as a constructor",
+ args.IsConstructCall());
+ verify(scope->MongoFT()->HasInstance(args.This()));
DBClientBase* conn = createDirectClient();
v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.This());
@@ -160,7 +166,9 @@ namespace mongo {
return v8::Undefined();
}
- DBClientBase* getConnection(const v8::Arguments& args) {
+ 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)));
DBClientBase* conn = (DBClientBase*)(c->Value());
massert(16667, "Unable to get db client connection", conn);
@@ -173,8 +181,8 @@ namespace mongo {
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")
- DBClientBase * conn = getConnection(args);
- GETNS;
+ 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() &&
@@ -182,24 +190,18 @@ namespace mongo {
if (haveFields)
fields = scope->v8ToMongo(args[2]->ToObject());
- v8::Local<v8::Object> mongo = args.This();
auto_ptr<mongo::DBClientCursor> cursor;
- int nToReturn = (int)(args[3]->ToNumber()->Value());
- int nToSkip = (int)(args[4]->ToNumber()->Value());
- int batchSize = (int)(args[5]->ToNumber()->Value());
- int options = (int)(args[6]->ToNumber()->Value());
- cursor = conn->query(ns.get(), q, nToReturn, nToSkip, haveFields ? &fields : 0,
+ 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::Function* cons = (v8::Function*)(*(mongo->Get(scope->v8StringData("internalCursor"))));
-
- if (!cons) {
- return v8AssertionException("could not create a cursor");
- }
-
+ v8::Handle<v8::Function> cons = scope->InternalCursorFT()->GetFunction();
v8::Persistent<v8::Object> c = v8::Persistent<v8::Object>::New(cons->NewInstance());
c->SetInternalField(0, v8::External::New(cursor.get()));
scope->dbClientCursorTracker.track(c, cursor.release());
@@ -210,12 +212,14 @@ namespace mongo {
argumentCheck(args.Length() == 3 ,"insert needs 3 args")
argumentCheck(args[1]->IsObject() ,"attempted to insert a non-object")
+ verify(scope->MongoFT()->HasInstance(args.This()));
+
if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) {
return v8AssertionException("js db in read only mode");
}
- DBClientBase * conn = getConnection(args);
- GETNS;
+ DBClientBase * conn = getConnection(scope, args);
+ const string ns = toSTLString(args[0]);
v8::Handle<v8::Integer> flags = args[2]->ToInteger();
@@ -233,21 +237,21 @@ namespace mongo {
if (!el->Has(scope->v8StringData("_id"))) {
v8::Handle<v8::Value> argv[1];
el->ForceSet(scope->v8StringData("_id"),
- scope->getObjectIdCons()->NewInstance(0, argv));
+ scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv));
}
bos.push_back(scope->v8ToMongo(el));
}
- conn->insert(ns.get(), bos, flags->Int32Value());
+ 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->getObjectIdCons()->NewInstance(0, argv));
+ scope->ObjectIdFT()->GetFunction()->NewInstance(0, argv));
}
BSONObj o = scope->v8ToMongo(in);
- conn->insert(ns.get(), o);
+ conn->insert(ns, o);
}
return v8::Undefined();
}
@@ -256,12 +260,14 @@ namespace mongo {
argumentCheck(args.Length() == 2 || args.Length() == 3, "remove needs 2 or 3 args")
argumentCheck(args[1]->IsObject(), "attempted to remove a non-object")
+ verify(scope->MongoFT()->HasInstance(args.This()));
+
if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) {
return v8AssertionException("js db in read only mode");
}
- DBClientBase * conn = getConnection(args);
- GETNS;
+ DBClientBase * conn = getConnection(scope, args);
+ const string ns = toSTLString(args[0]);
v8::Handle<v8::Object> in = args[1]->ToObject();
BSONObj o = scope->v8ToMongo(in);
@@ -271,7 +277,7 @@ namespace mongo {
justOne = args[2]->BooleanValue();
}
- conn->remove(ns.get(), o, justOne);
+ conn->remove(ns, o, justOne);
return v8::Undefined();
}
@@ -280,12 +286,14 @@ namespace mongo {
argumentCheck(args[1]->IsObject(), "1st param to update has to be an object")
argumentCheck(args[2]->IsObject(), "2nd param to update has to be an object")
+ verify(scope->MongoFT()->HasInstance(args.This()));
+
if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) {
return v8AssertionException("js db in read only mode");
}
- DBClientBase * conn = getConnection(args);
- GETNS;
+ 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();
@@ -295,12 +303,12 @@ namespace mongo {
BSONObj q1 = scope->v8ToMongo(q);
BSONObj o1 = scope->v8ToMongo(o);
- conn->update(ns.get(), q1, o1, upsert, multi);
+ conn->update(ns, q1, o1, upsert, multi);
return v8::Undefined();
}
v8::Handle<v8::Value> mongoAuth(V8Scope* scope, const v8::Arguments& args) {
- DBClientWithCommands* conn = getConnection(args);
+ DBClientWithCommands* conn = getConnection(scope, args);
if (NULL == conn)
return v8AssertionException("no connection");
@@ -329,7 +337,7 @@ namespace mongo {
v8::Handle<v8::Value> mongoLogout(V8Scope* scope, const v8::Arguments& args) {
argumentCheck(args.Length() == 1, "logout needs 1 arg")
- DBClientBase* conn = getConnection(args);
+ DBClientBase* conn = getConnection(scope, args);
const string db = toSTLString(args[0]);
BSONObj ret;
conn->logout(db, ret);
@@ -339,9 +347,11 @@ namespace mongo {
/**
* get cursor from v8 argument
*/
- mongo::DBClientCursor* getCursor(const v8::Arguments& args) {
+ 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 = (mongo::DBClientCursor*)(c->Value());
+ mongo::DBClientCursor* cursor = static_cast<mongo::DBClientCursor*>(c->Value());
return cursor;
}
@@ -353,7 +363,7 @@ namespace mongo {
* cursor.next()
*/
v8::Handle<v8::Value> internalCursorNext(V8Scope* scope, const v8::Arguments& args) {
- mongo::DBClientCursor* cursor = getCursor(args);
+ mongo::DBClientCursor* cursor = getCursor(scope, args);
if (! cursor)
return v8::Undefined();
BSONObj o = cursor->next();
@@ -367,7 +377,7 @@ namespace mongo {
* cursor.hasNext()
*/
v8::Handle<v8::Value> internalCursorHasNext(V8Scope* scope, const v8::Arguments& args) {
- mongo::DBClientCursor* cursor = getCursor(args);
+ mongo::DBClientCursor* cursor = getCursor(scope, args);
if (! cursor)
return v8::Boolean::New(false);
return v8::Boolean::New(cursor->more());
@@ -378,7 +388,7 @@ namespace mongo {
*/
v8::Handle<v8::Value> internalCursorObjsLeftInBatch(V8Scope* scope,
const v8::Arguments& args) {
- mongo::DBClientCursor* cursor = getCursor(args);
+ mongo::DBClientCursor* cursor = getCursor(scope, args);
if (! cursor)
return v8::Number::New(0.0);
return v8::Number::New(static_cast<double>(cursor->objsLeftInBatch()));
@@ -388,12 +398,21 @@ namespace mongo {
* 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]);
@@ -412,8 +431,20 @@ namespace mongo {
}
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]);
@@ -423,14 +454,17 @@ namespace mongo {
return v8AssertionException("can't use sharded collection from db.eval");
}
- for (int i = 0; i < args.Length(); i++) {
- argumentCheck(!args[i]->IsUndefined(),
- "collection constructor called with undefined argument")
- }
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();
@@ -479,86 +513,126 @@ namespace mongo {
v8::Handle<v8::Value> collectionSetter(v8::Local<v8::String> name,
v8::Local<v8::Value> value,
const v8::AccessorInfo& info) {
- // 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>();
+ 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");
}
- // dont set
- return value;
}
v8::Handle<v8::Value> collectionGetter(v8::Local<v8::String> name,
const v8::AccessorInfo& info) {
- 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
- if (haveLocalShardingInfo(toSTLString(
- prop->ToObject()->GetRealNamedProperty(v8::String::New("_fullName"))))) {
- return v8AssertionException("can't use sharded collection from db.eval");
+ 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
+ if (haveLocalShardingInfo(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>();
}
- 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::Function* f = (v8::Function*)(*getCollection);
- v8::Handle<v8::Value> argv[1];
- argv[0] = name;
- v8::Local<v8::Value> coll = f->Call(info.This(), 1, argv);
- if (coll.IsEmpty()) {
- if (tryCatch.HasCaught()) {
- return v8::ThrowException(tryCatch.Exception());
+ // 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");
}
- return v8::Handle<v8::Value>();
- }
- // cache collection for reuse, don't enumerate
- info.This()->ForceSet(name, coll, v8::DontEnum);
- return coll;
+ 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) {
- v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get(
- v8::String::New("arrayAccess"));
- massert(16660, "arrayAccess is not a function", arrayAccess->IsFunction());
+ try {
+ V8Scope* scope = getScope(info.GetIsolate());
+ verify(scope->DBQueryFT()->HasInstance(info.This()));
- v8::Function* f = (v8::Function*)(*arrayAccess);
- v8::Handle<v8::Value> argv[1];
- argv[0] = v8::Number::New(index);
+ v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get(
+ v8::String::New("arrayAccess"));
+ massert(16660, "arrayAccess is not a function", arrayAccess->IsFunction());
- return f->Call(info.This(), 1, argv);
+ 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) {
- v8::Handle<v8::Object> it = args.This();
- if (it->IsUndefined() || it == v8::Context::GetCurrent()->Global()) {
- v8::Function* f = scope->getObjectIdCons();
+ 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();
@@ -579,48 +653,49 @@ namespace mongo {
}
v8::Handle<v8::Value> dbRefInit(V8Scope* scope, const v8::Arguments& args) {
- v8::Handle<v8::Object> it = args.This();
- if (it->IsUndefined() || it == v8::Context::GetCurrent()->Global()) {
- v8::Function* f = scope->getNamedCons("DBRef");
+ if (!args.IsConstructCall()) {
+ v8::Handle<v8::Function> f = scope->DBRefFT()->GetFunction();
return newInstance(f, args);
}
- // We use a DBRef object to display DBRefs converted from BSON, so this clumsy hack
- // lets the constructor serve two masters with different requirements. The downside
- // is that this internal usage is available to direct calls from the shell, so DBRef()
- // is accepted and produces DBRef(undefined, undefined).
- // TODO(tad) fix this
- if (args.Length() == 0) {
- return it;
- }
+ v8::Handle<v8::Object> it = args.This();
+ verify(scope->DBRefFT()->HasInstance(it));
argumentCheck(args.Length() == 2, "DBRef needs 2 arguments")
+ argumentCheck(args[0]->IsString(), "DBRef 1st parameter must be a string")
it->ForceSet(scope->v8StringData("$ref"), args[0]);
it->ForceSet(scope->v8StringData("$id"), args[1]);
return it;
}
v8::Handle<v8::Value> dbPointerInit(V8Scope* scope, const v8::Arguments& args) {
- v8::Handle<v8::Object> it = args.This();
- if (it->IsUndefined() || it == v8::Context::GetCurrent()->Global()) {
- v8::Function* f = scope->getNamedCons("DBPointer");
+ 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]);
- it->SetHiddenValue(scope->v8StringData("__DBPointer"), v8::Number::New(1));
return it;
}
v8::Handle<v8::Value> dbTimestampInit(V8Scope* scope, const v8::Arguments& args) {
- v8::Handle<v8::Object> it = args.This();
- if (it->IsUndefined() || it == v8::Context::GetCurrent()->Global()) {
- v8::Function* f = scope->getNamedCons("Timestamp");
+ 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));
@@ -636,7 +711,7 @@ namespace mongo {
int64_t largestVal = ((2039LL-1970LL) *365*24*60*60); //seconds between 1970-2038
if( t > largestVal )
return v8AssertionException( str::stream()
- << "The first argument must be in seconds;"
+ << "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]);
@@ -645,29 +720,31 @@ namespace mongo {
return v8AssertionException("Timestamp needs 0 or 2 arguments");
}
- it->SetInternalField(0, v8::Uint32::New(Timestamp));
-
return it;
}
v8::Handle<v8::Value> binDataInit(V8Scope* scope, const v8::Arguments& args) {
- v8::Local<v8::Object> it = args.This();
- if (it->IsUndefined() || it == v8::Context::GetCurrent()->Global()) {
- v8::Function* f = scope->getNamedCons("BinData");
+ if (!args.IsConstructCall()) {
+ v8::Handle<v8::Function> f = scope->BinDataFT()->GetFunction();
return newInstance(f, args);
}
- v8::Handle<v8::Value> type;
+ v8::Local<v8::Object> it = args.This();
+ verify(scope->BinDataFT()->HasInstance(it));
+
if (args.Length() == 2) {
// 2 args: type, base64 string
- type = args[0];
+ 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()));
it->ForceSet(scope->v8StringData("type"), type);
- it->SetHiddenValue(v8::String::New("__BinData"), v8::Number::New(1));
it->SetInternalField(0, args[1]);
}
else if (args.Length() != 0) {
@@ -679,21 +756,27 @@ namespace mongo {
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));
int len = v8::Handle<v8::Number>::Cast(it->Get(v8::String::New("len")))->Int32Value();
+ verify(it->InternalFieldCount() == 1);
string data = base64::decode(toSTLString(it->GetInternalField(0)));
stringstream ss;
ss.setf (ios_base::hex, ios_base::basefield);
@@ -708,6 +791,8 @@ namespace mongo {
static v8::Handle<v8::Value> hexToBinData(V8Scope* scope, v8::Local<v8::Object> it, int type,
string hexstr) {
+ verify(scope->BinDataFT()->HasInstance(it));
+
int len = hexstr.length() / 2;
scoped_array<char> data(new char[len]);
const char* src = hexstr.c_str();
@@ -718,7 +803,6 @@ namespace mongo {
string encoded = base64::encode(data.get(), len);
it->ForceSet(v8::String::New("len"), v8::Number::New(len));
it->ForceSet(v8::String::New("type"), v8::Number::New(type));
- it->SetHiddenValue(v8::String::New("__BinData"), v8::Number::New(1));
it->SetInternalField(0, v8::String::New(encoded.c_str(), encoded.length()));
return it;
}
@@ -728,7 +812,7 @@ namespace mongo {
v8::String::Utf8Value utf(args[0]);
argumentCheck(utf.length() == 32, "UUID string must have 32 characters")
- v8::Function* f = scope->getNamedCons("BinData");
+ v8::Handle<v8::Function> f = scope->BinDataFT()->GetFunction();
v8::Local<v8::Object> it = f->NewInstance();
return hexToBinData(scope, it, bdtUUID, *utf);
}
@@ -738,7 +822,7 @@ namespace mongo {
v8::String::Utf8Value utf(args[0]);
argumentCheck(utf.length() == 32, "MD5 string must have 32 characters")
- v8::Function* f = scope->getNamedCons("BinData");
+ v8::Handle<v8::Function> f = scope->BinDataFT()->GetFunction();
v8::Local<v8::Object> it = f->NewInstance();
return hexToBinData(scope, it, MD5Type, *utf);
}
@@ -746,21 +830,23 @@ namespace mongo {
v8::Handle<v8::Value> hexDataInit(V8Scope* scope, const v8::Arguments& args) {
argumentCheck(args.Length() == 2, "HexData needs 2 arguments")
v8::String::Utf8Value utf(args[1]);
- v8::Function* f = scope->getNamedCons("BinData");
+ v8::Handle<v8::Function> f = scope->BinDataFT()->GetFunction();
v8::Local<v8::Object> it = f->NewInstance();
return hexToBinData(scope, it, args[0]->IntegerValue(), *utf);
}
v8::Handle<v8::Value> numberLongInit(V8Scope* scope, const v8::Arguments& args) {
- v8::Handle<v8::Object> it = args.This();
- if (it->IsUndefined() || it == v8::Context::GetCurrent()->Global()) {
- v8::Function* f = scope->getNamedCons("NumberLong");
+ 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));
}
@@ -798,15 +884,15 @@ namespace mongo {
}
}
else {
- it->ForceSet(scope->v8StringData("floatApprox"), args[0]);
- it->ForceSet(scope->v8StringData("top"), args[1]);
- it->ForceSet(scope->v8StringData("bottom"), args[2]);
+ it->ForceSet(scope->v8StringData("floatApprox"), args[0]->ToNumber());
+ it->ForceSet(scope->v8StringData("top"), args[1]->ToUint32());
+ it->ForceSet(scope->v8StringData("bottom"), args[2]->ToUint32());
}
- it->SetHiddenValue(v8::String::New("__NumberLong"), v8::Number::New(1));
return it;
}
- long long numberLongVal(const v8::Handle<v8::Object>& 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
@@ -817,7 +903,7 @@ namespace mongo {
v8::Handle<v8::Value> numberLongValueOf(V8Scope* scope, const v8::Arguments& args) {
v8::Handle<v8::Object> it = args.This();
- long long val = numberLongVal(it);
+ long long val = numberLongVal(scope, it);
return v8::Number::New(double(val));
}
@@ -829,7 +915,7 @@ namespace mongo {
v8::Handle<v8::Object> it = args.This();
stringstream ss;
- long long val = numberLongVal(it);
+ long long val = numberLongVal(scope, it);
const long long limit = 2LL << 30;
if (val <= -limit || limit <= val)
@@ -842,12 +928,14 @@ namespace mongo {
}
v8::Handle<v8::Value> numberIntInit(V8Scope* scope, const v8::Arguments& args) {
- v8::Handle<v8::Object> it = args.This();
- if (it->IsUndefined() || it == v8::Context::GetCurrent()->Global()) {
- v8::Function* f = scope->getNamedCons("NumberInt");
+ 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));
@@ -858,10 +946,16 @@ namespace mongo {
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();
- int val = it->GetHiddenValue(v8::String::New("__NumberInt"))->Int32Value();
- return v8::Number::New(double(val));
+ return v8::Integer::New(numberIntVal(scope, it));
}
v8::Handle<v8::Value> numberIntToNumber(V8Scope* scope, const v8::Arguments& args) {
@@ -870,8 +964,7 @@ namespace mongo {
v8::Handle<v8::Value> numberIntToString(V8Scope* scope, const v8::Arguments& args) {
v8::Handle<v8::Object> it = args.This();
-
- int val = it->GetHiddenValue(v8::String::New("__NumberInt"))->Int32Value();
+ int val = numberIntVal(scope, it);
string ret = str::stream() << "NumberInt(" << val << ")";
return v8::String::New(ret.c_str());
}
diff --git a/src/mongo/scripting/v8_db.h b/src/mongo/scripting/v8_db.h
index 2f747fd9714..445ab783681 100644
--- a/src/mongo/scripting/v8_db.h
+++ b/src/mongo/scripting/v8_db.h
@@ -27,19 +27,12 @@ namespace mongo {
class DBClientBase;
/**
- * install database access functions
- */
- void installDBAccess(V8Scope* scope);
-
- /**
- * install BSON types and helpers
- */
- void installBSONTypes(V8Scope* scope);
-
- /**
* get the DBClientBase connection from JS args
*/
- mongo::DBClientBase* getConnection(const v8::Arguments& args);
+ mongo::DBClientBase* getConnection(V8Scope* scope, const v8::Arguments& args);
+
+ // Internal Cursor
+ v8::Handle<v8::FunctionTemplate> getInternalCursorFunctionTemplate(V8Scope* scope);
// Mongo constructors
v8::Handle<v8::Value> mongoConsLocal(V8Scope* scope, const v8::Arguments& args);
@@ -68,12 +61,14 @@ namespace mongo {
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);
- // Number object
+ // 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);