summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTad Marshall <tad@10gen.com>2013-01-25 16:41:49 -0500
committerTad Marshall <tad@10gen.com>2013-01-25 16:41:49 -0500
commit41de9044a87c167de4ec2ba5e67f8f735d9f98c9 (patch)
tree975ea83ec56f236188f40547ac7dcefa5677be9e
parent18468f01130609f7ae4fbc36273378e52df7fa11 (diff)
downloadmongo-41de9044a87c167de4ec2ba5e67f8f735d9f98c9.tar.gz
SERVER-8292 SERVER-7843 SERVER-7087 Better V8 exceptions
Audit all handling of C++ and JavaScript exceptions in V8 interface code. Pass C++ exception details back to JavaScript similarly to the way this is done in the SpiderMonkey interface code. Format JavaScript exception reporting output to be more similar to the output generated in SpiderMonkey. Add code and macros to make argument testing and throwing of JavaScript exceptions more uniform. Fix typos in error reporting where the error names the wrong function. Mimic error text from SpiderMonkey more closely.
-rw-r--r--src/mongo/scripting/engine_v8.cpp120
-rw-r--r--src/mongo/scripting/v8_db.cpp301
-rw-r--r--src/mongo/scripting/v8_utils.cpp41
-rw-r--r--src/mongo/scripting/v8_utils.h22
4 files changed, 221 insertions, 263 deletions
diff --git a/src/mongo/scripting/engine_v8.cpp b/src/mongo/scripting/engine_v8.cpp
index 88f5faaa1a7..cd30e1f8c0e 100644
--- a/src/mongo/scripting/engine_v8.cpp
+++ b/src/mongo/scripting/engine_v8.cpp
@@ -559,8 +559,7 @@ namespace mongo {
for (int i = 0; i < args.Length(); ++i) {
std::string filename(toSTLString(args[i]));
if (!scope->execFile(filename, false, true, false)) {
- return v8::ThrowException(v8::String::New((string("error loading file: ") +
- filename).c_str()));
+ return v8AssertionException(string("error loading js file: ") + filename);
}
}
return v8::True();
@@ -568,7 +567,7 @@ namespace mongo {
v8::Handle<v8::Value> V8Scope::nativeCallback(V8Scope* scope, const v8::Arguments &args) {
BSONObj ret;
- string exception;
+ string exceptionText;
v8::HandleScope handle_scope;
try {
v8::Local<v8::External> f =
@@ -577,22 +576,20 @@ namespace mongo {
v8::Local<v8::External> data =
v8::External::Cast(*args.Callee()->Get(v8::String::New("_native_data")));
BSONObjBuilder b;
- for(int i = 0; i < args.Length(); ++i)
+ for (int i = 0; i < args.Length(); ++i)
scope->v8ToMongoElement(b, str::stream() << i, args[i]);
BSONObj nativeArgs = b.obj();
ret = function(nativeArgs, data->Value());
}
- catch(const std::exception &e) {
- exception = e.what();
+ catch (const std::exception &e) {
+ exceptionText = e.what();
}
- catch(...) {
- exception = "unknown exception";
+ catch (...) {
+ exceptionText = "unknown exception in V8Scope::nativeCallback";
+ }
+ if (!exceptionText.empty()) {
+ return v8AssertionException(exceptionText);
}
-
- if (!exception.empty())
- return v8::ThrowException(v8::String::New(string("exception during callback: ").
- append(exception).c_str()));
-
return handle_scope.Close(scope->mongoToV8Element(ret.firstElement()));
}
@@ -609,26 +606,26 @@ namespace mongo {
v8::External::Cast(*args.Callee()->Get(v8::String::New("_v8_function")));
v8Function function = (v8Function)(f->Value());
v8::Handle<v8::Value> ret;
- string exception;
+ string exceptionText;
try {
// execute the native function
ret = function(scope, args);
}
- catch(const std::exception &e) {
- exception = e.what();
+ catch (const std::exception& e) {
+ exceptionText = e.what();
}
- catch(...) {
- exception = "unknown exception";
+ catch (...) {
+ exceptionText = "unknown exception in V8Scope::v8Callback";
}
if (!scope->nativeEpilogue())
// execution terminated
return v8::Undefined();
- if (!exception.empty())
- return v8::ThrowException(v8::String::New(exception.c_str()));
-
+ if (!exceptionText.empty()) {
+ return v8AssertionException(exceptionText);
+ }
return handle_scope.Close(ret);
}
@@ -814,14 +811,14 @@ namespace mongo {
}
if (!nativeEpilogue()) {
- _error = str::stream() << "javascript execution terminated ";
+ _error = "JavaScript execution terminated";
return handle_scope.Close(v8::Handle<v8::Function>());
}
v8::Local<v8::Value> result = script->Run();
if (!nativePrologue()) {
- _error = str::stream() << "javascript execution terminated ";
+ _error = "JavaScript execution terminated";
return handle_scope.Close(v8::Handle<v8::Function>());
}
@@ -890,7 +887,7 @@ namespace mongo {
v8recv = _global;
if (!nativeEpilogue()) {
- _error = str::stream() << "javascript execution terminated (before call) ";
+ _error = "JavaScript execution terminated";
log() << _error << endl;
return 1;
}
@@ -906,17 +903,21 @@ namespace mongo {
_engine->getDeadlineMonitor()->stopDeadline(this);
if (!nativePrologue()) {
- _error = str::stream() << "javascript execution interrupted";
+ _error = "JavaScript execution interrupted";
log() << _error << endl;
return 1;
}
if (result.IsEmpty()) {
- _error = str::stream() << "javascript execution failed: ";
- if (try_catch.HasCaught())
- _error += toSTLString(&try_catch);
- if (hasOutOfMemoryException())
- _error += "v8 out of memory";
+ if (try_catch.HasCaught() && try_catch.CanContinue()) {
+ _error = toSTLString(&try_catch);
+ }
+ else {
+ _error = "JavaScript execution failed";
+ }
+ if (hasOutOfMemoryException()) {
+ _error += " -- v8 is out of memory";
+ }
log() << _error << endl;
return 1;
}
@@ -943,16 +944,16 @@ namespace mongo {
if (reportError)
log() << _error << endl;
if (assertOnError)
- uassert(10233, _error, 0);
+ uasserted(10233, _error);
return false;
}
if (!nativeEpilogue()) {
- _error = str::stream() << "javascript execution terminated (before call) ";
+ _error = "JavaScript execution terminated";
if (reportError)
log() << _error << endl;
if (assertOnError)
- uassert(13475, _error, 0);
+ uasserted(13475, _error);
return false;
}
@@ -969,21 +970,28 @@ namespace mongo {
bool resultSuccess = true;
if (!nativePrologue()) {
resultSuccess = false;
- _error = str::stream() << "javascript execution interrupted "
+ _error = str::stream() << "JavaScript execution interrupted "
<< (try_catch.HasCaught() && try_catch.CanContinue() ?
toSTLString(&try_catch) : "");
}
- if (result.IsEmpty()) {
+ else if (result.IsEmpty()) {
resultSuccess = false;
- _error = str::stream() << "javascript execution failed "
- << (try_catch.HasCaught() && try_catch.CanContinue() ?
- toSTLString(&try_catch) : "");
+ if (try_catch.HasCaught() && try_catch.CanContinue()) {
+ _error = toSTLString(&try_catch);
+ }
+ else {
+ _error = "JavaScript execution failed";
+ }
+ if (hasOutOfMemoryException()) {
+ _error += " -- v8 is out of memory";
+ }
}
+
if (!resultSuccess) {
if (reportError)
log() << _error << endl;
if (assertOnError)
- uassert(10234, _error, 0);
+ uasserted(10234, _error);
return false;
}
@@ -1049,11 +1057,13 @@ namespace mongo {
{
V8_SIMPLE_HEADER
if (_connectState == EXTERNAL)
- uassert(12510, "externalSetup already called, can't call localConnect", false);
+ uasserted(12510, "externalSetup already called, can't call localConnect");
if (_connectState == LOCAL) {
if (_localDBName == dbName)
return;
- uassert(12511, "localConnect previously called with a different name", false);
+ uasserted(12511,
+ str::stream() << "localConnect previously called with name "
+ << _localDBName);
}
// NOTE: order is important here. the following methods must be called after
@@ -1083,7 +1093,7 @@ namespace mongo {
if (_connectState == EXTERNAL)
return;
if (_connectState == LOCAL)
- uassert(12512, "localConnect already called, can't call externalSetup", false);
+ uasserted(12512, "localConnect already called, can't call externalSetup");
// install db access functions in the global object
installDBAccess();
@@ -1105,6 +1115,7 @@ namespace mongo {
v8::Handle<v8::FunctionTemplate> db = createV8Function(dbInit);
db->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter, collectionSetter);
_global->ForceSet(v8StringData("DB"), db->GetFunction());
+
v8::Handle<v8::FunctionTemplate> dbCollection = createV8Function(collectionInit);
dbCollection->InstanceTemplate()->SetNamedPropertyHandler(collectionGetter,
collectionSetter);
@@ -1121,19 +1132,19 @@ namespace mongo {
injectV8Function("DBPointer", dbPointerInit, _global);
_global->ForceSet(v8StringData("BinData"),
- getBinDataFunctionTemplate(this)->GetFunction());
+ getBinDataFunctionTemplate(this)->GetFunction());
_global->ForceSet(v8StringData("UUID"),
- createV8Function(uuidInit)->GetFunction());
+ createV8Function(uuidInit)->GetFunction());
_global->ForceSet(v8StringData("MD5"),
- createV8Function(md5Init)->GetFunction());
+ createV8Function(md5Init)->GetFunction());
_global->ForceSet(v8StringData("HexData"),
- createV8Function(hexDataInit)->GetFunction());
+ createV8Function(hexDataInit)->GetFunction());
_global->ForceSet(v8StringData("NumberLong"),
- getNumberLongFunctionTemplate(this)->GetFunction());
+ getNumberLongFunctionTemplate(this)->GetFunction());
_global->ForceSet(v8StringData("NumberInt"),
- getNumberIntFunctionTemplate(this)->GetFunction());
+ getNumberIntFunctionTemplate(this)->GetFunction());
_global->ForceSet(v8StringData("Timestamp"),
- getTimestampFunctionTemplate(this)->GetFunction());
+ getTimestampFunctionTemplate(this)->GetFunction());
BSONObjBuilder b;
b.appendMaxKey("");
@@ -1170,15 +1181,14 @@ namespace mongo {
uassert(16670, errStr, !compiled.IsEmpty());
if (!nativeEpilogue()) {
- _error = str::stream()
- << "javascript execution terminated during newFunction compilation";
+ _error = "JavaScript execution terminated";
return handle_scope.Close(v8::Handle<v8::Value>());
}
v8::Local<v8::Value> ret = compiled->Run();
if (!nativePrologue()) {
- _error = str::stream() << "javascript execution terminated";
+ _error = "JavaScript execution terminated";
if (!ret.IsEmpty())
return handle_scope.Close(ret);
return handle_scope.Close(v8::Handle<v8::Value>());
@@ -1805,7 +1815,7 @@ namespace mongo {
v8::Handle<v8::Value> V8Scope::startCpuProfiler(V8Scope* scope, const v8::Arguments& args) {
if (args.Length() != 1 || !args[0]->IsString()) {
- return v8::ThrowException(v8::String::New("startCpuProfiler takes a string argument"));
+ return v8AssertionException("startCpuProfiler takes a string argument");
}
scope->_cpuProfiler.start(*v8::String::Utf8Value(args[0]->ToString()));
return v8::Undefined();
@@ -1813,7 +1823,7 @@ namespace mongo {
v8::Handle<v8::Value> V8Scope::stopCpuProfiler(V8Scope* scope, const v8::Arguments& args) {
if (args.Length() != 1 || !args[0]->IsString()) {
- return v8::ThrowException(v8::String::New("stopCpuProfiler takes a string argument"));
+ return v8AssertionException("stopCpuProfiler takes a string argument");
}
scope->_cpuProfiler.stop(*v8::String::Utf8Value(args[0]->ToString()));
return v8::Undefined();
@@ -1821,7 +1831,7 @@ namespace mongo {
v8::Handle<v8::Value> V8Scope::getCpuProfile(V8Scope* scope, const v8::Arguments& args) {
if (args.Length() != 1 || !args[0]->IsString()) {
- return v8::ThrowException(v8::String::New("getCpuProfile takes a string argument"));
+ return v8AssertionException("getCpuProfile takes a string argument");
}
return scope->mongoToLZV8(scope->_cpuProfiler.fetch(
*v8::String::Utf8Value(args[0]->ToString())));
diff --git a/src/mongo/scripting/v8_db.cpp b/src/mongo/scripting/v8_db.cpp
index e056dfbe206..57813fea38b 100644
--- a/src/mongo/scripting/v8_db.cpp
+++ b/src/mongo/scripting/v8_db.cpp
@@ -110,7 +110,6 @@ namespace mongo {
v8::Handle<v8::Value> mongoConsExternal(V8Scope* scope, const v8::Arguments& args) {
char host[255];
-
if (args.Length() > 0 && args[0]->IsString()) {
uassert(16666, "string argument too long", args[0]->ToString()->Utf8Length() < 250);
args[0]->ToString()->WriteAscii(host);
@@ -121,13 +120,15 @@ namespace mongo {
string errmsg;
ConnectionString cs = ConnectionString::parse(host, errmsg);
- if (!cs.isValid())
- return v8::ThrowException(v8::String::New(errmsg.c_str()));
+ if (!cs.isValid()) {
+ return v8AssertionException(errmsg);
+ }
DBClientWithCommands* conn;
conn = cs.connect(errmsg);
- if (!conn)
- return v8::ThrowException(v8::String::New(errmsg.c_str()));
+ if (!conn) {
+ return v8AssertionException(errmsg);
+ }
v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.Holder());
self.MakeWeak(conn, destroyConnection);
@@ -142,8 +143,7 @@ namespace mongo {
}
v8::Handle<v8::Value> mongoConsLocal(V8Scope* scope, const v8::Arguments& args) {
- if (args.Length() > 0)
- return v8::ThrowException(v8::String::New("local Mongo constructor takes no args"));
+ argumentCheck(args.Length() == 0, "local Mongo constructor takes no args")
DBClientBase* conn = createDirectClient();
v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.This());
@@ -173,8 +173,8 @@ namespace mongo {
* JavaScript binding for Mongo.prototype.find(namespace, query, fields, limit, skip)
*/
v8::Handle<v8::Value> mongoFind(V8Scope* scope, const v8::Arguments& args) {
- uassert(16653, "find needs 7 args", args.Length() == 7);
- uassert(16654, "needs to be an object", args[1]->IsObject());
+ argumentCheck(args.Length() == 7, "find needs 7 args")
+ argumentCheck(args[1]->IsObject(), "needs to be an object")
DBClientBase * conn = getConnection(args);
GETNS;
BSONObj fields;
@@ -185,39 +185,36 @@ namespace mongo {
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,
+ options, batchSize);
+ if (!cursor.get()) {
+ return v8AssertionException("error doing query: failed");
+ }
- try {
- 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,
- options, batchSize);
- if (!cursor.get())
- return v8::ThrowException(v8::String::New("error doing query: failed"));
-
- v8::Function* cons = (v8::Function*)(*(mongo->Get(scope->v8StringData("internalCursor"))));
-
- if (!cons)
- return v8::ThrowException(v8::String::New("Could not create a cursor"));
+ v8::Function* cons = (v8::Function*)(*(mongo->Get(scope->v8StringData("internalCursor"))));
- v8::Persistent<v8::Object> c = v8::Persistent<v8::Object>::New(cons->NewInstance());
- c.MakeWeak(cursor.get(), destroyCursor);
- c->SetInternalField(0, v8::External::New(cursor.release()));
- return c;
- }
- catch (...) {
- return v8::ThrowException(v8::String::New("socket error on query"));
+ if (!cons) {
+ return v8AssertionException("could not create a cursor");
}
+
+ v8::Persistent<v8::Object> c = v8::Persistent<v8::Object>::New(cons->NewInstance());
+ c.MakeWeak(cursor.get(), destroyCursor);
+ c->SetInternalField(0, v8::External::New(cursor.release()));
+ return c;
}
v8::Handle<v8::Value> mongoInsert(V8Scope* scope, const v8::Arguments& args) {
- uassert(16626, "insert needs 3 args", args.Length() == 3);
- uassert(16627, "attempted to insert a non-object", args[1]->IsObject());
+ argumentCheck(args.Length() == 3 ,"insert needs 3 args")
+ argumentCheck(args[1]->IsObject() ,"attempted to insert a non-object")
- if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue())
- return v8::ThrowException(v8::String::New("js db in read only mode"));
+ if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) {
+ return v8AssertionException("js db in read only mode");
+ }
DBClientBase * conn = getConnection(args);
GETNS;
@@ -228,11 +225,11 @@ namespace mongo {
v8::Local<v8::Array> arr = v8::Array::Cast(*args[1]);
vector<BSONObj> bos;
uint32_t len = arr->Length();
- uassert(16629, "attempted to insert an empty array", len);
+ argumentCheck(len > 0, "attempted to insert an empty array")
for(uint32_t i = 0; i < len; i++){
v8::Local<v8::Object> el = arr->CloneElementAt(i);
- uassert(16628, "attempted to insert an array of non-object types", !el.IsEmpty());
+ argumentCheck(!el.IsEmpty(), "attempted to insert an array of non-object types")
// Set ID on the element if necessary
if (!el->Has(scope->v8StringData("_id"))) {
@@ -242,13 +239,7 @@ namespace mongo {
}
bos.push_back(scope->v8ToMongo(el));
}
-
- try {
- conn->insert(ns.get(), bos, flags->Int32Value());
- }
- catch (...) {
- return v8::ThrowException(v8::String::New("socket error on bulk insert"));
- }
+ conn->insert(ns.get(), bos, flags->Int32Value());
}
else {
v8::Handle<v8::Object> in = args[1]->ToObject();
@@ -257,25 +248,19 @@ namespace mongo {
in->ForceSet(scope->v8StringData("_id"),
scope->getObjectIdCons()->NewInstance(0, argv));
}
-
BSONObj o = scope->v8ToMongo(in);
-
- try {
- conn->insert(ns.get(), o);
- }
- catch (...) {
- return v8::ThrowException(v8::String::New("socket error on insert"));
- }
+ conn->insert(ns.get(), o);
}
return v8::Undefined();
}
v8::Handle<v8::Value> mongoRemove(V8Scope* scope, const v8::Arguments& args) {
- jsassert(args.Length() == 2 || args.Length() == 3, "remove needs 2 args");
- jsassert(args[1]->IsObject(), "have to remove an object template");
+ argumentCheck(args.Length() == 2 || args.Length() == 3, "remove needs 2 or 3 args")
+ argumentCheck(args[1]->IsObject(), "attempted to remove a non-object")
- if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue())
- return v8::ThrowException(v8::String::New("js db in read only mode"));
+ if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) {
+ return v8AssertionException("js db in read only mode");
+ }
DBClientBase * conn = getConnection(args);
GETNS;
@@ -288,23 +273,18 @@ namespace mongo {
justOne = args[2]->BooleanValue();
}
- try {
- conn->remove(ns.get(), o, justOne);
- }
- catch (...) {
- return v8::ThrowException(v8::String::New("socket error on remove"));
- }
-
+ conn->remove(ns.get(), o, justOne);
return v8::Undefined();
}
v8::Handle<v8::Value> mongoUpdate(V8Scope* scope, const v8::Arguments& args) {
- jsassert(args.Length() >= 3, "update needs at least 3 args");
- jsassert(args[1]->IsObject(), "1st param to update has to be an object");
- jsassert(args[2]->IsObject(), "2nd param to update has to be an object");
+ argumentCheck(args.Length() >= 3, "update needs at least 3 args")
+ argumentCheck(args[1]->IsObject(), "1st param to update has to be an object")
+ argumentCheck(args[2]->IsObject(), "2nd param to update has to be an object")
- if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue())
- return v8::ThrowException(v8::String::New("js db in read only mode"));
+ if (args.This()->Get(scope->v8StringData("readOnly"))->BooleanValue()) {
+ return v8AssertionException("js db in read only mode");
+ }
DBClientBase * conn = getConnection(args);
GETNS;
@@ -313,49 +293,33 @@ namespace mongo {
v8::Handle<v8::Object> o = args[2]->ToObject();
bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value();
- bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value();
-
- try {
- BSONObj q1 = scope->v8ToMongo(q);
- BSONObj o1 = scope->v8ToMongo(o);
- conn->update(ns.get(), q1, o1, upsert, multi);
- }
- catch (...) {
- return v8::ThrowException(v8::String::New("socket error on remove"));
- }
+ bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value();
+ BSONObj q1 = scope->v8ToMongo(q);
+ BSONObj o1 = scope->v8ToMongo(o);
+ conn->update(ns.get(), q1, o1, upsert, multi);
return v8::Undefined();
}
v8::Handle<v8::Value> mongoAuth(V8Scope* scope, const v8::Arguments& args) {
- jsassert(args.Length() >= 3, "update needs at least 3 args");
+ argumentCheck(args.Length() == 3, "mongoAuth needs 3 args")
DBClientBase* conn = getConnection(args);
- string db = toSTLString(args[0]);
+ string db = toSTLString(args[0]);
string username = toSTLString(args[1]);
string password = toSTLString(args[2]);
- string errmsg = "";
-
- try {
- if (conn->auth(db, username, password, errmsg)) {
- return v8::Boolean::New(true);
- }
- } catch (...) {
+ string errmsg;
+ if (conn->auth(db, username, password, errmsg)) {
+ return v8::Boolean::New(true);
}
- return v8::ThrowException(v8::String::New(errmsg.c_str()));
+ return v8AssertionException(errmsg);
}
v8::Handle<v8::Value> mongoLogout(V8Scope* scope, const v8::Arguments& args) {
- jsassert(args.Length() == 1, "update needs 1 arg");
+ argumentCheck(args.Length() == 1, "logout needs 1 arg")
DBClientBase* conn = getConnection(args);
const string db = toSTLString(args[0]);
-
BSONObj ret;
- try {
- conn->logout(db, ret);
- }
- catch (const std::exception& ex) {
- return v8::ThrowException(v8::String::New(ex.what()));
- }
+ conn->logout(db, ret);
return scope->mongoToLZV8(ret, false);
}
@@ -379,8 +343,7 @@ namespace mongo {
mongo::DBClientCursor* cursor = getCursor(args);
if (! cursor)
return v8::Undefined();
- BSONObj o;
- o = cursor->next();
+ BSONObj o = cursor->next();
bool ro = false;
if (args.This()->Has(v8::String::New("_ro")))
ro = args.This()->Get(v8::String::New("_ro"))->BooleanValue();
@@ -394,9 +357,7 @@ namespace mongo {
mongo::DBClientCursor* cursor = getCursor(args);
if (! cursor)
return v8::Boolean::New(false);
- bool ret;
- ret = cursor->more();
- return v8::Boolean::New(ret);
+ return v8::Boolean::New(cursor->more());
}
/**
@@ -406,10 +367,8 @@ namespace mongo {
const v8::Arguments& args) {
mongo::DBClientCursor* cursor = getCursor(args);
if (! cursor)
- return v8::Number::New((double) 0);
- int ret;
- ret = cursor->objsLeftInBatch();
- return v8::Number::New((double) ret);
+ return v8::Number::New(0.0);
+ return v8::Number::New(static_cast<double>(cursor->objsLeftInBatch()));
}
/**
@@ -422,47 +381,46 @@ namespace mongo {
}
v8::Handle<v8::Value> dbInit(V8Scope* scope, const v8::Arguments& args) {
- uassert(16655, "db constructor requires 2 arguments", args.Length() == 2);
+ argumentCheck(args.Length() == 2, "db constructor requires 2 arguments")
args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]);
args.This()->ForceSet(scope->v8StringData("_name"), args[1]);
- for (int i=0; i<args.Length(); i++)
- uassert(16656, "db initializer called with undefined argument",
- !args[i]->IsUndefined());
+ for (int i = 0; i < args.Length(); i++) {
+ argumentCheck(!args[i]->IsUndefined(), "db initializer called with undefined argument")
+ }
string dbName = toSTLString(args[1]);
if (!NamespaceString::validDBName(dbName)) {
- string msg = str::stream() << "[" << dbName << "] is not a "
- << "valid database name";
- return v8::ThrowException(v8::String::New(msg.c_str()));
+ return v8AssertionException(str::stream() << "[" << dbName
+ << "] is not a valid database name");
}
-
return v8::Undefined();
}
v8::Handle<v8::Value> collectionInit(V8Scope* scope, const v8::Arguments& args) {
- uassert(16657, "collection constructor requires 4 arguments", args.Length() == 4);
+ argumentCheck(args.Length() == 4, "collection constructor requires 4 arguments")
args.This()->ForceSet(scope->v8StringData("_mongo"), args[0]);
args.This()->ForceSet(scope->v8StringData("_db"), args[1]);
args.This()->ForceSet(scope->v8StringData("_shortName"), args[2]);
args.This()->ForceSet(v8::String::New("_fullName"), args[3]);
- if (haveLocalShardingInfo(toSTLString(args[3])))
- return v8::ThrowException(v8::String::New("can't use sharded collection from db.eval"));
-
- for (int i=0; i<args.Length(); i++)
- uassert(16668, "collection constructor cannot take undefined argument",
- !args[i]->IsUndefined());
+ if (haveLocalShardingInfo(toSTLString(args[3]))) {
+ 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) {
- v8::Handle<v8::Object> t = args.This();
- uassert(16658, "dbQuery constructor requires at least 4 arguments", args.Length() >= 4);
+ argumentCheck(args.Length() >= 4, "dbQuery constructor requires at least 4 arguments")
+ v8::Handle<v8::Object> t = args.This();
t->ForceSet(scope->v8StringData("_mongo"), args[0]);
t->ForceSet(scope->v8StringData("_db"), args[1]);
t->ForceSet(scope->v8StringData("_collection"), args[2]);
@@ -535,12 +493,13 @@ namespace mongo {
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 v8::ThrowException(
- v8::String::New("can't use sharded collection from db.eval"));
+ 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] == '_') {
+ }
+ else if (sname.length() == 0 || sname[0] == '_') {
// if starts with '_' we dont return collection, one must use getCollection()
return v8::Handle<v8::Value>();
}
@@ -548,7 +507,9 @@ namespace mongo {
// no hit, create new collection
v8::Handle<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get(
v8::String::New("getCollection"));
- massert(16659, "getCollection is not a function", getCollection->IsFunction());
+ if (! getCollection->IsFunction()) {
+ return v8AssertionException("getCollection is not a function");
+ }
v8::Function* f = (v8::Function*)(*getCollection);
v8::Handle<v8::Value> argv[1];
@@ -561,7 +522,7 @@ namespace mongo {
return v8::Handle<v8::Value>();
}
- // cache collection for reuse, dont enumerate
+ // cache collection for reuse, don't enumerate
info.This()->ForceSet(name, coll, v8::DontEnum);
return coll;
}
@@ -595,8 +556,7 @@ namespace mongo {
Scope::validateObjectIdString(s);
}
catch (const MsgAssertionException& m) {
- string error = m.toString();
- return v8::ThrowException(v8::String::New(error.c_str()));
+ return v8AssertionException(m.toString());
}
oid.init(s);
}
@@ -612,15 +572,18 @@ namespace mongo {
return newInstance(f, args);
}
- if (args.Length() != 2 && args.Length() != 0) {
- return v8::ThrowException(v8::String::New("DBRef needs 2 arguments"));
- }
-
- if (args.Length() == 2) {
- it->ForceSet(scope->v8StringData("$ref"), args[0]);
- it->ForceSet(scope->v8StringData("$id"), args[1]);
+ // 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;
}
+ argumentCheck(args.Length() == 2, "DBRef needs 2 arguments")
+ it->ForceSet(scope->v8StringData("$ref"), args[0]);
+ it->ForceSet(scope->v8StringData("$id"), args[1]);
return it;
}
@@ -631,14 +594,10 @@ namespace mongo {
return newInstance(f, args);
}
- if (args.Length() != 2) {
- return v8::ThrowException(v8::String::New("DBPointer needs 2 arguments"));
- }
-
+ argumentCheck(args.Length() == 2, "DBPointer needs 2 arguments")
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;
}
@@ -658,7 +617,7 @@ namespace mongo {
it->ForceSet(scope->v8StringData("i"), args[1]);
}
else {
- return v8::ThrowException(v8::String::New("Timestamp needs 0 or 2 arguments"));
+ return v8AssertionException("Timestamp needs 0 or 2 arguments");
}
it->SetInternalField(0, v8::Uint32::New(Timestamp));
@@ -697,11 +656,13 @@ namespace mongo {
data = new char[rlen];
memcpy(data, tmp, rlen);
len = v8::Number::New(rlen);
- } else if (args.Length() == 0) {
+ }
+ else if (args.Length() == 0) {
// this is called by subclasses that will fill properties
return it;
- } else {
- return v8::ThrowException(v8::String::New("BinData needs 2 or 3 arguments"));
+ }
+ else {
+ return v8AssertionException("BinData needs 2 or 3 arguments");
}
it->ForceSet(scope->v8StringData("len"), len);
@@ -769,13 +730,9 @@ namespace mongo {
}
v8::Handle<v8::Value> uuidInit(V8Scope* scope, const v8::Arguments& args) {
- if (args.Length() != 1) {
- return v8::ThrowException(v8::String::New("UUIS needs 1 argument"));
- }
+ argumentCheck(args.Length() == 1, "UUID needs 1 argument")
v8::String::Utf8Value utf(args[0]);
- if(utf.length() != 32) {
- return v8::ThrowException(v8::String::New("UUIS string must have 32 characters"));
- }
+ argumentCheck(utf.length() == 32, "UUID string must have 32 characters")
v8::Function* f = scope->getNamedCons("BinData");
v8::Local<v8::Object> it = f->NewInstance();
@@ -783,13 +740,9 @@ namespace mongo {
}
v8::Handle<v8::Value> md5Init(V8Scope* scope, const v8::Arguments& args) {
- if (args.Length() != 1) {
- return v8::ThrowException(v8::String::New("MD5 needs 1 argument"));
- }
+ argumentCheck(args.Length() == 1, "MD5 needs 1 argument")
v8::String::Utf8Value utf(args[0]);
- if(utf.length() != 32) {
- return v8::ThrowException(v8::String::New("MD5 string must have 32 characters"));
- }
+ argumentCheck(utf.length() == 32, "MD5 string must have 32 characters")
v8::Function* f = scope->getNamedCons("BinData");
v8::Local<v8::Object> it = f->NewInstance();
@@ -797,9 +750,7 @@ namespace mongo {
}
v8::Handle<v8::Value> hexDataInit(V8Scope* scope, const v8::Arguments& args) {
- if (args.Length() != 2) {
- return v8::ThrowException(v8::String::New("HexData needs 2 arguments"));
- }
+ argumentCheck(args.Length() == 2, "HexData needs 2 arguments")
v8::String::Utf8Value utf(args[1]);
v8::Function* f = scope->getNamedCons("BinData");
v8::Local<v8::Object> it = f->NewInstance();
@@ -813,9 +764,8 @@ namespace mongo {
return newInstance(f, args);
}
- if (args.Length() != 0 && args.Length() != 1 && args.Length() != 3) {
- return v8::ThrowException(v8::String::New("NumberLong needs 0, 1 or 3 arguments"));
- }
+ argumentCheck(args.Length() == 0 || args.Length() == 1 || args.Length() == 3,
+ "NumberLong needs 0, 1 or 3 arguments")
if (args.Length() == 0) {
it->ForceSet(scope->v8StringData("floatApprox"), v8::Number::New(0));
@@ -832,9 +782,10 @@ namespace mongo {
try {
n = parseLL(numStr);
}
- catch (const AssertionException& ) {
- return v8::ThrowException(
- v8::String::New("could not convert string to long long"));
+ catch (const AssertionException&) {
+ return v8AssertionException(string("could not convert \"") +
+ num +
+ "\" to NumberLong");
}
unsigned long long val = n;
// values above 2^53 are not accurately represented in JS
@@ -858,7 +809,6 @@ namespace mongo {
it->ForceSet(scope->v8StringData("bottom"), args[2]);
}
it->SetHiddenValue(v8::String::New("__NumberLong"), v8::Number::New(1));
-
return it;
}
@@ -904,17 +854,13 @@ namespace mongo {
return newInstance(f, args);
}
- if (args.Length() != 0 && args.Length() != 1) {
- return v8::ThrowException(v8::String::New("NumberInt needs 0, 1 argument"));
- }
-
+ argumentCheck(args.Length() == 0 || args.Length() == 1, "NumberInt needs 0 or 1 arguments")
if (args.Length() == 0) {
it->SetHiddenValue(v8::String::New("__NumberInt"), v8::Number::New(0));
}
else if (args.Length() == 1) {
it->SetHiddenValue(v8::String::New("__NumberInt"), args[0]->ToInt32());
}
-
return it;
}
@@ -937,12 +883,11 @@ namespace mongo {
}
v8::Handle<v8::Value> bsonsize(V8Scope* scope, const v8::Arguments& args) {
- if (args.Length() != 1)
- return v8::ThrowException(v8::String::New("bsonsize needs 1 argument"));
- if (args[0]->IsNull())
+ argumentCheck(args.Length() == 1, "bsonsize needs 1 argument")
+ if (args[0]->IsNull()) {
return v8::Number::New(0);
- if (!args[0]->IsObject())
- return v8::ThrowException(v8::String::New("argument to bsonsize has to be an object"));
+ }
+ argumentCheck(args[0]->IsObject(), "argument to bsonsize has to be an object")
return v8::Number::New(scope->v8ToMongo(args[0]->ToObject()).objsize());
}
diff --git a/src/mongo/scripting/v8_utils.cpp b/src/mongo/scripting/v8_utils.cpp
index de20f0713e1..dcc6c89ec9e 100644
--- a/src/mongo/scripting/v8_utils.cpp
+++ b/src/mongo/scripting/v8_utils.cpp
@@ -51,32 +51,15 @@ namespace mongo {
std::string toSTLString(const v8::TryCatch* try_catch) {
stringstream ss;
- v8::String::Utf8Value exception(try_catch->Exception());
+ v8::String::Utf8Value exceptionText(try_catch->Exception());
+ ss << *exceptionText;
v8::Handle<v8::Message> message = try_catch->Message();
-
- if (message.IsEmpty()) {
- ss << *exception << endl;
- }
- else {
+ if (!message.IsEmpty()) {
v8::String::Utf8Value filename(message->GetScriptResourceName());
if (*filename) {
int linenum = message->GetLineNumber();
- ss << *filename << ":" << linenum << " ";
+ ss << " " << *filename << ":" << linenum;
}
- ss << *exception << endl;
-
- v8::String::Utf8Value sourceline(message->GetSourceLine());
- ss << *sourceline << endl;
-
- int start = message->GetStartColumn();
- for (int i = 0; i < start; i++)
- ss << " ";
-
- int end = message->GetEndColumn();
- for (int i = start; i < end; i++)
- ss << "^";
-
- ss << endl;
}
return ss.str();
}
@@ -89,16 +72,16 @@ namespace mongo {
std::ostream& operator<<(std::ostream& s, const v8::TryCatch* try_catch) {
v8::HandleScope handle_scope;
- v8::String::Utf8Value exception(try_catch->Exception());
+ v8::String::Utf8Value exceptionText(try_catch->Exception());
v8::Handle<v8::Message> message = try_catch->Message();
if (message.IsEmpty()) {
- s << *exception << endl;
+ s << *exceptionText << endl;
}
else {
v8::String::Utf8Value filename(message->GetScriptResourceName());
int linenum = message->GetLineNumber();
- cout << *filename << ":" << linenum << " " << *exception << endl;
+ cout << *filename << ":" << linenum << " " << *exceptionText << endl;
v8::String::Utf8Value sourceline(message->GetSourceLine());
cout << *sourceline << endl;
@@ -116,10 +99,6 @@ namespace mongo {
return s;
}
- void ReportException(v8::TryCatch* try_catch) {
- cout << try_catch << endl;
- }
-
class JSThreadConfig {
public:
JSThreadConfig(V8Scope* scope, const v8::Arguments& args, bool newScope = false) :
@@ -285,4 +264,10 @@ namespace mongo {
scope->injectV8Function("_scopedThreadInject", ScopedThreadInject, global);
}
+ v8::Handle<v8::Value> v8AssertionException(const char* errorMessage) {
+ return v8::ThrowException(v8::Exception::Error(v8::String::New(errorMessage)));
+ }
+ v8::Handle<v8::Value> v8AssertionException(const std::string& errorMessage) {
+ return v8AssertionException(errorMessage.c_str());
+ }
}
diff --git a/src/mongo/scripting/v8_utils.h b/src/mongo/scripting/v8_utils.h
index 1bdec4ae213..a53b4ba78d0 100644
--- a/src/mongo/scripting/v8_utils.h
+++ b/src/mongo/scripting/v8_utils.h
@@ -25,9 +25,12 @@
namespace mongo {
- void ReportException(v8::TryCatch* handler);
+#define jsassert(x,msg) uassert(16664, (msg), (x))
-#define jsassert(x,msg) uassert(16664, msg, x)
+#define argumentCheck(mustBeTrue, errorMessage) \
+ if (!(mustBeTrue)) { \
+ return v8AssertionException((errorMessage)); \
+ }
std::ostream& operator<<(std::ostream& s, const v8::Handle<v8::Value>& o);
std::ostream& operator<<(std::ostream& s, const v8::Handle<v8::TryCatch>* try_catch);
@@ -40,5 +43,20 @@ namespace mongo {
void installFork(V8Scope* scope,
v8::Handle<v8::Object>& global,
v8::Handle<v8::Context>& context);
+
+ /** Throw a V8 exception from Mongo callback code; message text will be preceded by "Error: ".
+ * Note: this function should be used for text that did not originate from the JavaScript
+ * engine. Errors from the JavaScript engine will already have a prefix such as
+ * ReferenceError, TypeError or SyntaxError.
+ * Note: call only from a native function called from JavaScript (a callback).
+ * The V8 ThrowException routine will note a JavaScript exception that will be
+ * "thrown" in JavaScript when we return from the native function.
+ * Note: it's required to return immediately to V8's execution control without calling any
+ * V8 API functions. In this state, an empty handle may (will) be returned.
+ * @param errorMessage Error message text.
+ * @return Empty handle to be returned from callback function.
+ */
+ v8::Handle<v8::Value> v8AssertionException(const char* errorMessage);
+ v8::Handle<v8::Value> v8AssertionException(const std::string& errorMessage);
}