// engine_spidermonkey.cpp /* Copyright 2009 10gen Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "pch.h" #include "mongo/scripting/engine_spidermonkey.h" #include #include #ifndef _WIN32 #include #endif #include #include "mongo/util/mongoutils/str.h" #define smuassert( cx , msg , val ) \ if ( ! ( val ) ){ \ JS_ReportError( cx , msg ); \ return JS_FALSE; \ } #define CHECKNEWOBJECT(xx,ctx,w) \ if ( ! xx ){ \ massert(13072,(string)"JS_NewObject failed: " + w ,xx); \ } #define CHECKJSALLOC( newthing ) \ massert( 13615 , "JS allocation failed, either memory leak or using too much memory" , newthing ) namespace mongo { string trim( string s ) { while ( s.size() && isspace( s[0] ) ) s = s.substr( 1 ); while ( s.size() && isspace( s[s.size()-1] ) ) s = s.substr( 0 , s.size() - 1 ); return s; } boost::thread_specific_ptr currentScope( dontDeleteScope ); boost::recursive_mutex &smmutex = *( new boost::recursive_mutex ); #define smlock boost::recursive_mutex::scoped_lock ___lk( smmutex ); #define GETHOLDER(x,o) ((BSONHolder*)JS_GetPrivate( x , o )) class BSONFieldIterator; class BSONHolder { public: BSONHolder( BSONObj obj ) { _obj = obj.getOwned(); _inResolve = false; _modified = false; _magic = 17; } ~BSONHolder() { _magic = 18; } void check() { uassert( 10212 , "holder magic value is wrong" , _magic == 17 && _obj.isValid() ); } BSONFieldIterator * it(); BSONObj _obj; bool _inResolve; char _magic; list _extra; set _removed; bool _modified; }; class BSONFieldIterator { public: BSONFieldIterator( BSONHolder * holder ) { set added; BSONObjIterator it( holder->_obj ); while ( it.more() ) { BSONElement e = it.next(); if ( holder->_removed.count( e.fieldName() ) ) continue; _names.push_back( e.fieldName() ); added.insert( e.fieldName() ); } for ( list::iterator i = holder->_extra.begin(); i != holder->_extra.end(); i++ ) { if ( ! added.count( *i ) ) _names.push_back( *i ); } _it = _names.begin(); } bool more() { return _it != _names.end(); } string next() { string s = *_it; _it++; return s; } private: list _names; list::iterator _it; }; BSONFieldIterator * BSONHolder::it() { 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: Convertor( JSContext * cx ) { _context = cx; } string toString( JSString* jsString ) { size_t srclen = JS_GetStringLength( jsString ); if( srclen == 0 ) return ""; size_t len = (srclen * 6) + 1; boost::scoped_array utf8Chars( new char[len] ); jschar* utf16Chars = JS_GetStringChars( jsString ); if ( !JS_EncodeCharacters( _context, utf16Chars, srclen, utf8Chars.get(), &len ) ) { uasserted( 16268, "error converting UTF-16 string to UTF-8" ); } return string( utf8Chars.get(), len ); } string toString( jsval v ) { return toString( JS_ValueToString( _context , v ) ); } // NOTE No validation of passed in object long long toNumberLongUnsafe( JSObject *o ) { boost::uint64_t val; if ( hasProperty( o, "top" ) ) { val = ( (boost::uint64_t)(boost::uint32_t)getNumber( o , "top" ) << 32 ) + ( boost::uint32_t)( getNumber( o , "bottom" ) ); } else { val = (boost::uint64_t)(boost::int64_t) getNumber( o, "floatApprox" ); } return val; } int toNumberInt( JSObject *o ) { return (boost::uint32_t)(boost::int32_t) getNumber( o, "floatApprox" ); } double toNumber( jsval v ) { double d; uassert( 10214 , "not a number" , JS_ValueToNumber( _context , v , &d ) ); return d; } bool toBoolean( jsval v ) { JSBool b; verify( JS_ValueToBoolean( _context, v , &b ) ); return b; } OID toOID( jsval v ) { JSContext * cx = _context; verify( JSVAL_IS_OID( v ) ); JSObject * o = JSVAL_TO_OBJECT( v ); OID oid; oid.init( getString( o , "str" ) ); return oid; } BSONObj toObject( JSObject * o , const TraverseStack& stack=TraverseStack() ) { if ( ! o ) return BSONObj(); if ( JS_InstanceOf( _context , o , &bson_ro_class , 0 ) ) { BSONHolder * holder = GETHOLDER( _context , o ); verify( holder ); return holder->_obj.getOwned(); } BSONObj orig; if ( JS_InstanceOf( _context , o , &bson_class , 0 ) ) { BSONHolder * holder = GETHOLDER(_context,o); verify( holder ); if ( ! holder->_modified ) { return holder->_obj; } orig = holder->_obj; } BSONObjBuilder b; if ( ! appendSpecialDBObject( this , b , "value" , OBJECT_TO_JSVAL( o ) , o ) ) { if ( stack.isTop() ) { jsval theid = getProperty( o , "_id" ); if ( ! JSVAL_IS_VOID( theid ) ) { append( b , "_id" , theid , EOO , stack.dive( o ) ); } } JSIdArray * properties = JS_Enumerate( _context , o ); verify( properties ); try { for ( jsint i=0; ilength; i++ ) { jsid id = properties->vector[i]; jsval nameval; verify( JS_IdToValue( _context, id, &nameval ) ); string name = toString( nameval ); if ( stack.isTop() && name == "_id" ) continue; append( b, name, getProperty( o, name.c_str() ), orig[name].type(), stack.dive( o ) ); } } catch ( const AssertionException& ) { JS_DestroyIdArray( _context , properties ); throw; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16269 ); } JS_DestroyIdArray( _context , properties ); } return b.obj(); } BSONObj toObject( jsval v ) { if ( JSVAL_IS_NULL( v ) || JSVAL_IS_VOID( v ) ) return BSONObj(); uassert( 10215, "not an object", JSVAL_IS_OBJECT( v ) ); return toObject( JSVAL_TO_OBJECT( v ) ); } string getFunctionCode( JSFunction * func ) { return toString( JS_DecompileFunction( _context , func , 0 ) ); } string getFunctionCode( jsval v ) { uassert( 10216, "not a function", JS_TypeOfValue( _context, v ) == JSTYPE_FUNCTION ); return getFunctionCode( JS_ValueToFunction( _context , v ) ); } void appendRegex( BSONObjBuilder& b , const string& name , string s ) { verify( s[0] == '/' ); s = s.substr(1); string::size_type end = s.rfind( '/' ); b.appendRegex( name , s.substr( 0 , end ) , s.substr( end + 1 ) ); } 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 ); break; case JSTYPE_NULL: b.appendNull( name ); break; case JSTYPE_NUMBER: { double d = toNumber( val ); if ( oldType == NumberInt && ((int)d) == d ) b.append( name , (int)d ); else b.append( name , d ); 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 ); } else if ( ! appendSpecialDBObject( this , b , name , val , o ) ) { BSONObj sub = toObject( o , stack ); if ( JS_IsArrayObject( _context , o ) ) { b.appendArray( name , sub ); } else { b.append( name , sub ); } } break; } case JSTYPE_FUNCTION: { string s = toString(val); if ( s[0] == '/' ) { appendRegex( b , name , s ); } else { b.appendCode( name , getFunctionCode( val ) ); } break; } default: uassert( 10217 , (string)"can't append field. name:" + name + " type: " + typeString( val ) , 0 ); } } // ---------- to spider monkey --------- bool hasFunctionIdentifier( const string& code ) { if ( code.size() < 9 || code.find( "function" ) != 0 ) return false; return code[8] == ' ' || code[8] == '('; } bool isSimpleStatement( const string& code ) { if ( hasJSReturn( code ) ) return false; if ( code.find( ';' ) != string::npos && code.find( ';' ) != code.rfind( ';' ) ) return false; if ( code.find( '\n') != string::npos ) return false; if ( code.find( "for(" ) != string::npos || code.find( "for (" ) != string::npos || code.find( "while (" ) != string::npos || code.find( "while(" ) != string::npos ) return false; return true; } JSFunction * compileFunction( const char * code, JSObject * assoc = 0 ) { const char * gcName = "unknown"; JSFunction * f = _compileFunction( code , assoc , gcName ); return f; } JSFunction * _compileFunction( const char * raw , JSObject * assoc , const char *& gcName ) { if ( ! assoc ) assoc = JS_GetGlobalObject( _context ); raw = jsSkipWhiteSpace( raw ); //cout << "RAW\n---\n" << raw << "\n---" << endl; static int fnum = 1; stringstream fname; fname << "__cf__" << fnum++ << "__"; if ( ! hasFunctionIdentifier( raw ) ) { string s = raw; if ( isSimpleStatement( s ) ) { s = "return " + s; } gcName = "cf anon"; fname << "anon"; return JS_CompileFunction( _context , assoc , fname.str().c_str() , 0 , 0 , s.c_str() , s.size() , "nofile_a" , 0 ); } string code = raw; size_t start = code.find( '(' ); verify( start != string::npos ); string fbase; if ( start > 9 ) { fbase = trim( code.substr( 9 , start - 9 ) ); } if ( fbase.length() == 0 ) { fbase = "anonymous_function"; } fname << "f__" << fbase; code = code.substr( start + 1 ); size_t end = code.find( ')' ); verify( end != string::npos ); string paramString = trim( code.substr( 0 , end ) ); code = code.substr( end + 1 ); vector params; while ( paramString.size() ) { size_t c = paramString.find( ',' ); if ( c == string::npos ) { params.push_back( paramString ); break; } params.push_back( trim( paramString.substr( 0 , c ) ) ); paramString = trim( paramString.substr( c + 1 ) ); paramString = trim( paramString ); } boost::scoped_array paramArray (new const char*[params.size()]); for ( size_t i=0; ifirstElementFieldName() ) { JSObject * o = JS_NewObject( _context , &dbref_class , NULL, NULL); CHECKNEWOBJECT(o,_context,"toJSObject1"); verify( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); return o; } JSObject * o = JS_NewObject( _context , readOnly ? &bson_ro_class : &bson_class , NULL, NULL); CHECKNEWOBJECT(o,_context,"toJSObject2"); verify( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); return o; } jsval toval( const BSONObj* obj , bool readOnly=false ) { JSObject * o = toJSObject( obj , readOnly ); return OBJECT_TO_JSVAL( o ); } void makeLongObj( long long n, JSObject * o ) { boost::uint64_t val = (boost::uint64_t)n; CHECKNEWOBJECT(o,_context,"NumberLong1"); double floatApprox = (double)(boost::int64_t)val; setProperty( o , "floatApprox" , toval( floatApprox ) ); if ( (boost::int64_t)val != (boost::int64_t)floatApprox ) { // 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 ); } void makeIntObj( int n, JSObject * o ) { boost::uint32_t val = (boost::uint32_t)n; CHECKNEWOBJECT(o,_context,"NumberInt1"); double floatApprox = (double)(boost::int32_t)val; setProperty( o , "floatApprox" , toval( floatApprox ) ); } jsval toval( int n ) { JSObject * o = JS_NewObject( _context , &numberint_class , 0 , 0 ); makeIntObj( n, o ); return OBJECT_TO_JSVAL( o ); } jsval toval( const BSONElement& e ) { switch( e.type() ) { case EOO: case jstNULL: case Undefined: return JSVAL_NULL; case NumberDouble: case NumberInt: return toval( e.number() ); // case NumberInt: // return toval( e.numberInt() ); case Symbol: // TODO: should we make a special class for this case String: return toval( e.valuestr() ); case Bool: return e.boolean() ? JSVAL_TRUE : JSVAL_FALSE; case Object: { BSONObj embed = e.embeddedObject().getOwned(); return toval( &embed ); } case Array: { BSONObj embed = e.embeddedObject().getOwned(); if ( embed.isEmpty() ) { return OBJECT_TO_JSVAL( JS_NewArrayObject( _context , 0 , 0 ) ); } JSObject * array = JS_NewArrayObject( _context , 1 , 0 ); CHECKJSALLOC( array ); jsval myarray = OBJECT_TO_JSVAL( array ); BSONObjIterator i( embed ); while ( i.more() ){ const BSONElement& e = i.next(); jsval v = toval( e ); verify( JS_SetElement( _context , array , atoi(e.fieldName()) , &v ) ); } return myarray; } case jstOID: { OID oid = e.__oid(); JSObject * o = JS_NewObject( _context , &object_id_class , 0 , 0 ); CHECKNEWOBJECT(o,_context,"jstOID"); setProperty( o , "str" , toval( oid.str().c_str() ) ); return OBJECT_TO_JSVAL( o ); } case RegEx: { const char * flags = e.regexFlags(); uintN flagNumber = 0; while ( *flags ) { switch ( *flags ) { case 'g': flagNumber |= JSREG_GLOB; break; case 'i': flagNumber |= JSREG_FOLD; break; case 'm': flagNumber |= JSREG_MULTILINE; break; //case 'y': flagNumber |= JSREG_STICKY; break; default: log() << "warning: unknown regex flag:" << *flags << endl; } flags++; } JSObject * r = JS_NewRegExpObject( _context , (char*)e.regex() , strlen( e.regex() ) , flagNumber ); verify( r ); return OBJECT_TO_JSVAL( r ); } case Code: { JSFunction * func = compileFunction( e.valuestr() ); if ( func ) return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); return JSVAL_NULL; } case CodeWScope: { JSFunction * func = compileFunction( e.codeWScopeCode() ); if ( !func ) return JSVAL_NULL; BSONObj extraScope = e.codeWScopeObject(); if ( ! extraScope.isEmpty() ) { log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; } return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); } case Date: return OBJECT_TO_JSVAL( js_NewDateObjectMsec( _context , (jsdouble) ((long long)e.date().millis) ) ); case MinKey: return OBJECT_TO_JSVAL( JS_NewObject( _context , &minkey_class , 0 , 0 ) ); case MaxKey: return OBJECT_TO_JSVAL( JS_NewObject( _context , &maxkey_class , 0 , 0 ) ); case Timestamp: { JSObject * o = JS_NewObject( _context , ×tamp_class , 0 , 0 ); CHECKNEWOBJECT(o,_context,"Timestamp1"); setProperty( o , "t" , toval( (double)(e.timestampTime()) ) ); setProperty( o , "i" , toval( (double)(e.timestampInc()) ) ); return OBJECT_TO_JSVAL( o ); } case NumberLong: { return toval( e.numberLong() ); } case DBRef: { JSObject * o = JS_NewObject( _context , &dbpointer_class , 0 , 0 ); CHECKNEWOBJECT(o,_context,"DBRef1"); setProperty( o , "ns" , toval( e.dbrefNS() ) ); JSObject * oid = JS_NewObject( _context , &object_id_class , 0 , 0 ); CHECKNEWOBJECT(oid,_context,"DBRef2"); setProperty( oid , "str" , toval( e.dbrefOID().str().c_str() ) ); setProperty( o , "id" , OBJECT_TO_JSVAL( oid ) ); return OBJECT_TO_JSVAL( o ); } case BinData: { JSObject * o = JS_NewObject( _context , &bindata_class , 0 , 0 ); CHECKNEWOBJECT(o,_context,"Bindata_BinData1"); int len; const char * data = e.binData( len ); verify( data ); verify( JS_SetPrivate( _context , o , new BinDataHolder( data , len ) ) ); setProperty( o , "len" , toval( (double)len ) ); setProperty( o , "type" , toval( (double)e.binDataType() ) ); return OBJECT_TO_JSVAL( o ); } } log() << "toval: unknown type: " << (int) e.type() << endl; uassert( 10218 , "not done: toval" , 0 ); return 0; } // ------- object helpers ------ JSObject * getJSObject( JSObject * o , const char * name ) { jsval v; verify( JS_GetProperty( _context , o , name , &v ) ); return JSVAL_TO_OBJECT( v ); } JSObject * getGlobalObject( const char * name ) { return getJSObject( JS_GetGlobalObject( _context ) , name ); } JSObject * getGlobalPrototype( const char * name ) { return getJSObject( getGlobalObject( name ) , "prototype" ); } bool hasProperty( JSObject * o , const char * name ) { JSBool res; verify( JS_HasProperty( _context , o , name , & res ) ); return res; } jsval getProperty( JSObject * o , const char * field ) { uassert( 10219 , "object passed to getPropery is null" , o ); jsval v; verify( JS_GetProperty( _context , o , field , &v ) ); return v; } void setProperty( JSObject * o , const char * field , jsval v ) { verify( JS_SetProperty( _context , o , field , &v ) ); } string typeString( jsval v ) { JSType t = JS_TypeOfValue( _context , v ); return JS_GetTypeName( _context , t ); } bool getBoolean( JSObject * o , const char * field ) { return toBoolean( getProperty( o , field ) ); } double getNumber( JSObject * o , const char * field ) { return toNumber( getProperty( o , field ) ); } string getString( JSObject * o , const char * field ) { return toString( getProperty( o , field ) ); } JSClass * getClass( JSObject * o , const char * field ) { jsval v; verify( JS_GetProperty( _context , o , field , &v ) ); if ( ! JSVAL_IS_OBJECT( v ) ) return 0; return JS_GET_CLASS( _context , JSVAL_TO_OBJECT( v ) ); } JSContext * _context; }; void bson_finalize( JSContext * cx , JSObject * obj ) { try { BSONHolder * o = GETHOLDER( cx , obj ); if ( o ) { delete o; verify( JS_SetPrivate( cx , obj , 0 ) ); } } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) JS_ReportError( cx, e.what() ); } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16271 ); } } JSBool bson_enumerate( JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp ) { try { BSONHolder * o = GETHOLDER( cx , obj ); if ( enum_op == JSENUMERATE_INIT ) { if ( o ) { BSONFieldIterator * it = o->it(); *statep = PRIVATE_TO_JSVAL( it ); } else { *statep = 0; } if ( idp ) *idp = JSVAL_ZERO; return JS_TRUE; } BSONFieldIterator * it = (BSONFieldIterator*)JSVAL_TO_PRIVATE( *statep ); if ( ! it ) { *statep = 0; return JS_TRUE; } if ( enum_op == JSENUMERATE_NEXT ) { if ( it->more() ) { string name = it->next(); Convertor c(cx); verify( JS_ValueToId( cx , c.toval( name.c_str() ) , idp ) ); } else { delete it; *statep = 0; } return JS_TRUE; } if ( enum_op == JSENUMERATE_DESTROY ) { if ( it ) delete it; return JS_TRUE; } uassert( 10220 , "don't know what to do with this op" , 0 ); return JS_FALSE; } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16272 ); } } JSBool noaccess( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { try { BSONHolder * holder = GETHOLDER( cx , obj ); if ( ! holder ) { // in init code still return JS_TRUE; } if ( holder->_inResolve ) return JS_TRUE; JS_ReportError( cx , "doing write op on read only operation" ); return JS_FALSE; } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16273 ); } } JSClass bson_ro_class = { "bson_ro_object", // class name JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE, // flags noaccess, // addProperty noaccess, // delProperty JS_PropertyStub, // getProperty noaccess, // setProperty (JSEnumerateOp)bson_enumerate, // enumerate (JSResolveOp)resolveBSONField, // resolve JS_ConvertStub, // convert bson_finalize, // finalize JSCLASS_NO_OPTIONAL_MEMBERS // optional members }; JSBool bson_cons( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) { JS_ReportError( cx , "can't construct bson object" ); return JS_FALSE; } JSFunctionSpec bson_functions[] = { { 0 } }; JSBool bson_add_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { try { BSONHolder * holder = GETHOLDER( cx , obj ); if ( ! holder ) { // static init return JS_TRUE; } if ( ! holder->_inResolve ) { Convertor c(cx); string name( c.toString( idval ) ); if ( holder->_obj[name].eoo() ) { holder->_extra.push_back( name ); } holder->_modified = true; } } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16274 ); } return JS_TRUE; } JSBool mark_modified( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { try { Convertor c(cx); BSONHolder * holder = GETHOLDER( cx , obj ); if ( !holder ) // needed when we're messing with DBRef.prototype return JS_TRUE; if ( holder->_inResolve ) return JS_TRUE; holder->_modified = true; holder->_removed.erase( c.toString( idval ) ); } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16275 ); } return JS_TRUE; } JSBool mark_modified_remove( JSContext *cx, JSObject *obj, jsval idval, jsval *vp) { try { Convertor c(cx); BSONHolder * holder = GETHOLDER( cx , obj ); if ( holder->_inResolve ) return JS_TRUE; holder->_modified = true; holder->_removed.insert( c.toString( idval ) ); } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16276 ); } return JS_TRUE; } JSClass bson_class = { "bson_object", // class name JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE, // flags bson_add_prop, // addProperty mark_modified_remove, // delProperty JS_PropertyStub, // getProperty mark_modified, // setProperty (JSEnumerateOp)bson_enumerate, // enumerate (JSResolveOp)resolveBSONField, // resolve JS_ConvertStub, // convert bson_finalize, // finalize JSCLASS_NO_OPTIONAL_MEMBERS // optional members }; static JSClass global_class = { "global", // class name JSCLASS_GLOBAL_FLAGS, // flags JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty JS_PropertyStub, // getProperty JS_PropertyStub, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert JS_FinalizeStub, // finalize JSCLASS_NO_OPTIONAL_MEMBERS // optional members }; // --- global helpers --- static void hexToBinData( JSContext* cx, Convertor* c, jsval* rval, int subtype, const string& s ) { JSObject * o = JS_NewObject( cx , &bindata_class , 0 , 0 ); CHECKNEWOBJECT(o,_context,"Bindata_BinData1"); size_t s_size = s.size(); int len = s_size / 2; boost::scoped_array data( new char[len] ); char* p = data.get(); const char *src = s.c_str(); for( size_t i = 0; i+1 < s_size; i += 2 ) { *p++ = fromHex( src + i ); } verify( JS_SetPrivate( cx, o, new BinDataHolder( data.get(), len ) ) ); c->setProperty( o, "len", c->toval( static_cast(len) ) ); c->setProperty( o, "type", c->toval( static_cast(subtype) ) ); *rval = OBJECT_TO_JSVAL( o ); } static bool testHexString( JSContext* cx, const string& hexString ) { size_t len = hexString.length(); for ( size_t i = 0; i < len; ++i ) { char ch = hexString[i]; if ( (ch>='0' && ch<='9') || (ch>='a' && ch<='f') || (ch>='A' && ch<='F') ) { continue; } JS_ReportError( cx, "invalid hexstring character '%c'", ch ); return false; } return true; } JSBool _HexData( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { try { if ( argc != 2 ) { JS_ReportError( cx , "HexData needs 2 arguments -- HexData(subtype,hexstring)" ); return JS_FALSE; } Convertor c( cx ); int subtype = static_cast( c.toNumber( argv[ 0 ] ) ); if ( subtype == 2 ) { JS_ReportError( cx , "BinData subtype 2 is deprecated" ); return JS_FALSE; } else if ( subtype < 0 || subtype > 255 ) { JS_ReportError( cx, "subtype must be between 0 and 255" ); return JS_FALSE; } string s( c.toString( argv[1] ) ); if ( ! testHexString( cx, s ) ) { return JS_FALSE; } size_t len = s.length(); if ( 0 != ( len % 2 ) ) { JS_ReportError( cx, "hexstring must be even length" ); return JS_FALSE; } hexToBinData(cx, &c, rval, subtype, s); } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16277 ); } return JS_TRUE; } JSBool _UUID( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { try { if ( argc != 1 ) { JS_ReportError( cx , "UUID needs argument -- UUID(hexstring)" ); return JS_FALSE; } Convertor c( cx ); string s( c.toString( argv[0] ) ); if ( ! testHexString( cx, s ) ) { return JS_FALSE; } size_t len = s.length(); if ( len != 32 ) { JS_ReportError( cx, "UUID hexstring length is %d, must be 32", len ); return JS_FALSE; } hexToBinData(cx, &c, rval, 3, s); } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16278 ); } return JS_TRUE; } JSBool _MD5( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ) { try { if ( argc != 1 ) { JS_ReportError( cx , "MD5 needs argument -- MD5(hexstring)" ); return JS_FALSE; } Convertor c( cx ); string s( c.toString( argv[0] ) ); if ( ! testHexString( cx, s ) ) { return JS_FALSE; } size_t len = s.length(); if ( len != 32 ) { JS_ReportError( cx, "MD5 hexstring length is %d, must be 32", len ); return JS_FALSE; } hexToBinData(cx, &c, rval, 5, s); } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16279 ); } return JS_TRUE; } JSBool native_print( JSContext * cx, JSObject * obj, uintN argc, jsval *argv, jsval *rval ) { stringstream ss; bool someWritten = false; try { Convertor c( cx ); for ( uintN i=0; i 0 ) ss << " "; ss << c.toString( argv[i] ); someWritten = true; } ss << "\n"; Logstream::logLockless( ss.str() ); } catch ( const AssertionException& ) { if ( someWritten ) { ss << "\n"; Logstream::logLockless( ss.str() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16280 ); } return JS_TRUE; } JSBool native_helper( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { try { Convertor c(cx); NativeFunction func = reinterpret_cast( static_cast( c.getNumber( obj , "x" ) ) ); void* data = reinterpret_cast( static_cast( c.getNumber( obj , "y" ) ) ); verify( func ); BSONObj a; if ( argc > 0 ) { BSONObjBuilder args; for ( uintN i = 0; i < argc; ++i ) { c.append( args , args.numStr( i ) , argv[i] ); } a = args.obj(); } BSONObj out; try { out = func( a, data ); } catch ( std::exception& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } if ( out.isEmpty() ) { *rval = JSVAL_VOID; } else { *rval = c.toval( out.firstElement() ); } } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16281 ); } return JS_TRUE; } JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ); JSBool native_gc( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { JS_GC( cx ); return JS_TRUE; } JSFunctionSpec globalHelpers[] = { { "print" , &native_print , 0 , 0 , 0 } , { "nativeHelper" , &native_helper , 1 , 0 , 0 } , { "load" , &native_load , 1 , 0 , 0 } , { "gc" , &native_gc , 1 , 0 , 0 } , { "UUID", &_UUID, 0, 0, 0 } , { "MD5", &_MD5, 0, 0, 0 } , { "HexData", &_HexData, 0, 0, 0 } , { 0 , 0 , 0 , 0 , 0 } }; // ----END global helpers ---- // Object helpers JSBool bson_get_size(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { try { if ( argc != 1 || !JSVAL_IS_OBJECT( argv[ 0 ] ) ) { JS_ReportError( cx , "bsonsize requires one valid object" ); return JS_FALSE; } Convertor c(cx); if ( argv[0] == JSVAL_VOID || argv[0] == JSVAL_NULL ) { *rval = c.toval( 0.0 ); return JS_TRUE; } JSObject * o = JSVAL_TO_OBJECT( argv[0] ); double size = 0.0; 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(); } } else { BSONObj temp = c.toObject( o ); size = temp.objsize(); } *rval = c.toval( size ); } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16282 ); } return JS_TRUE; } JSFunctionSpec objectHelpers[] = { { "bsonsize" , &bson_get_size , 1 , 0 , 0 } , { 0 , 0 , 0 , 0 , 0 } }; // end Object helpers JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) { try { verify( JS_EnterLocalRootScope( cx ) ); } catch ( const AssertionException& e ) { JS_ReportError( cx, e.what() ); return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16283 ); } try { BSONHolder * holder = GETHOLDER( cx , obj ); if ( ! holder ) { // static init *objp = 0; JS_LeaveLocalRootScope( cx ); return JS_TRUE; } holder->check(); Convertor c( cx ); string s( c.toString( id ) ); BSONElement e = holder->_obj[ s.c_str() ]; if ( e.type() == EOO || holder->_removed.count( s ) ) { *objp = 0; JS_LeaveLocalRootScope( cx ); return JS_TRUE; } jsval val = c.toval( e ); verify( ! holder->_inResolve ); holder->_inResolve = true; verify( JS_SetProperty( cx , obj , s.c_str() , &val ) ); holder->_inResolve = false; if ( val != JSVAL_NULL && val != JSVAL_VOID && JSVAL_IS_OBJECT( val ) ) { // TODO: this is a hack to get around sub objects being modified // basically right now whenever a sub object is read we mark whole obj as possibly modified JSObject * oo = JSVAL_TO_OBJECT( val ); if ( JS_InstanceOf( cx , oo , &bson_class , 0 ) || JS_IsArrayObject( cx , oo ) ) { holder->_modified = true; } } *objp = obj; JS_LeaveLocalRootScope( cx ); } catch ( const AssertionException& e ) { JS_LeaveLocalRootScope( cx ); if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16284 ); } return JS_TRUE; } class SMScope; class SMEngine : public ScriptEngine { public: SMEngine() { #ifdef SM18 JS_SetCStringsAreUTF8(); #endif _runtime = JS_NewRuntime(64L * 1024L * 1024L); uassert( 10221 , "JS_NewRuntime failed" , _runtime ); if ( ! utf8Ok() ) { log() << "*** warning: spider monkey build without utf8 support. consider rebuilding with utf8 support" << endl; } } ~SMEngine() { JS_DestroyRuntime( _runtime ); JS_ShutDown(); } Scope * createScope(); void runTest(); virtual bool utf8Ok() const { return JS_CStringsAreUTF8(); } #ifdef XULRUNNER JSClass * _dateClass; JSClass * _regexClass; #endif private: JSRuntime * _runtime; friend class SMScope; }; SMEngine * globalSMEngine; void ScriptEngine::setup() { globalSMEngine = new SMEngine(); globalScriptEngine = globalSMEngine; } // ------ scope ------ JSBool no_gc(JSContext *cx, JSGCStatus status) { return JS_FALSE; } JSBool yes_gc(JSContext *cx, JSGCStatus status) { return JS_TRUE; } class SMScope : public Scope { public: SMScope() : _this( 0 ), _reportError( true ), _externalSetup( false ), _localConnect( false ) { smlock; _context = JS_NewContext( globalSMEngine->_runtime , 8192 ); _convertor = new Convertor( _context ); massert( 10431 , "JS_NewContext failed" , _context ); JS_SetOptions( _context , JSOPTION_VAROBJFIX); //JS_SetVersion( _context , JSVERSION_LATEST); TODO JS_SetErrorReporter( _context , errorReporter ); _global = JS_NewObject( _context , &global_class, NULL, NULL); massert( 10432 , "JS_NewObject failed for global" , _global ); JS_SetGlobalObject( _context , _global ); massert( 10433 , "js init failed" , JS_InitStandardClasses( _context , _global ) ); JS_SetOptions( _context , JS_GetOptions( _context ) | JSOPTION_VAROBJFIX ); JS_DefineFunctions( _context , _global , globalHelpers ); JS_DefineFunctions( _context , _convertor->getGlobalObject( "Object" ), objectHelpers ); //JS_SetGCCallback( _context , no_gc ); // this is useful for seeing if something is a gc problem _postCreateHacks(); } ~SMScope() { smlock; uassert( 10223 , "deleted SMScope twice?" , _convertor ); if ( _this ) { JS_RemoveRoot( _context , &_this ); _this = 0; } if ( _convertor ) { delete _convertor; _convertor = 0; } if ( _context ) { // This is expected to reclaim _global as well. JS_DestroyContext( _context ); _context = 0; } } void reset() { smlock; verify( _convertor ); } void init( const BSONObj * data ) { smlock; if ( ! data ) return; BSONObjIterator i( *data ); while ( i.more() ) { BSONElement e = i.next(); _convertor->setProperty( _global , e.fieldName() , _convertor->toval( e ) ); _initFieldNames.insert( e.fieldName() ); } } bool hasOutOfMemoryException() { string err = getError(); return err.find("out of memory") != string::npos; } void externalSetup() { smlock; uassert( 10224 , "already local connected" , ! _localConnect ); if ( _externalSetup ) return; initMongoJS( this , _context , _global , false ); _externalSetup = true; } 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; } initMongoJS( this , _context , _global , true ); exec( "_mongo = new Mongo();" ); exec( ((string)"db = _mongo.getDB( \"" + dbName + "\" ); ").c_str() ); _localConnect = true; _localDBName = dbName; } loadStored(); } // ----- getters ------ double getNumber( const char *field ) { smlock; jsval val; verify( JS_GetProperty( _context , _global , field , &val ) ); return _convertor->toNumber( val ); } string getString( const char *field ) { smlock; jsval val; verify( JS_GetProperty( _context , _global , field , &val ) ); JSString * s = JS_ValueToString( _context , val ); return _convertor->toString( s ); } bool getBoolean( const char *field ) { smlock; return _convertor->getBoolean( _global , field ); } BSONObj getObject( const char *field ) { smlock; return _convertor->toObject( _convertor->getProperty( _global , field ) ); } JSObject * getJSObject( const char * field ) { smlock; return _convertor->getJSObject( _global , field ); } int type( const char *field ) { smlock; jsval val; verify( JS_GetProperty( _context , _global , field , &val ) ); switch ( JS_TypeOfValue( _context , val ) ) { case JSTYPE_VOID: return Undefined; case JSTYPE_NULL: return jstNULL; case JSTYPE_OBJECT: { if ( val == JSVAL_NULL ) return jstNULL; JSObject * o = JSVAL_TO_OBJECT( val ); if ( JS_IsArrayObject( _context , o ) ) return Array; if ( isDate( _context , o ) ) return Date; return Object; } case JSTYPE_FUNCTION: return Code; case JSTYPE_STRING: return String; case JSTYPE_NUMBER: return NumberDouble; case JSTYPE_BOOLEAN: return Bool; default: uassert( 10227 , "unknown type" , 0 ); } return 0; } // ----- setters ------ void setElement( const char *field , const BSONElement& val ) { smlock; jsval v = _convertor->toval( val ); verify( JS_SetProperty( _context , _global , field , &v ) ); } void setNumber( const char *field , double val ) { smlock; jsval v = _convertor->toval( val ); verify( JS_SetProperty( _context , _global , field , &v ) ); } void setString( const char *field , const char * val ) { smlock; jsval v = _convertor->toval( val ); verify( JS_SetProperty( _context , _global , field , &v ) ); } void setObject( const char *field , const BSONObj& obj , bool readOnly ) { smlock; jsval v = _convertor->toval( &obj , readOnly ); JS_SetProperty( _context , _global , field , &v ); } void setBoolean( const char *field , bool val ) { smlock; jsval v = BOOLEAN_TO_JSVAL( val ); verify( JS_SetProperty( _context , _global , field , &v ) ); } void setThis( const BSONObj * obj ) { smlock; if ( _this ) { JS_RemoveRoot( _context , &_this ); _this = 0; } if ( obj ) { _this = _convertor->toJSObject( obj ); JS_AddNamedRoot( _context , &_this , "scope this" ); } } void setFunction( const char *field , const char * code ) { smlock; jsval v = OBJECT_TO_JSVAL(JS_GetFunctionObject(_convertor->compileFunction(code))); JS_SetProperty( _context , _global , field , &v ); } void rename( const char * from , const char * to ) { smlock; jsval v; verify( JS_GetProperty( _context , _global , from , &v ) ); verify( JS_SetProperty( _context , _global , to , &v ) ); v = JSVAL_VOID; verify( JS_SetProperty( _context , _global , from , &v ) ); } // ---- functions ----- ScriptingFunction _createFunction( const char * code ) { smlock; precall(); return (ScriptingFunction)_convertor->compileFunction( code ); } struct TimeoutSpec { boost::posix_time::ptime start; boost::posix_time::time_duration timeout; int count; }; // should not generate exceptions, as those can be caught in // javascript code; returning false without an exception exits // immediately static JSBool _interrupt( JSContext *cx ) { TimeoutSpec &spec = *(TimeoutSpec *)( JS_GetContextPrivate( cx ) ); if ( ++spec.count % 1000 != 0 ) return JS_TRUE; const char * interrupt = ScriptEngine::checkInterrupt(); if ( interrupt && interrupt[ 0 ] ) { return JS_FALSE; } if ( spec.timeout.ticks() == 0 ) { return JS_TRUE; } boost::posix_time::time_duration elapsed = ( boost::posix_time::microsec_clock::local_time() - spec.start ); if ( elapsed < spec.timeout ) { return JS_TRUE; } return JS_FALSE; } static JSBool interrupt( JSContext *cx, JSScript *script ) { return _interrupt( cx ); } void installInterrupt( int timeoutMs ) { if ( timeoutMs != 0 || ScriptEngine::haveCheckInterruptCallback() ) { TimeoutSpec *spec = new TimeoutSpec; spec->timeout = boost::posix_time::millisec( timeoutMs ); spec->start = boost::posix_time::microsec_clock::local_time(); spec->count = 0; JS_SetContextPrivate( _context, (void*)spec ); #if defined(SM181) && !defined(XULRUNNER190) JS_SetOperationCallback( _context, _interrupt ); #else JS_SetBranchCallback( _context, interrupt ); #endif } } void uninstallInterrupt( int timeoutMs ) { if ( timeoutMs != 0 || ScriptEngine::haveCheckInterruptCallback() ) { #if defined(SM181) && !defined(XULRUNNER190) JS_SetOperationCallback( _context , 0 ); #else JS_SetBranchCallback( _context, 0 ); #endif delete (TimeoutSpec *)JS_GetContextPrivate( _context ); JS_SetContextPrivate( _context, 0 ); } } void precall() { _error = ""; currentScope.reset( this ); } bool isReportingErrors() const { return _reportError; } bool exec( const StringData& code, const string& name = "(anon)", bool printResult = false, bool reportError = true, bool assertOnError = true, int timeoutMs = 0 ) { smlock; precall(); jsval ret = JSVAL_VOID; installInterrupt( timeoutMs ); _reportError = reportError; JSBool worked = JS_EvaluateScript( _context, _global, code.data(), code.size(), name.c_str(), 1, &ret ); _reportError = true; uninstallInterrupt( timeoutMs ); if ( ! worked && _error.size() == 0 ) { jsval v; if ( JS_GetPendingException( _context , &v ) ) { _error = _convertor->toString( v ); if ( reportError ) cout << _error << endl; } } uassert( 10228, mongoutils::str::stream() << name + " exec failed: " << _error, worked || ! assertOnError ); if ( worked ) _convertor->setProperty( _global , "__lastres__" , ret ); if ( worked && printResult && ! JSVAL_IS_VOID( ret ) ) cout << _convertor->toString( ret ) << endl; return worked; } int invoke( JSFunction* func, const BSONObj* args, const BSONObj* recv, int timeoutMs, bool ignoreReturn, bool readOnlyArgs, bool readOnlyRecv ) { smlock; precall(); try { verify( JS_EnterLocalRootScope( _context ) ); } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( _context ) ) { JS_ReportError( _context, e.what() ); } return 0; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16285 ); } int nargs = args ? args->nFields() : 0; scoped_array smargsPtr( new jsval[nargs] ); try { if ( nargs ) { BSONObjIterator it( *args ); for ( int i=0; itoval( it.next() ); } } if ( !args ) { _convertor->setProperty( _global , "args" , JSVAL_NULL ); } else { setObject( "args" , *args , true ); // this is for backwards compatability } } catch ( const AssertionException& e ) { JS_LeaveLocalRootScope( _context ); if ( ! JS_IsExceptionPending( _context ) ) { JS_ReportError( _context, e.what() ); } return 0; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16286 ); } JS_LeaveLocalRootScope( _context ); installInterrupt( timeoutMs ); jsval rval; setThis(recv); JSBool ret = JS_CallFunction( _context, _this ? _this : _global, func, nargs, smargsPtr.get(), &rval ); setThis(0); uninstallInterrupt( timeoutMs ); if ( !ret ) { return -3; } if ( ! ignoreReturn ) { verify( JS_SetProperty( _context , _global , "return" , &rval ) ); } return 0; } int invoke( ScriptingFunction funcAddr, const BSONObj* args, const BSONObj* recv, int timeoutMs = 0, bool ignoreReturn = 0, bool readOnlyArgs = false, bool readOnlyRecv = false ) { return invoke( reinterpret_cast( funcAddr ), args, recv, timeoutMs, ignoreReturn, readOnlyArgs, readOnlyRecv ); } void gotError( string s ) { _error = s; } string getError() { return _error; } void injectNative( const char *field, NativeFunction func, void* data ) { smlock; string name = field; jsval v; v = _convertor->toval( static_cast( reinterpret_cast(func) ) ); _convertor->setProperty( _global, (name + "_").c_str(), v ); stringstream code; if (data) { v = _convertor->toval( static_cast( reinterpret_cast(data) ) ); _convertor->setProperty( _global, (name + "_data_").c_str(), v ); code << field << "_" << " = { x : " << field << "_ , y: " << field << "_data_ }; "; } else { code << field << "_" << " = { x : " << field << "_ }; "; } code << field << " = function(){ return nativeHelper.apply( " << field << "_ , arguments ); }"; exec( code.str() ); } virtual void gc() { smlock; JS_GC( _context ); } JSContext *SavedContext() const { return _context; } private: void _postCreateHacks() { #ifdef XULRUNNER exec( "__x__ = new Date(1);" ); globalSMEngine->_dateClass = _convertor->getClass( _global , "__x__" ); exec( "__x__ = /abc/i" ); globalSMEngine->_regexClass = _convertor->getClass( _global , "__x__" ); #endif } JSContext * _context; Convertor * _convertor; JSObject * _global; JSObject * _this; string _error; bool _reportError; bool _externalSetup; bool _localConnect; set _initFieldNames; }; /* 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; if( !isShell ) ss << "JS Error: "; ss << message; if ( report && report->filename ) { ss << " " << report->filename << ":" << report->lineno; } if ( !currentScope.get() || currentScope->isReportingErrors() ) { tlog() << ss.str() << endl; } if ( currentScope.get() ) { currentScope->gotError( ss.str() ); } } JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { try { Convertor c(cx); Scope * s = currentScope.get(); for ( uintN i=0; iexecFile( filename, false, true, false ) ) { JS_ReportError( cx, (string("error loading js file: ") + filename).c_str() ); return JS_FALSE; } } } catch ( const AssertionException& e ) { if ( ! JS_IsExceptionPending( cx ) ) { JS_ReportError( cx, e.what() ); } return JS_FALSE; } catch ( const std::exception& e ) { log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl; fassertFailed( 16287 ); } return JS_TRUE; } void SMEngine::runTest() { SMScope s; s.localConnect( "foo" ); s.exec( "verify( db.getMongo() )" ); s.exec( "verify( db.bar , 'collection getting does not work' ); " ); s.exec( "assert.eq( db._name , 'foo' );" ); s.exec( "verify( _mongo == db.getMongo() ); " ); s.exec( "verify( _mongo == db._mongo ); " ); s.exec( "verify( typeof DB.bar == 'undefined' ); " ); s.exec( "verify( typeof DB.prototype.bar == 'undefined' , 'resolution is happening on prototype, not object' ); " ); s.exec( "verify( db.bar ); " ); s.exec( "verify( typeof db.addUser == 'function' )" ); s.exec( "verify( db.addUser == DB.prototype.addUser )" ); s.exec( "assert.eq( 'foo.bar' , db.bar._fullName ); " ); s.exec( "db.bar.verify();" ); s.exec( "db.bar.silly.verify();" ); s.exec( "assert.eq( 'foo.bar.silly' , db.bar.silly._fullName )" ); s.exec( "assert.eq( 'function' , typeof _mongo.find , 'mongo.find is not a function' )" ); verify( (string)"abc" == trim( "abc" ) ); verify( (string)"abc" == trim( " abc" ) ); verify( (string)"abc" == trim( "abc " ) ); verify( (string)"abc" == trim( " abc " ) ); } Scope * SMEngine::createScope() { return new SMScope(); } } #include "sm_db.cpp"