diff options
Diffstat (limited to 'scripting/engine_spidermonkey.cpp')
-rw-r--r-- | scripting/engine_spidermonkey.cpp | 237 |
1 files changed, 168 insertions, 69 deletions
diff --git a/scripting/engine_spidermonkey.cpp b/scripting/engine_spidermonkey.cpp index 6609925..22102ba 100644 --- a/scripting/engine_spidermonkey.cpp +++ b/scripting/engine_spidermonkey.cpp @@ -15,15 +15,14 @@ * limitations under the License. */ -#include "stdafx.h" +#include "pch.h" #include "engine_spidermonkey.h" - #include "../client/dbclient.h" #ifndef _WIN32 #include <boost/date_time/posix_time/posix_time.hpp> #undef assert -#define assert xassert +#define assert MONGO_assert #endif #define smuassert( cx , msg , val ) \ @@ -38,6 +37,12 @@ } namespace mongo { + + class InvalidUTF8Exception : public UserException { + public: + InvalidUTF8Exception() : UserException( 9006 , "invalid utf8" ){ + } + }; string trim( string s ){ while ( s.size() && isspace( s[0] ) ) @@ -128,6 +133,54 @@ namespace mongo { return new BSONFieldIterator( this ); } + class TraverseStack { + public: + TraverseStack(){ + _o = 0; + _parent = 0; + } + + TraverseStack( JSObject * o , const TraverseStack * parent ){ + _o = o; + _parent = parent; + } + + TraverseStack dive( JSObject * o ) const { + if ( o ){ + uassert( 13076 , (string)"recursive toObject" , ! has( o ) ); + } + return TraverseStack( o , this ); + } + + int depth() const { + int d = 0; + const TraverseStack * s = _parent; + while ( s ){ + s = s->_parent; + d++; + } + return d; + } + + bool isTop() const { + return _parent == 0; + } + + bool has( JSObject * o ) const { + if ( ! o ) + return false; + const TraverseStack * s = this; + while ( s ){ + if ( s->_o == o ) + return true; + s = s->_parent; + } + return false; + } + + JSObject * _o; + const TraverseStack * _parent; + }; class Convertor : boost::noncopyable { public: @@ -171,7 +224,7 @@ namespace mongo { ( (boost::uint64_t)(boost::uint32_t)getNumber( o , "top" ) << 32 ) + ( boost::uint32_t)( getNumber( o , "bottom" ) ); } else { - val = (boost::uint64_t) getNumber( o, "floatApprox" ); + val = (boost::uint64_t)(boost::int64_t) getNumber( o, "floatApprox" ); } return val; } @@ -198,7 +251,7 @@ namespace mongo { return oid; } - BSONObj toObject( JSObject * o , int depth = 0){ + BSONObj toObject( JSObject * o , const TraverseStack& stack=TraverseStack() ){ if ( ! o ) return BSONObj(); @@ -222,10 +275,10 @@ namespace mongo { if ( ! appendSpecialDBObject( this , b , "value" , OBJECT_TO_JSVAL( o ) , o ) ){ - if ( depth == 0 ){ + if ( stack.isTop() ){ jsval theid = getProperty( o , "_id" ); if ( ! JSVAL_IS_VOID( theid ) ){ - append( b , "_id" , theid , EOO , depth + 1 ); + append( b , "_id" , theid , EOO , stack.dive( o ) ); } } @@ -237,10 +290,10 @@ namespace mongo { jsval nameval; assert( JS_IdToValue( _context ,id , &nameval ) ); string name = toString( nameval ); - if ( depth == 0 && name == "_id" ) + if ( stack.isTop() && name == "_id" ) continue; - append( b , name , getProperty( o , name.c_str() ) , orig[name].type() , depth + 1 ); + append( b , name , getProperty( o , name.c_str() ) , orig[name].type() , stack.dive( o ) ); } JS_DestroyIdArray( _context , properties ); @@ -271,39 +324,39 @@ namespace mongo { assert( s[0] == '/' ); s = s.substr(1); string::size_type end = s.rfind( '/' ); - b.appendRegex( name.c_str() , s.substr( 0 , end ).c_str() , s.substr( end + 1 ).c_str() ); + b.appendRegex( name , s.substr( 0 , end ).c_str() , s.substr( end + 1 ).c_str() ); } - void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO , int depth=0 ){ + void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO , const TraverseStack& stack=TraverseStack() ){ //cout << "name: " << name << "\t" << typeString( val ) << " oldType: " << oldType << endl; switch ( JS_TypeOfValue( _context , val ) ){ - case JSTYPE_VOID: b.appendUndefined( name.c_str() ); break; - case JSTYPE_NULL: b.appendNull( name.c_str() ); break; + case JSTYPE_VOID: b.appendUndefined( name ); break; + case JSTYPE_NULL: b.appendNull( name ); break; case JSTYPE_NUMBER: { double d = toNumber( val ); if ( oldType == NumberInt && ((int)d) == d ) - b.append( name.c_str() , (int)d ); + b.append( name , (int)d ); else - b.append( name.c_str() , d ); + b.append( name , d ); break; } - case JSTYPE_STRING: b.append( name.c_str() , toString( val ) ); break; - case JSTYPE_BOOLEAN: b.appendBool( name.c_str() , toBoolean( val ) ); break; + case JSTYPE_STRING: b.append( name , toString( val ) ); break; + case JSTYPE_BOOLEAN: b.appendBool( name , toBoolean( val ) ); break; case JSTYPE_OBJECT: { JSObject * o = JSVAL_TO_OBJECT( val ); if ( ! o || o == JSVAL_NULL ){ - b.appendNull( name.c_str() ); + b.appendNull( name ); } else if ( ! appendSpecialDBObject( this , b , name , val , o ) ){ - BSONObj sub = toObject( o , depth ); + BSONObj sub = toObject( o , stack ); if ( JS_IsArrayObject( _context , o ) ){ - b.appendArray( name.c_str() , sub ); + b.appendArray( name , sub ); } else { - b.append( name.c_str() , sub ); + b.append( name , sub ); } } break; @@ -315,7 +368,7 @@ namespace mongo { appendRegex( b , name , s ); } else { - b.appendCode( name.c_str() , getFunctionCode( val ).c_str() ); + b.appendCode( name , getFunctionCode( val ).c_str() ); } break; } @@ -334,7 +387,7 @@ namespace mongo { } bool isSimpleStatement( const string& code ){ - if ( code.find( "return" ) != string::npos ) + if ( hasJSReturn( code ) ) return false; if ( code.find( ";" ) != string::npos && @@ -416,7 +469,7 @@ namespace mongo { JSFunction * func = JS_CompileFunction( _context , assoc , fname.str().c_str() , params.size() , paramArray.get() , code.c_str() , strlen( code.c_str() ) , "nofile_b" , 0 ); if ( ! func ){ - cout << "compile failed for: " << raw << endl; + log() << "compile failed for: " << raw << endl; return 0; } gcName = "cf normal"; @@ -449,11 +502,11 @@ namespace mongo { free( dst ); if ( ! res ){ - cout << "decode failed. probably invalid utf-8 string [" << c << "]" << endl; + tlog() << "decode failed. probably invalid utf-8 string [" << c << "]" << endl; jsval v; if ( JS_GetPendingException( _context , &v ) ) - cout << "\t why: " << toString( v ) << endl; - throw UserException( 9006 , "invalid utf8" ); + tlog() << "\t why: " << toString( v ) << endl; + throw InvalidUTF8Exception(); } assert( s ); @@ -479,6 +532,24 @@ namespace mongo { return OBJECT_TO_JSVAL( o ); } + void makeLongObj( long long n, JSObject * o ) { + boost::uint64_t val = (boost::uint64_t)n; + CHECKNEWOBJECT(o,_context,"NumberLong1"); + setProperty( o , "floatApprox" , toval( (double)(boost::int64_t)( val ) ) ); + if ( (boost::int64_t)val != (boost::int64_t)(double)(boost::int64_t)( val ) ) { + // using 2 doubles here instead of a single double because certain double + // bit patterns represent undefined values and sm might trash them + setProperty( o , "top" , toval( (double)(boost::uint32_t)( val >> 32 ) ) ); + setProperty( o , "bottom" , toval( (double)(boost::uint32_t)( val & 0x00000000ffffffff ) ) ); + } + } + + jsval toval( long long n ) { + JSObject * o = JS_NewObject( _context , &numberlong_class , 0 , 0 ); + makeLongObj( n, o ); + return OBJECT_TO_JSVAL( o ); + } + jsval toval( const BSONElement& e ){ switch( e.type() ){ @@ -549,7 +620,9 @@ namespace mongo { } case Code:{ JSFunction * func = compileFunction( e.valuestr() ); - return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); + if ( func ) + return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); + return JSVAL_NULL; } case CodeWScope:{ JSFunction * func = compileFunction( e.codeWScopeCode() ); @@ -578,17 +651,7 @@ namespace mongo { return OBJECT_TO_JSVAL( o ); } case NumberLong: { - boost::uint64_t val = (boost::uint64_t)e.numberLong(); - JSObject * o = JS_NewObject( _context , &numberlong_class , 0 , 0 ); - CHECKNEWOBJECT(o,_context,"NumberLong1"); - setProperty( o , "floatApprox" , toval( (double)(boost::int64_t)( val ) ) ); - if ( (boost::int64_t)val != (boost::int64_t)(double)(boost::int64_t)( val ) ) { - // using 2 doubles here instead of a single double because certain double - // bit patterns represent undefined values and sm might trash them - setProperty( o , "top" , toval( (double)(boost::uint32_t)( val >> 32 ) ) ); - setProperty( o , "bottom" , toval( (double)(boost::uint32_t)( val & 0x00000000ffffffff ) ) ); - } - return OBJECT_TO_JSVAL( o ); + return toval( e.numberLong() ); } case DBRef: { JSObject * o = JS_NewObject( _context , &dbpointer_class , 0 , 0 ); @@ -609,13 +672,13 @@ namespace mongo { const char * data = e.binData( len ); assert( JS_SetPrivate( _context , o , new BinDataHolder( data ) ) ); - setProperty( o , "len" , toval( len ) ); - setProperty( o , "type" , toval( (int)e.binDataType() ) ); + setProperty( o , "len" , toval( (double)len ) ); + setProperty( o , "type" , toval( (double)e.binDataType() ) ); return OBJECT_TO_JSVAL( o ); } } - cout << "toval: unknown type: " << e.type() << endl; + log() << "toval: unknown type: " << (int) e.type() << endl; uassert( 10218 , "not done: toval" , 0 ); return 0; } @@ -824,13 +887,15 @@ namespace mongo { // --- global helpers --- JSBool native_print( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ){ + stringstream ss; Convertor c( cx ); for ( uintN i=0; i<argc; i++ ){ if ( i > 0 ) - cout << " "; - cout << c.toString( argv[i] ); + ss << " "; + ss << c.toString( argv[i] ); } - cout << endl; + ss << "\n"; + Logstream::logLockless( ss.str() ); return JS_TRUE; } @@ -894,14 +959,25 @@ namespace mongo { return JS_FALSE; } - BSONHolder * o = GETHOLDER( cx , JSVAL_TO_OBJECT( argv[ 0 ] ) ); + JSObject * o = JSVAL_TO_OBJECT( argv[0] ); + + Convertor c(cx); double size = 0; - if ( o ){ - size = o->_obj.objsize(); + + if ( JS_InstanceOf( cx , o , &bson_ro_class , 0 ) || + JS_InstanceOf( cx , o , &bson_class , 0 ) ){ + BSONHolder * h = GETHOLDER( cx , o ); + if ( h ){ + size = h->_obj.objsize(); + } } - Convertor c(cx); + else { + BSONObj temp = c.toObject( o ); + size = temp.objsize(); + } + *rval = c.toval( size ); - return JS_TRUE; + return JS_TRUE; } JSFunctionSpec objectHelpers[] = { @@ -934,7 +1010,15 @@ namespace mongo { return JS_TRUE; } - jsval val = c.toval( e ); + jsval val; + try { + val = c.toval( e ); + } + catch ( InvalidUTF8Exception& ) { + JS_LeaveLocalRootScope( cx ); + JS_ReportError( cx , "invalid utf8" ); + return JS_FALSE; + } assert( ! holder->_inResolve ); holder->_inResolve = true; @@ -1115,20 +1199,22 @@ namespace mongo { } void localConnect( const char * dbName ){ - smlock; - uassert( 10225 , "already setup for external db" , ! _externalSetup ); - if ( _localConnect ){ - uassert( 10226 , "connected to different db" , _localDBName == dbName ); - return; + { + smlock; + uassert( 10225 , "already setup for external db" , ! _externalSetup ); + if ( _localConnect ){ + uassert( 10226 , "connected to different db" , _localDBName == dbName ); + return; + } + + initMongoJS( this , _context , _global , true ); + + exec( "_mongo = new Mongo();" ); + exec( ((string)"db = _mongo.getDB( \"" + dbName + "\" ); ").c_str() ); + + _localConnect = true; + _localDBName = dbName; } - - initMongoJS( this , _context , _global , true ); - - exec( "_mongo = new Mongo();" ); - exec( ((string)"db = _mongo.getDB( \"" + dbName + "\" ); ").c_str() ); - - _localConnect = true; - _localDBName = dbName; loadStored(); } @@ -1309,6 +1395,15 @@ namespace mongo { JSBool worked = JS_EvaluateScript( _context , _global , code.c_str() , strlen( code.c_str() ) , name.c_str() , 0 , &ret ); uninstallCheckTimeout( timeoutMs ); + if ( ! worked && _error.size() == 0 ){ + jsval v; + if ( JS_GetPendingException( _context , &v ) ){ + _error = _convertor->toString( v ); + if ( reportError ) + cout << _error << endl; + } + } + if ( assertOnError ) uassert( 10228 , name + " exec failed" , worked ); @@ -1387,7 +1482,6 @@ namespace mongo { code << field << "_" << " = { x : " << field << "_ }; "; code << field << " = function(){ return nativeHelper.apply( " << field << "_ , arguments ); }"; exec( code.str().c_str() ); - } virtual void gc(){ @@ -1424,15 +1518,20 @@ namespace mongo { }; + /* used to make the logging not overly chatty in the mongo shell. */ + extern bool isShell; + void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ){ stringstream ss; - ss << "JS Error: " << message; + if( !isShell ) + ss << "JS Error: "; + ss << message; if ( report && report->filename ){ ss << " " << report->filename << ":" << report->lineno; } - log() << ss.str() << endl; + tlog() << ss.str() << endl; if ( currentScope.get() ){ currentScope->gotError( ss.str() ); @@ -1446,10 +1545,10 @@ namespace mongo { for ( uintN i=0; i<argc; i++ ){ string filename = c.toString( argv[i] ); - cout << "should load [" << filename << "]" << endl; + //cout << "load [" << filename << "]" << endl; if ( ! s->execFile( filename , false , true , false ) ){ - JS_ReportError( cx , ((string)"error loading file: " + filename ).c_str() ); + JS_ReportError( cx , ((string)"error loading js file: " + filename ).c_str() ); return JS_FALSE; } } |