diff --git a/build/premake/extern_libs4.lua b/build/premake/extern_libs4.lua index 7a7fe81203..ec57c181cf 100644 --- a/build/premake/extern_libs4.lua +++ b/build/premake/extern_libs4.lua @@ -562,38 +562,42 @@ extern_lib_defs = { }, spidermonkey = { compile_settings = function() - if _OPTIONS["with-system-mozjs185"] then + if _OPTIONS["with-system-mozjs24"] then if not _OPTIONS["android"] then - pkgconfig_cflags("mozjs185") + pkgconfig_cflags("mozjs-24") end - defines { "WITH_SYSTEM_MOZJS185" } + defines { "WITH_SYSTEM_MOZJS24" } else if os.is("windows") then include_dir = "include-win32" - elseif os.is("macosx") then - include_dir = "include" else include_dir = "include-unix" end configuration "Debug" - includedirs { libraries_source_dir.."spidermonkey/"..include_dir } + includedirs { libraries_source_dir.."spidermonkey/"..include_dir.."-debug" } + defines { "DEBUG" } configuration "Release" - includedirs { libraries_source_dir.."spidermonkey/"..include_dir } + includedirs { libraries_source_dir.."spidermonkey/"..include_dir.."-release" } configuration { } end end, link_settings = function() - if _OPTIONS["with-system-mozjs185"] then + if _OPTIONS["with-system-mozjs24"] then if _OPTIONS["android"] then - links { "mozjs185-1.0" } + links { "mozjs-24" } else - pkgconfig_libs("mozjs185") + pkgconfig_libs("nspr") + pkgconfig_libs("mozjs-24") end else + if os.is("macosx") then + add_default_lib_paths("nspr") + links { "nspr4", "plc4", "plds4" } + end configuration "Debug" - links { "mozjs185-ps-debug" } + links { "mozjs24-ps-debug" } configuration "Release" - links { "mozjs185-ps-release" } + links { "mozjs24-ps-release" } configuration { } add_source_lib_paths("spidermonkey") end diff --git a/build/premake/premake4.lua b/build/premake/premake4.lua index 26ea0f902a..69deeaeed3 100644 --- a/build/premake/premake4.lua +++ b/build/premake/premake4.lua @@ -15,7 +15,7 @@ newoption { trigger = "without-miniupnpc", description = "Disable use of miniupn newoption { trigger = "with-system-nvtt", description = "Search standard paths for nvidia-texture-tools library, instead of using bundled copy" } newoption { trigger = "with-system-enet", description = "Search standard paths for libenet, instead of using bundled copy" } newoption { trigger = "with-system-miniupnpc", description = "Search standard paths for libminiupnpc, instead of using bundled copy" } -newoption { trigger = "with-system-mozjs185", description = "Search standard paths for libmozjs185, instead of using bundled copy" } +newoption { trigger = "with-system-mozjs24", description = "Search standard paths for libmozjs24, instead of using bundled copy" } newoption { trigger = "with-c++11", description = "Enable C++11 on GCC" } newoption { trigger = "sysroot", description = "Set compiler system root path, used for building against a non-system SDK. For example /usr/local becomes SYSROOT/user/local" } newoption { trigger = "macosx-version-min", description = "Set minimum required version of the OS X API, the build will possibly fail if an older SDK is used, while newer API functions will be weakly linked (i.e. resolved at runtime)" } diff --git a/build/workspaces/update-workspaces.sh b/build/workspaces/update-workspaces.sh index 3e8669fbe9..8f36dee6e2 100644 --- a/build/workspaces/update-workspaces.sh +++ b/build/workspaces/update-workspaces.sh @@ -37,7 +37,7 @@ without_nvtt=false with_system_nvtt=false with_system_enet=false with_system_miniupnpc=false -with_system_mozjs185=false +with_system_mozjs24=false enable_atlas=true for i in "$@" @@ -47,7 +47,7 @@ do --with-system-nvtt ) with_system_nvtt=true; premake_args="${premake_args} --with-system-nvtt" ;; --with-system-enet ) with_system_enet=true; premake_args="${premake_args} --with-system-enet" ;; --with-system-miniupnpc ) with_system_miniupnpc=true; premake_args="${premake_args} --with-system-miniupnpc" ;; - --with-system-mozjs185 ) with_system_mozjs185=true; premake_args="${premake_args} --with-system-mozjs185" ;; + --with-system-mozjs24 ) with_system_mozjs24=true; premake_args="${premake_args} --with-system-mozjs24" ;; --enable-atlas ) enable_atlas=true ;; --disable-atlas ) enable_atlas=false ;; -j* ) JOBS=$i ;; @@ -81,7 +81,7 @@ if [ "`uname -s`" != "Darwin" ]; then # Build/update bundled external libraries (cd ../../libraries/source/fcollada/src && ${MAKE} ${JOBS}) || die "FCollada build failed" echo - if [ "$with_system_mozjs185" = "false" ]; then + if [ "$with_system_mozjs24" = "false" ]; then (cd ../../libraries/source/spidermonkey && MAKE=${MAKE} JOBS=${JOBS} ./build.sh) || die "SpiderMonkey build failed" fi echo diff --git a/libraries/osx/build-osx-libs.sh b/libraries/osx/build-osx-libs.sh index dcc4ca32a6..2104b938ed 100755 --- a/libraries/osx/build-osx-libs.sh +++ b/libraries/osx/build-osx-libs.sh @@ -38,9 +38,11 @@ OGG_VERSION="libogg-1.3.0" VORBIS_VERSION="libvorbis-1.3.3" # gloox is necessary for multiplayer lobby GLOOX_VERSION="gloox-1.0.9" +# NSPR is necessary for threadsafe Spidermonkey +NSPR_VERSION="4.10.3" # -------------------------------------------------------------- # Bundled with the game: -# * SpiderMonkey 1.8.5 +# * SpiderMonkey 24 # * ENet 1.3.3 # * NVTT # * FCollada @@ -520,32 +522,69 @@ else fi popd > /dev/null +# -------------------------------------------------------------- +echo -e "Building NSPR..." + +LIB_VERSION="${NSPR_VERSION}" +LIB_ARCHIVE="nspr-$LIB_VERSION.tar.gz" +LIB_DIRECTORY="nspr-$LIB_VERSION" +LIB_URL="https://ftp.mozilla.org/pub/mozilla.org/nspr/releases/v$LIB_VERSION/src/" + +mkdir -p nspr +pushd nspr > /dev/null + +NSPR_DIR="$(pwd)" + +if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] +then + rm -f .already-built + download_lib $LIB_URL $LIB_ARCHIVE + + rm -rf $LIB_DIRECTORY bin include lib share + tar -xf $LIB_ARCHIVE + pushd $LIB_DIRECTORY/nspr + + (CFLAGS="$CFLAGS" CPPFLAGS="$CPPFLAGS" LDFLAGS="$LDFLAGS" ./configure --prefix="$NSPR_DIR" && make ${JOBS} && make install) || die "NSPR build failed" + popd + # TODO: how can we not build the dylibs? + rm -f lib/*.dylib + touch .already-built +else + already_built +fi +popd > /dev/null + # -------------------------------------------------------------------- # The following libraries are shared on different OSes and may # be customzied, so we build and install them from bundled sources # -------------------------------------------------------------------- echo -e "Building Spidermonkey..." -LIB_VERSION="js185-1.0.0" -LIB_ARCHIVE="$LIB_VERSION.tar.gz" -LIB_DIRECTORY="js-1.8.5" +LIB_VERSION="mozjs-24.2.0" +LIB_ARCHIVE="$LIB_VERSION.tar.bz2" +LIB_DIRECTORY="mozjs24" pushd ../source/spidermonkey/ > /dev/null if [[ "$force_rebuild" = "true" ]] || [[ ! -e .already-built ]] || [[ .already-built -ot $LIB_DIRECTORY ]] then INSTALL_DIR="$(pwd)" + INCLUDE_DIR_DEBUG=$INSTALL_DIR/include-unix-debug + INCLUDE_DIR_RELEASE=$INSTALL_DIR/include-unix-release rm -f .already-built rm -f lib/*.a - rm -rf $LIB_DIRECTORY + rm -rf $LIB_DIRECTORY $INCLUDE_DIR_DEBUG $INCLUDE_DIR_RELEASE tar -xf $LIB_ARCHIVE + # rename the extracted directory to something shorter + mv $LIB_VERSION $LIB_DIRECTORY pushd $LIB_DIRECTORY/js/src # We want separate debug/release versions of the library, so change their install name in the Makefile - sed -i.bak -e 's/\(STATIC_LIBRARY_NAME),mozjs185-\)\(\$(SRCREL_ABI_VERSION)\)\{0,1\}/\1ps-debug/' Makefile.in + perl -i.bak -pe 's/(^STATIC_LIBRARY_NAME\s+=).*/$1mozjs24-ps-debug/' Makefile.in + perl -i.bak -pe 's/js_static/mozjs24-ps-debug/g' shell/Makefile.in - CONF_OPTS="--prefix=${INSTALL_DIR} --disable-tests --disable-shared-js" + CONF_OPTS="--target=$ARCH-apple-darwin --prefix=${INSTALL_DIR} --with-system-nspr --with-nspr-prefix=${NSPR_DIR} --with-system-zlib=${ZLIB_DIR} --enable-threadsafe --disable-tests --disable-shared-js" # --enable-trace-logging" # Uncomment this line for 32-bit 10.5 cross compile: #CONF_OPTS="$CONF_OPTS --target=i386-apple-darwin9.0.0" if [[ $MIN_OSX_VERSION && ${MIN_OSX_VERSION-_} ]]; then @@ -555,19 +594,32 @@ then CONF_OPTS="$CONF_OPTS --with-macosx-sdk=$SYSROOT" fi + # apply patch to fix clang build on Mavericks (see https://bugzilla.mozilla.org/show_bug.cgi?id=917526) + patch -p0 -d ../../ -i ../../../osx/patches/sm-mavericks-clang-fix.diff || die "Spidermonkey build failed" + mkdir -p build-debug pushd build-debug - (CC="$CC -arch $ARCH" CXX="$CXX -arch $ARCH" AR=ar CROSS_COMPILE=1 ../configure $CONF_OPTS --enable-debug --disable-optimize && make ${JOBS} && make install) || die "Spidermonkey build failed" + (CC="clang" CXX="clang++" AR=ar CROSS_COMPILE=1 ../configure $CONF_OPTS --enable-debug --disable-optimize --enable-js-diagnostics --enable-gczeal && make ${JOBS}) || die "Spidermonkey build failed" + # js-config.h is different for debug and release builds, so we need different include directories for both + mkdir -p $INCLUDE_DIR_DEBUG + cp -R -L dist/include/* $INCLUDE_DIR_DEBUG/ + cp *.a $INSTALL_DIR/lib popd mv Makefile.in.bak Makefile.in - - sed -i.bak -e 's/\(STATIC_LIBRARY_NAME),mozjs185-\)\(\$(SRCREL_ABI_VERSION)\)\{0,1\}/\1ps-release/' Makefile.in + mv shell/Makefile.in.bak shell/Makefile.in + perl -i.bak -pe 's/(^STATIC_LIBRARY_NAME\s+=).*/$1mozjs24-ps-release/' Makefile.in + perl -i.bak -pe 's/js_static/mozjs24-ps-release/g' shell/Makefile.in mkdir -p build-release pushd build-release - (CC="$CC -arch $ARCH" CXX="$CXX -arch $ARCH" AR=ar CROSS_COMPILE=1 ../configure $CONF_OPTS && make ${JOBS} && make install) || die "Spidermonkey build failed" + (CC="clang" CXX="clang++" AR=ar CROSS_COMPILE=1 ../configure $CONF_OPTS --enable-optimize && make ${JOBS}) || die "Spidermonkey build failed" + # js-config.h is different for debug and release builds, so we need different include directories for both + mkdir -p $INCLUDE_DIR_RELEASE + cp -R -L dist/include/* $INCLUDE_DIR_RELEASE/ + cp *.a $INSTALL_DIR/lib popd mv Makefile.in.bak Makefile.in + mv shell/Makefile.in.bak shell/Makefile.in popd touch .already-built diff --git a/libraries/osx/patches/sm-mavericks-clang-fix.diff b/libraries/osx/patches/sm-mavericks-clang-fix.diff new file mode 100644 index 0000000000..44b0d68447 --- /dev/null +++ b/libraries/osx/patches/sm-mavericks-clang-fix.diff @@ -0,0 +1,16 @@ +--- js/src/configure 2013-12-11 14:23:20.000000000 -0800 ++++ js/src/configure 2014-03-10 19:12:35.000000000 -0700 +@@ -6229,7 +6229,12 @@ + if test "$GNU_CC"; then + # Per bug 719659 comment 2, some of the headers on ancient build machines + # may require gnu89 inline semantics. But otherwise, we use C99. +- CFLAGS="$CFLAGS -std=gnu99 -fgnu89-inline" ++ # But on OS X we just use C99 plus GNU extensions, in order to fix ++ # bug 917526. ++ CFLAGS="$CFLAGS -std=gnu99" ++ if test "${OS_ARCH}" != Darwin; then ++ CFLAGS="$CFLAGS -fgnu89-inline" ++ fi + MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$(notdir $@) -o $@' + MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$(notdir $@) -o $@' + DSO_LDOPTS='-shared' \ No newline at end of file diff --git a/source/graphics/GameView.h b/source/graphics/GameView.h index 64a0e38d87..c79d542845 100644 --- a/source/graphics/GameView.h +++ b/source/graphics/GameView.h @@ -30,7 +30,7 @@ class CCinemaManager; class CVector3D; struct SViewPort; -struct JSObject; +class JSObject; class CGameViewImpl; diff --git a/source/graphics/MapGenerator.cpp b/source/graphics/MapGenerator.cpp index 50565404a4..a2c926178b 100644 --- a/source/graphics/MapGenerator.cpp +++ b/source/graphics/MapGenerator.cpp @@ -37,13 +37,6 @@ CMapGeneratorWorker::~CMapGeneratorWorker() { // Wait for thread to end pthread_join(m_WorkerThread, NULL); - - // The StructuredClone destructor references a JSContext created by our - // ScriptInterface, so explicitly clean it up before ScriptInterface - m_MapData.reset(); - - // Cleanup ScriptInterface - delete m_ScriptInterface; } void CMapGeneratorWorker::Initialize(const VfsPath& scriptFile, const std::string& settings) @@ -99,40 +92,52 @@ bool CMapGeneratorWorker::Run() m_ScriptInterface->RegisterFunction("MaybeGC"); m_ScriptInterface->RegisterFunction, CMapGeneratorWorker::GetCivData>("GetCivData"); - // Parse settings - CScriptValRooted settingsVal = m_ScriptInterface->ParseJSON(m_Settings); - if (settingsVal.undefined()) + // TODO: This code is a bit ugly because we have to ensure that CScriptValRooted gets destroyed before the ScriptInterface. + // In the future we should work more with the standard JSAPI types for rooting on the stack, which should avoid such problems. + bool ret = true; { - LOGERROR(L"CMapGeneratorWorker::Run: Failed to parse settings"); - return false; + // Parse settings + CScriptValRooted settingsVal = m_ScriptInterface->ParseJSON(m_Settings); + if (settingsVal.undefined()) + { + LOGERROR(L"CMapGeneratorWorker::Run: Failed to parse settings"); + ret = false; + } + else + { + // Init RNG seed + u32 seed; + if (!m_ScriptInterface->GetProperty(settingsVal.get(), "Seed", seed)) + { // No seed specified + LOGWARNING(L"CMapGeneratorWorker::Run: No seed value specified - using 0"); + seed = 0; + } + + m_MapGenRNG.seed(seed); + + // Copy settings to global variable + if (!m_ScriptInterface->SetProperty(m_ScriptInterface->GetGlobalObject(), "g_MapSettings", settingsVal)) + { + LOGERROR(L"CMapGeneratorWorker::Run: Failed to define g_MapSettings"); + ret = false; + } + else + { + // Load RMS + LOGMESSAGE(L"Loading RMS '%ls'", m_ScriptPath.string().c_str()); + if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath)) + { + LOGERROR(L"CMapGeneratorWorker::Run: Failed to load RMS '%ls'", m_ScriptPath.string().c_str()); + ret = false; + } + } + } } + + // We must destroy the ScriptInterface in the same thread because the JSAPI requires that! + SAFE_DELETE(m_ScriptInterface); - // Init RNG seed - uint32_t seed; - if (!m_ScriptInterface->GetProperty(settingsVal.get(), "Seed", seed)) - { // No seed specified - LOGWARNING(L"CMapGeneratorWorker::Run: No seed value specified - using 0"); - seed = 0; - } - - m_MapGenRNG.seed((int32_t)seed); - - // Copy settings to global variable - if (!m_ScriptInterface->SetProperty(m_ScriptInterface->GetGlobalObject(), "g_MapSettings", settingsVal)) - { - LOGERROR(L"CMapGeneratorWorker::Run: Failed to define g_MapSettings"); - return false; - } - - // Load RMS - LOGMESSAGE(L"Loading RMS '%ls'", m_ScriptPath.string().c_str()); - if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath)) - { - LOGERROR(L"CMapGeneratorWorker::Run: Failed to load RMS '%ls'", m_ScriptPath.string().c_str()); - return false; - } - - return true; + return ret; } int CMapGeneratorWorker::GetProgress() diff --git a/source/gui/CGUI.h b/source/gui/CGUI.h index 3320bed81e..448c5f1906 100644 --- a/source/gui/CGUI.h +++ b/source/gui/CGUI.h @@ -77,7 +77,7 @@ struct SGUIStyle std::map m_SettingsDefaults; }; -struct JSObject; // The GUI stores a JSObject*, so needs to know that JSObject exists +class JSObject; // The GUI stores a JSObject*, so needs to know that JSObject exists class IGUIObject; class CGUISpriteInstance; struct SGUIText; diff --git a/source/gui/GUIManager.cpp b/source/gui/GUIManager.cpp index fc57c3461d..eb6011d2ee 100644 --- a/source/gui/GUIManager.cpp +++ b/source/gui/GUIManager.cpp @@ -71,24 +71,7 @@ void CGUIManager::SwitchPage(const CStrW& pageName, ScriptInterface* srcScriptIn shared_ptr initDataClone; if (initData.get() != JSVAL_VOID) { - ENSURE(srcScriptInterface); - // TODO: Fix this horribly ugly hack as soon as we have integrated the new SpiderMonkey library version. - // In SpiderMonkey 1.8.5, StructuredClone data needs to be freed with JS_Free. - // JS_Free takes a JSContext as argument which must be the one the clone was created wtih. - // This means we must ensure to call the destructor of all structuredclones before the context is destroyed. - // To achieve this, we write all initData structured clones with the GUIManager's JSContext. - // That's why we have to clone the value twice here. - // Calling WriteStructuredClone with a context in another compartment than the value works, but I don't think that's - // supposed to work and will probably break under some conditions. - // Newer SpiderMonkey versions introduce JS_ClearStructuredClone instead of JS_Free which does not require a context. - if (srcScriptInterface != m_ScriptInterface.get()) - { - CScriptVal cloneSaveInitData; - cloneSaveInitData = m_ScriptInterface->CloneValueFromOtherContext(*srcScriptInterface, initData.get()); - initDataClone = m_ScriptInterface->WriteStructuredClone(cloneSaveInitData.get()); - } - else - initDataClone = m_ScriptInterface->WriteStructuredClone(initData.get()); + initDataClone = srcScriptInterface->WriteStructuredClone(initData.get()); } m_PageStack.clear(); PushPage(pageName, initDataClone); diff --git a/source/gui/GUIManager.h b/source/gui/GUIManager.h index 24906f8d65..81aa9c7dfc 100644 --- a/source/gui/GUIManager.h +++ b/source/gui/GUIManager.h @@ -30,7 +30,7 @@ #include class CGUI; -struct JSObject; +class JSObject; class IGUIObject; struct CColor; struct SGUIIcon; diff --git a/source/gui/IGUIObject.cpp b/source/gui/IGUIObject.cpp index 9c359301ee..c280ff86c1 100644 --- a/source/gui/IGUIObject.cpp +++ b/source/gui/IGUIObject.cpp @@ -422,6 +422,7 @@ void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGU throw PSERROR_GUI_OperationNeedsGUIObject(); JSContext* cx = pGUI->GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); const int paramCount = 1; const char* paramNames[paramCount] = { "mouse" }; @@ -445,7 +446,7 @@ void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGU void IGUIObject::SetScriptHandler(const CStr& Action, JSObject* Function) { - m_ScriptHandlers[Action] = CScriptValRooted(m_pGUI->GetScriptInterface()->GetContext(), OBJECT_TO_JSVAL(Function)); + m_ScriptHandlers[Action] = CScriptValRooted(m_pGUI->GetScriptInterface()->GetContext(), JS::ObjectValue(*Function)); } InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName) @@ -469,6 +470,7 @@ void IGUIObject::ScriptEvent(const CStr& Action) return; JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); // Set up the 'mouse' parameter CScriptVal mouse; @@ -480,7 +482,7 @@ void IGUIObject::ScriptEvent(const CStr& Action) jsval paramData[] = { mouse.get() }; jsval result; - JSBool ok = JS_CallFunctionValue(cx, GetJSObject(), (*it).second.get(), ARRAY_SIZE(paramData), paramData, &result); + bool ok = JS_CallFunctionValue(cx, GetJSObject(), (*it).second.get(), ARRAY_SIZE(paramData), paramData, &result); if (!ok) { // We have no way to propagate the script exception, so just ignore it @@ -490,17 +492,19 @@ void IGUIObject::ScriptEvent(const CStr& Action) void IGUIObject::ScriptEvent(const CStr& Action, const CScriptValRooted& Argument) { - JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); std::map::iterator it = m_ScriptHandlers.find(Action); if (it == m_ScriptHandlers.end()) return; + JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + JSObject* object = GetJSObject(); jsval arg = Argument.get(); jsval result; - JSBool ok = JS_CallFunctionValue(cx, object, (*it).second.get(), 1, &arg, &result); + bool ok = JS_CallFunctionValue(cx, object, (*it).second.get(), 1, &arg, &result); if (!ok) { JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str()); @@ -510,6 +514,7 @@ void IGUIObject::ScriptEvent(const CStr& Action, const CScriptValRooted& Argumen JSObject* IGUIObject::GetJSObject() { JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); // Cache the object when somebody first asks for it, because otherwise // we end up doing far too much object allocation. TODO: Would be nice to // not have these objects hang around forever using up memory, though. @@ -517,7 +522,7 @@ JSObject* IGUIObject::GetJSObject() { JSObject* obj = JS_NewObject(cx, &JSI_IGUIObject::JSI_class, NULL, NULL); m_JSObject = CScriptValRooted(cx, OBJECT_TO_JSVAL(obj)); - JS_SetPrivate(cx, JSVAL_TO_OBJECT(m_JSObject.get()), this); + JS_SetPrivate(JSVAL_TO_OBJECT(m_JSObject.get()), this); } return JSVAL_TO_OBJECT(m_JSObject.get());; } diff --git a/source/gui/IGUIObject.h b/source/gui/IGUIObject.h index 48a6df1461..b03d09aa83 100644 --- a/source/gui/IGUIObject.h +++ b/source/gui/IGUIObject.h @@ -69,7 +69,7 @@ class CScriptValRooted; // Types //-------------------------------------------------------- -struct JSObject; +class JSObject; //-------------------------------------------------------- // Error declarations @@ -142,9 +142,9 @@ class IGUIObject friend class GUITooltip; // Allow getProperty to access things like GetParent() - friend JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp); - friend JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool strict, jsval* vp); - friend JSBool JSI_IGUIObject::getComputedSize(JSContext* cx, uintN argc, jsval* vp); + friend JSBool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); + friend JSBool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JSBool UNUSED(strict), JS::MutableHandleValue vp); + friend JSBool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, jsval* vp); public: IGUIObject(); diff --git a/source/gui/scripting/GuiScriptConversions.cpp b/source/gui/scripting/GuiScriptConversions.cpp index 93c26007dc..a9814d7b44 100644 --- a/source/gui/scripting/GuiScriptConversions.cpp +++ b/source/gui/scripting/GuiScriptConversions.cpp @@ -23,14 +23,13 @@ #include "lib/external_libraries/libsdl.h" #include "ps/Hotkey.h" -#include "js/jsapi.h" - -#define SET(obj, name, value) STMT(jsval v_ = ToJSVal(cx, (value)); JS_SetProperty(cx, (obj), (name), &v_)) +#define SET(obj, name, value) STMT(JS::RootedValue v_(cx); ToJSVal(cx, v_.get(), (value)); JS_SetProperty(cx, (obj), (name), v_.address())) // ignore JS_SetProperty return value, because errors should be impossible // and we can't do anything useful in the case of errors anyway -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, SDL_Event_ const& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, SDL_Event_ const& val) { + JSAutoRequest rq(cx); const char* typeName; switch (val.ev.type) @@ -55,7 +54,10 @@ template<> jsval ScriptInterface::ToJSVal(JSContext* cx, SDL_Event_ JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); if (! obj) - return JSVAL_VOID; + { + ret = JSVAL_VOID; + return; + } SET(obj, "type", typeName); @@ -77,9 +79,12 @@ template<> jsval ScriptInterface::ToJSVal(JSContext* cx, SDL_Event_ JSObject* keysym = JS_NewObject(cx, NULL, NULL, NULL); if (! keysym) - return JSVAL_VOID; - jsval keysymVal = OBJECT_TO_JSVAL(keysym); - JS_SetProperty(cx, obj, "keysym", &keysymVal); + { + ret = JSVAL_VOID; + return; + } + JS::RootedValue keysymVal(cx, JS::ObjectValue(*keysym)); + JS_SetProperty(cx, obj, "keysym", keysymVal.address()); // SET(keysym, "scancode", (int)val.ev.key.keysym.scancode); // (not in wsdl.h) SET(keysym, "sym", (int)val.ev.key.keysym.sym); @@ -126,15 +131,13 @@ template<> jsval ScriptInterface::ToJSVal(JSContext* cx, SDL_Event_ } } - jsval rval = OBJECT_TO_JSVAL(obj); - - return rval; + ret = JS::ObjectValue(*obj); } -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), IGUIObject* const& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, IGUIObject* const& val) { if (val == NULL) - return JSVAL_NULL; - - return OBJECT_TO_JSVAL(val->GetJSObject()); + ret = JSVAL_NULL; + else + ret = JS::ObjectValue(*val->GetJSObject()); } diff --git a/source/gui/scripting/JSInterface_GUITypes.cpp b/source/gui/scripting/JSInterface_GUITypes.cpp index 7bf7af8043..860ae9532f 100644 --- a/source/gui/scripting/JSInterface_GUITypes.cpp +++ b/source/gui/scripting/JSInterface_GUITypes.cpp @@ -23,10 +23,10 @@ /**** GUISize ****/ JSClass JSI_GUISize::JSI_class = { "GUISize", 0, - JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, - JS_ConvertStub, JS_FinalizeStub, + JS_ConvertStub, NULL, NULL, NULL, NULL, JSI_GUISize::construct }; @@ -45,48 +45,60 @@ JSPropertySpec JSI_GUISize::JSI_props[] = JSFunctionSpec JSI_GUISize::JSI_methods[] = { - { "toString", JSI_GUISize::toString, 0, 0 }, - { 0 } + JS_FS("toString", JSI_GUISize::toString, 0, 0), + JS_FS_END }; -JSBool JSI_GUISize::construct(JSContext* cx, uintN argc, jsval* vp) +JSBool JSI_GUISize::construct(JSContext* cx, uint argc, jsval* vp) { JSObject* obj = JS_NewObject(cx, &JSI_GUISize::JSI_class, NULL, NULL); if (argc == 8) { - JS_SetProperty(cx, obj, "left", &JS_ARGV(cx, vp)[0]); - JS_SetProperty(cx, obj, "top", &JS_ARGV(cx, vp)[1]); - JS_SetProperty(cx, obj, "right", &JS_ARGV(cx, vp)[2]); - JS_SetProperty(cx, obj, "bottom", &JS_ARGV(cx, vp)[3]); - JS_SetProperty(cx, obj, "rleft", &JS_ARGV(cx, vp)[4]); - JS_SetProperty(cx, obj, "rtop", &JS_ARGV(cx, vp)[5]); - JS_SetProperty(cx, obj, "rright", &JS_ARGV(cx, vp)[6]); - JS_SetProperty(cx, obj, "rbottom", &JS_ARGV(cx, vp)[7]); + JS::RootedValue v0(cx, JS_ARGV(cx, vp)[0]); + JS::RootedValue v1(cx, JS_ARGV(cx, vp)[1]); + JS::RootedValue v2(cx, JS_ARGV(cx, vp)[2]); + JS::RootedValue v3(cx, JS_ARGV(cx, vp)[3]); + JS::RootedValue v4(cx, JS_ARGV(cx, vp)[4]); + JS::RootedValue v5(cx, JS_ARGV(cx, vp)[5]); + JS::RootedValue v6(cx, JS_ARGV(cx, vp)[6]); + JS::RootedValue v7(cx, JS_ARGV(cx, vp)[7]); + JS_SetProperty(cx, obj, "left", v0.address()); + JS_SetProperty(cx, obj, "top", v1.address()); + JS_SetProperty(cx, obj, "right", v2.address()); + JS_SetProperty(cx, obj, "bottom", v3.address()); + JS_SetProperty(cx, obj, "rleft", v4.address()); + JS_SetProperty(cx, obj, "rtop", v5.address()); + JS_SetProperty(cx, obj, "rright", v6.address()); + JS_SetProperty(cx, obj, "rbottom", v7.address()); } else if (argc == 4) { - jsval zero = JSVAL_ZERO; - JS_SetProperty(cx, obj, "left", &JS_ARGV(cx, vp)[0]); - JS_SetProperty(cx, obj, "top", &JS_ARGV(cx, vp)[1]); - JS_SetProperty(cx, obj, "right", &JS_ARGV(cx, vp)[2]); - JS_SetProperty(cx, obj, "bottom", &JS_ARGV(cx, vp)[3]); - JS_SetProperty(cx, obj, "rleft", &zero); - JS_SetProperty(cx, obj, "rtop", &zero); - JS_SetProperty(cx, obj, "rright", &zero); - JS_SetProperty(cx, obj, "rbottom", &zero); + JS::RootedValue zero(cx, JSVAL_ZERO); + JS::RootedValue v0(cx, JS_ARGV(cx, vp)[0]); + JS::RootedValue v1(cx, JS_ARGV(cx, vp)[1]); + JS::RootedValue v2(cx, JS_ARGV(cx, vp)[2]); + JS::RootedValue v3(cx, JS_ARGV(cx, vp)[3]); + JS_SetProperty(cx, obj, "left", v0.address()); + JS_SetProperty(cx, obj, "top", v1.address()); + JS_SetProperty(cx, obj, "right", v2.address()); + JS_SetProperty(cx, obj, "bottom", v3.address()); + JS_SetProperty(cx, obj, "rleft", zero.address()); + JS_SetProperty(cx, obj, "rtop", zero.address()); + JS_SetProperty(cx, obj, "rright", zero.address()); + JS_SetProperty(cx, obj, "rbottom", zero.address()); } else { - jsval zero = JSVAL_ZERO; - JS_SetProperty(cx, obj, "left", &zero); - JS_SetProperty(cx, obj, "top", &zero); - JS_SetProperty(cx, obj, "right", &zero); - JS_SetProperty(cx, obj, "bottom", &zero); - JS_SetProperty(cx, obj, "rleft", &zero); - JS_SetProperty(cx, obj, "rtop", &zero); - JS_SetProperty(cx, obj, "rright", &zero); - JS_SetProperty(cx, obj, "rbottom", &zero); + JS::RootedValue zero(cx, JSVAL_ZERO); + JS_SetProperty(cx, obj, "left", zero.address()); + JS_SetProperty(cx, obj, "top", zero.address()); + JS_SetProperty(cx, obj, "right", zero.address()); + JS_SetProperty(cx, obj, "bottom", zero.address()); + JS_SetProperty(cx, obj, "rleft", zero.address()); + JS_SetProperty(cx, obj, "rtop", zero.address()); + JS_SetProperty(cx, obj, "rright", zero.address()); + JS_SetProperty(cx, obj, "rbottom", zero.address()); } JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); @@ -102,7 +114,7 @@ CStr ToPercentString(double pix, double per) return CStr::FromDouble(per)+"%"+( pix == 0.0 ? CStr() : pix > 0.0 ? CStr("+")+CStr::FromDouble(pix) : CStr::FromDouble(pix) ); } -JSBool JSI_GUISize::toString(JSContext* cx, uintN argc, jsval* vp) +JSBool JSI_GUISize::toString(JSContext* cx, uint argc, jsval* vp) { UNUSED2(argc); @@ -141,10 +153,10 @@ JSBool JSI_GUISize::toString(JSContext* cx, uintN argc, jsval* vp) JSClass JSI_GUIColor::JSI_class = { "GUIColor", 0, - JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, - JS_ConvertStub, JS_FinalizeStub, + JS_ConvertStub, NULL, NULL, NULL, NULL, JSI_GUIColor::construct }; @@ -159,11 +171,11 @@ JSPropertySpec JSI_GUIColor::JSI_props[] = JSFunctionSpec JSI_GUIColor::JSI_methods[] = { - { "toString", JSI_GUIColor::toString, 0, 0 }, - { 0 } + JS_FS("toString", JSI_GUIColor::toString, 0, 0), + JS_FS_END }; -JSBool JSI_GUIColor::construct(JSContext* cx, uintN argc, jsval* vp) +JSBool JSI_GUIColor::construct(JSContext* cx, uint argc, jsval* vp) { JSObject* obj = JS_NewObject(cx, &JSI_GUIColor::JSI_class, NULL, NULL); @@ -177,22 +189,19 @@ JSBool JSI_GUIColor::construct(JSContext* cx, uintN argc, jsval* vp) else { // Nice magenta: - jsval c; - if (!JS_NewNumberValue(cx, 1.0, &c)) - return JS_FALSE; - JS_SetProperty(cx, obj, "r", &c); - JS_SetProperty(cx, obj, "b", &c); - JS_SetProperty(cx, obj, "a", &c); - if (!JS_NewNumberValue(cx, 0.0, &c)) - return JS_FALSE; - JS_SetProperty(cx, obj, "g", &c); + JS::RootedValue c(cx, JS::NumberValue(1.0)); + JS_SetProperty(cx, obj, "r", c.address()); + JS_SetProperty(cx, obj, "b", c.address()); + JS_SetProperty(cx, obj, "a", c.address()); + c = JS::NumberValue(0.0); + JS_SetProperty(cx, obj, "g", c.address()); } JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); return JS_TRUE; } -JSBool JSI_GUIColor::toString(JSContext* cx, uintN argc, jsval* vp) +JSBool JSI_GUIColor::toString(JSContext* cx, uint argc, jsval* vp) { UNUSED2(argc); @@ -218,10 +227,10 @@ JSBool JSI_GUIColor::toString(JSContext* cx, uintN argc, jsval* vp) JSClass JSI_GUIMouse::JSI_class = { "GUIMouse", 0, - JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, - JS_ConvertStub, JS_FinalizeStub, + JS_ConvertStub, NULL, NULL, NULL, NULL, JSI_GUIMouse::construct }; @@ -235,37 +244,40 @@ JSPropertySpec JSI_GUIMouse::JSI_props[] = JSFunctionSpec JSI_GUIMouse::JSI_methods[] = { - { "toString", JSI_GUIMouse::toString, 0, 0 }, - { 0 } + JS_FS("toString", JSI_GUIMouse::toString, 0, 0), + JS_FS_END }; -JSBool JSI_GUIMouse::construct(JSContext* cx, uintN argc, jsval* vp) +JSBool JSI_GUIMouse::construct(JSContext* cx, uint argc, jsval* vp) { JSObject* obj = JS_NewObject(cx, &JSI_GUIMouse::JSI_class, NULL, NULL); if (argc == 3) { - JS_SetProperty(cx, obj, "x", &JS_ARGV(cx, vp)[0]); - JS_SetProperty(cx, obj, "y", &JS_ARGV(cx, vp)[1]); - JS_SetProperty(cx, obj, "buttons", &JS_ARGV(cx, vp)[2]); + JS::RootedValue v0(cx, JS_ARGV(cx, vp)[0]); + JS::RootedValue v1(cx, JS_ARGV(cx, vp)[1]); + JS::RootedValue v2(cx, JS_ARGV(cx, vp)[2]); + JS_SetProperty(cx, obj, "x", v0.address()); + JS_SetProperty(cx, obj, "y", v1.address()); + JS_SetProperty(cx, obj, "buttons", v2.address()); } else { - jsval zero = JSVAL_ZERO; - JS_SetProperty(cx, obj, "x", &zero); - JS_SetProperty(cx, obj, "y", &zero); - JS_SetProperty(cx, obj, "buttons", &zero); + JS::RootedValue zero (cx, JS::NumberValue(0)); + JS_SetProperty(cx, obj, "x", zero.address()); + JS_SetProperty(cx, obj, "y", zero.address()); + JS_SetProperty(cx, obj, "buttons", zero.address()); } JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); return JS_TRUE; } -JSBool JSI_GUIMouse::toString(JSContext* cx, uintN argc, jsval* vp) +JSBool JSI_GUIMouse::toString(JSContext* cx, uint argc, jsval* vp) { UNUSED2(argc); - int32 x, y, buttons; + i32 x, y, buttons; ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; pScriptInterface->GetProperty(JS_THIS_VALUE(cx, vp), "x", x); pScriptInterface->GetProperty(JS_THIS_VALUE(cx, vp), "y", y); diff --git a/source/gui/scripting/JSInterface_GUITypes.h b/source/gui/scripting/JSInterface_GUITypes.h index 37fd2f9199..1c722fce9a 100644 --- a/source/gui/scripting/JSInterface_GUITypes.h +++ b/source/gui/scripting/JSInterface_GUITypes.h @@ -26,8 +26,8 @@ extern JSClass JSI_class; \ extern JSPropertySpec JSI_props[]; \ extern JSFunctionSpec JSI_methods[]; \ - JSBool construct(JSContext* cx, uintN argc, jsval* vp); \ - JSBool toString(JSContext* cx, uintN argc, jsval* vp); \ + JSBool construct(JSContext* cx, uint argc, jsval* vp); \ + JSBool toString(JSContext* cx, uint argc, jsval* vp); \ } GUISTDTYPE(Size) diff --git a/source/gui/scripting/JSInterface_IGUIObject.cpp b/source/gui/scripting/JSInterface_IGUIObject.cpp index d122033090..95649f664b 100644 --- a/source/gui/scripting/JSInterface_IGUIObject.cpp +++ b/source/gui/scripting/JSInterface_IGUIObject.cpp @@ -32,28 +32,28 @@ JSClass JSI_IGUIObject::JSI_class = { "GUIObject", JSCLASS_HAS_PRIVATE, - JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_DeletePropertyStub, JSI_IGUIObject::getProperty, JSI_IGUIObject::setProperty, JS_EnumerateStub, JS_ResolveStub, - JS_ConvertStub, JS_FinalizeStub, + JS_ConvertStub, NULL, NULL, NULL, NULL, JSI_IGUIObject::construct }; -JSPropertySpec JSI_IGUIObject::JSI_props[] = +JSPropertySpec JSI_IGUIObject::JSI_props[] = { { 0 } }; -JSFunctionSpec JSI_IGUIObject::JSI_methods[] = +JSFunctionSpec JSI_IGUIObject::JSI_methods[] = { - { "toString", JSI_IGUIObject::toString, 0, 0 }, - { "focus", JSI_IGUIObject::focus, 0, 0 }, - { "blur", JSI_IGUIObject::blur, 0, 0 }, - { "getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0 }, - { 0 } + JS_FS("toString", JSI_IGUIObject::toString, 0, 0), + JS_FS("focus", JSI_IGUIObject::focus, 0, 0), + JS_FS("blur", JSI_IGUIObject::blur, 0, 0), + JS_FS("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0), + JS_FS_END }; -JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp) +JSBool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) { IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL); if (!e) @@ -88,9 +88,9 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* CStr eventName (CStr(propName.substr(2)).LowerCase()); std::map::iterator it = e->m_ScriptHandlers.find(eventName); if (it == e->m_ScriptHandlers.end()) - *vp = JSVAL_NULL; + vp.set(JS::NullValue()); else - *vp = it->second.get(); + vp.set((*it).second.get()); return JS_TRUE; } @@ -102,19 +102,19 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* if (parent) { // If the object isn't parentless, return a new object - *vp = OBJECT_TO_JSVAL(parent->GetJSObject()); + vp.set(JS::ObjectValue(*parent->GetJSObject())); } else { // Return null if there's no parent - *vp = JSVAL_NULL; + vp.set(JS::NullValue()); } return JS_TRUE; } // Also handle "name" specially else if (propName == "name") { - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, e->GetName().c_str())); + vp.set(JS::StringValue(JS_NewStringCopyZ(cx, e->GetName().c_str()))); return JS_TRUE; } // Handle all other properties @@ -135,7 +135,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* { bool value; GUI::GetSetting(e, propName, value); - *vp = value ? JSVAL_TRUE : JSVAL_FALSE; + vp.set(JS::BooleanValue(value)); break; } @@ -143,7 +143,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* { int value; GUI::GetSetting(e, propName, value); - *vp = INT_TO_JSVAL(value); + vp.set(JS::Int32Value(value)); break; } @@ -152,19 +152,22 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* float value; GUI::GetSetting(e, propName, value); // Create a garbage-collectable double - return JS_NewNumberValue(cx, value, vp); + vp.set(JS::NumberValue(value)); + return !vp.isNull(); } case GUIST_CColor: { CColor colour; GUI::GetSetting(e, propName, colour); - JSObject* obj = JS_NewObject(cx, &JSI_GUIColor::JSI_class, NULL, NULL); - *vp = OBJECT_TO_JSVAL(obj); // root it - - jsval c; + JS::RootedObject obj(cx, JS_NewObject(cx, &JSI_GUIColor::JSI_class, NULL, NULL)); + vp.set(JS::ObjectValue(*obj)); + JS::RootedValue c(cx); // Attempt to minimise ugliness through macrosity - #define P(x) if (!JS_NewNumberValue(cx, colour.x, &c)) return JS_FALSE; JS_SetProperty(cx, obj, #x, &c) + #define P(x) c = JS::NumberValue(colour.x); \ + if (c.isNull()) \ + return false; \ + JS_SetProperty(cx, obj, #x, c.address()) P(r); P(g); P(b); @@ -178,12 +181,12 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* { CClientArea area; GUI::GetSetting(e, propName, area); - JSObject* obj = JS_NewObject(cx, &JSI_GUISize::JSI_class, NULL, NULL); - *vp = OBJECT_TO_JSVAL(obj); // root it + + vp.set(JS::ObjectValue(*JS_NewObject(cx, &JSI_GUISize::JSI_class, NULL, NULL))); try { ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - #define P(x, y, z) pScriptInterface->SetProperty(OBJECT_TO_JSVAL(obj), #z, area.x.y, false, true) + #define P(x, y, z) pScriptInterface->SetProperty(vp, #z, area.x.y, false, true) P(pixel, left, left); P(pixel, top, top); P(pixel, right, right); @@ -207,7 +210,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* { CGUIString value; GUI::GetSetting(e, propName, value); - *vp = ScriptInterface::ToJSVal(cx, value.GetOriginalString()); + ScriptInterface::ToJSVal(cx, *vp.address(), value.GetOriginalString()); break; } @@ -215,7 +218,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* { CStr value; GUI::GetSetting(e, propName, value); - *vp = ScriptInterface::ToJSVal(cx, value); + ScriptInterface::ToJSVal(cx, *vp.address(), value); break; } @@ -223,7 +226,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* { CStrW value; GUI::GetSetting(e, propName, value); - *vp = ScriptInterface::ToJSVal(cx, value); + ScriptInterface::ToJSVal(cx, *vp.address(), value); break; } @@ -231,7 +234,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* { CGUISpriteInstance *value; GUI::GetSettingPointer(e, propName, value); - *vp = ScriptInterface::ToJSVal(cx, value->GetName()); + ScriptInterface::ToJSVal(cx, *vp.address(), value->GetName()); break; } @@ -247,7 +250,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* case EAlign_Center: word = "center"; break; default: debug_warn(L"Invalid EAlign!"); word = "error"; break; } - *vp = ScriptInterface::ToJSVal(cx, word); + ScriptInterface::ToJSVal(cx, *vp.address(), word); break; } @@ -263,7 +266,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* case EVAlign_Center: word = "center"; break; default: debug_warn(L"Invalid EVAlign!"); word = "error"; break; } - *vp = ScriptInterface::ToJSVal(cx, word); + ScriptInterface::ToJSVal(cx, *vp.address(), word); break; } @@ -272,13 +275,14 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* CGUIList value; GUI::GetSetting(e, propName, value); - JSObject *obj = JS_NewArrayObject(cx, 0, NULL); - *vp = OBJECT_TO_JSVAL(obj); // root it - - for (size_t i = 0; i < value.m_Items.size(); ++i) + JS::RootedObject obj(cx, JS_NewArrayObject(cx, 0, NULL)); + vp.set(JS::ObjectValue(*obj)); + + for (u32 i = 0; i < value.m_Items.size(); ++i) { - jsval val = ScriptInterface::ToJSVal(cx, value.m_Items[i].GetOriginalString()); - JS_SetElement(cx, obj, (jsint)i, &val); + JS::RootedValue val(cx); + ScriptInterface::ToJSVal(cx, val.get(), value.m_Items[i].GetOriginalString()); + JS_SetElement(cx, obj, i, val.address()); } break; @@ -294,7 +298,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* } } -JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool UNUSED(strict), jsval* vp) +JSBool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JSBool UNUSED(strict), JS::MutableHandleValue vp) { IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL); if (!e) @@ -311,7 +315,7 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool if (propName == "name") { std::string value; - if (!ScriptInterface::FromJSVal(cx, *vp, value)) + if (!ScriptInterface::FromJSVal(cx, vp, value)) return JS_FALSE; e->SetName(value); return JS_TRUE; @@ -320,14 +324,14 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool // Use onWhatever to set event handlers if (propName.substr(0, 2) == "on") { - if (!JSVAL_IS_OBJECT(*vp) || !JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(*vp))) + if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(cx, &vp.toObject())) { JS_ReportError(cx, "on- event-handlers must be functions"); return JS_FALSE; } CStr eventName (CStr(propName.substr(2)).LowerCase()); - e->SetScriptHandler(eventName, JSVAL_TO_OBJECT(*vp)); + e->SetScriptHandler(eventName, &vp.toObject()); return JS_TRUE; } @@ -346,7 +350,7 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_CStr: { std::string value; - if (!ScriptInterface::FromJSVal(cx, *vp, value)) + if (!ScriptInterface::FromJSVal(cx, vp, value)) return JS_FALSE; GUI::SetSetting(e, propName, value); @@ -356,8 +360,8 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_CStrW: { std::wstring value; - if (!ScriptInterface::FromJSVal(cx, *vp, value)) - return JS_FALSE; + if (!ScriptInterface::FromJSVal(cx, vp, value)) + return false; GUI::SetSetting(e, propName, value); break; @@ -366,8 +370,8 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_CGUISpriteInstance: { std::string value; - if (!ScriptInterface::FromJSVal(cx, *vp, value)) - return JS_FALSE; + if (!ScriptInterface::FromJSVal(cx, vp, value)) + return false; GUI::SetSetting(e, propName, CGUISpriteInstance(value)); break; @@ -376,7 +380,7 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_CGUIString: { std::wstring value; - if (!ScriptInterface::FromJSVal(cx, *vp, value)) + if (!ScriptInterface::FromJSVal(cx, vp, value)) return JS_FALSE; CGUIString str; @@ -388,7 +392,7 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_EAlign: { std::string value; - if (!ScriptInterface::FromJSVal(cx, *vp, value)) + if (!ScriptInterface::FromJSVal(cx, vp, value)) return JS_FALSE; EAlign a; @@ -407,7 +411,7 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_EVAlign: { std::string value; - if (!ScriptInterface::FromJSVal(cx, *vp, value)) + if (!ScriptInterface::FromJSVal(cx, vp, value)) return JS_FALSE; EVAlign a; @@ -425,8 +429,8 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_int: { - int32 value; - if (JS_ValueToInt32(cx, *vp, &value) == JS_TRUE) + int value; + if (ScriptInterface::FromJSVal(cx, vp, value)) GUI::SetSetting(e, propName, value); else { @@ -438,8 +442,8 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_float: { - jsdouble value; - if (JS_ValueToNumber(cx, *vp, &value) == JS_TRUE) + double value; + if (JS_ValueToNumber(cx, vp, &value) == true) GUI::SetSetting(e, propName, (float)value); else { @@ -452,8 +456,8 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_bool: { JSBool value; - if (JS_ValueToBoolean(cx, *vp, &value) == JS_TRUE) - GUI::SetSetting(e, propName, value == JS_TRUE); + if (JS_ValueToBoolean(cx, vp, &value)) + GUI::SetSetting(e, propName, value); else { JS_ReportError(cx, "Cannot convert value to bool"); @@ -464,10 +468,10 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_CClientArea: { - if (JSVAL_IS_STRING(*vp)) + if (vp.isString()) { std::wstring value; - if (!ScriptInterface::FromJSVal(cx, *vp, value)) + if (!ScriptInterface::FromJSVal(cx, vp, value)) return JS_FALSE; if (e->SetSetting(propName, value) != PSRETURN_OK) @@ -476,14 +480,13 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool return JS_FALSE; } } - else if (JSVAL_IS_OBJECT(*vp) && JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), &JSI_GUISize::JSI_class, NULL)) + else if (vp.isObject() && JS_InstanceOf(cx, &vp.toObject(), &JSI_GUISize::JSI_class, NULL)) { CClientArea area; GUI::GetSetting(e, propName, area); - JSObject* obj = JSVAL_TO_OBJECT(*vp); ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - #define P(x, y, z) pScriptInterface->GetProperty(OBJECT_TO_JSVAL(obj), #z, area.x.y) + #define P(x, y, z) pScriptInterface->GetProperty(vp, #z, area.x.y) P(pixel, left, left); P(pixel, top, top); P(pixel, right, right); @@ -506,10 +509,10 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_CColor: { - if (JSVAL_IS_STRING(*vp)) + if (vp.isString()) { std::wstring value; - if (!ScriptInterface::FromJSVal(cx, *vp, value)) + if (!ScriptInterface::FromJSVal(cx, vp, value)) return JS_FALSE; if (e->SetSetting(propName, value) != PSRETURN_OK) @@ -518,13 +521,14 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool return JS_FALSE; } } - else if (JSVAL_IS_OBJECT(*vp) && JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), &JSI_GUIColor::JSI_class, NULL)) + else if (vp.isObject() && JS_InstanceOf(cx, &vp.toObject(), &JSI_GUIColor::JSI_class, NULL)) { CColor colour; - JSObject* obj = JSVAL_TO_OBJECT(*vp); - jsval t; double s; - #define PROP(x) JS_GetProperty(cx, obj, #x, &t); \ - JS_ValueToNumber(cx, t, &s); \ + JS::RootedObject (cx, &vp.toObject()); + JS::RootedValue t(cx); + double s; + #define PROP(x) JS_GetProperty(cx, obj, #x, t.address()); \ + s = t.toDouble(); \ colour.x = (float)s PROP(r); PROP(g); PROP(b); PROP(a); #undef PROP @@ -541,16 +545,16 @@ JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool case GUIST_CGUIList: { - JSObject* obj = JSVAL_TO_OBJECT(*vp); - jsuint length; - if (JSVAL_IS_OBJECT(*vp) && JS_GetArrayLength(cx, obj, &length) == JS_TRUE) + u32 length; + if (vp.isObject() && JS_GetArrayLength(cx, &vp.toObject(), &length) == true) { CGUIList list; - - for (int i=0; i<(int)length; ++i) + JS::RootedObject obj(cx, &vp.toObject()); + + for (u32 i=0; iGetName().c_str()); buffer[255] = 0; - JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, buffer))); + JS_SET_RVAL(cx, vp, JS::StringValue(JS_NewStringCopyZ(cx, buffer))); return JS_TRUE; } -JSBool JSI_IGUIObject::focus(JSContext* cx, uintN argc, jsval* vp) +JSBool JSI_IGUIObject::focus(JSContext* cx, uint argc, jsval* vp) { UNUSED2(argc); @@ -639,7 +643,7 @@ JSBool JSI_IGUIObject::focus(JSContext* cx, uintN argc, jsval* vp) return JS_TRUE; } -JSBool JSI_IGUIObject::blur(JSContext* cx, uintN argc, jsval* vp) +JSBool JSI_IGUIObject::blur(JSContext* cx, uint argc, jsval* vp) { UNUSED2(argc); @@ -653,7 +657,7 @@ JSBool JSI_IGUIObject::blur(JSContext* cx, uintN argc, jsval* vp) return JS_TRUE; } -JSBool JSI_IGUIObject::getComputedSize(JSContext* cx, uintN argc, jsval* vp) +JSBool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, jsval* vp) { UNUSED2(argc); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL); @@ -663,14 +667,14 @@ JSBool JSI_IGUIObject::getComputedSize(JSContext* cx, uintN argc, jsval* vp) e->UpdateCachedSize(); CRect size = e->m_CachedActualSize; - JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); + JS::RootedValue objVal(cx, JS::ObjectValue(*JS_NewObject(cx, NULL, NULL, NULL))); try { ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - pScriptInterface->SetProperty(OBJECT_TO_JSVAL(obj), "left", size.left, false, true); - pScriptInterface->SetProperty(OBJECT_TO_JSVAL(obj), "right", size.right, false, true); - pScriptInterface->SetProperty(OBJECT_TO_JSVAL(obj), "top", size.top, false, true); - pScriptInterface->SetProperty(OBJECT_TO_JSVAL(obj), "bottom", size.bottom, false, true); + pScriptInterface->SetProperty(objVal, "left", size.left, false, true); + pScriptInterface->SetProperty(objVal, "right", size.right, false, true); + pScriptInterface->SetProperty(objVal, "top", size.top, false, true); + pScriptInterface->SetProperty(objVal, "bottom", size.bottom, false, true); } catch (PSERROR_Scripting_ConversionFailed&) { @@ -678,6 +682,6 @@ JSBool JSI_IGUIObject::getComputedSize(JSContext* cx, uintN argc, jsval* vp) return JS_FALSE; } - JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); + JS_SET_RVAL(cx, vp, objVal); return JS_TRUE; } diff --git a/source/gui/scripting/JSInterface_IGUIObject.h b/source/gui/scripting/JSInterface_IGUIObject.h index 94b8a5e40a..20e16272e4 100644 --- a/source/gui/scripting/JSInterface_IGUIObject.h +++ b/source/gui/scripting/JSInterface_IGUIObject.h @@ -25,13 +25,13 @@ namespace JSI_IGUIObject extern JSClass JSI_class; extern JSPropertySpec JSI_props[]; extern JSFunctionSpec JSI_methods[]; - JSBool getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp); - JSBool setProperty(JSContext* cx, JSObject* obj, jsid id, JSBool strict, jsval* vp); - JSBool construct(JSContext* cx, uintN argc, jsval* vp); - JSBool toString(JSContext* cx, uintN argc, jsval* vp); - JSBool focus(JSContext* cx, uintN argc, jsval* vp); - JSBool blur(JSContext* cx, uintN argc, jsval* vp); - JSBool getComputedSize(JSContext* cx, uintN argc, jsval* vp); + JSBool getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); + JSBool setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JSBool UNUSED(strict), JS::MutableHandleValue vp); + JSBool construct(JSContext* cx, uint argc, jsval* vp); + JSBool toString(JSContext* cx, uint argc, jsval* vp); + JSBool focus(JSContext* cx, uint argc, jsval* vp); + JSBool blur(JSContext* cx, uint argc, jsval* vp); + JSBool getComputedSize(JSContext* cx, uint argc, jsval* vp); void init(ScriptInterface& scriptInterface); } diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 0c6a6fded0..f55cc4a815 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -69,7 +69,6 @@ #include "soundmanager/SoundManager.h" #include "soundmanager/scripting/JSInterface_Sound.h" -#include "js/jsapi.h" /* * This file defines a set of functions that are available to GUI scripts, to allow * interaction with the rest of the engine. @@ -88,10 +87,7 @@ namespace { // TODO: Use LOGERROR to print a friendly error message when the requirements aren't met instead of failing with debug_warn when cloning. void PushGuiPage(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal initData) { - // TODO: Check the comment in CGUIManager::SwitchPage for a detailed explanation of this hack. - CScriptVal cloneSaveInitData; - cloneSaveInitData = g_GUI->GetScriptInterface()->CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), initData.get()); - g_GUI->PushPage(name, g_GUI->GetScriptInterface()->WriteStructuredClone(cloneSaveInitData.get())); + g_GUI->PushPage(name, pCxPrivate->pScriptInterface->WriteStructuredClone(initData.get())); } void SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal initData) @@ -557,7 +553,7 @@ void DebugWarn(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) void ForceGC(ScriptInterface::CxPrivate* pCxPrivate) { double time = timer_Time(); - JS_GC(pCxPrivate->pScriptInterface->GetContext()); + JS_GC(pCxPrivate->pScriptInterface->GetJSRuntime()); time = timer_Time() - time; g_Console->InsertMessage(L"Garbage collection completed in: %f", time); } diff --git a/source/lib/types.h b/source/lib/types.h index bd8b15b196..f0f5405b86 100644 --- a/source/lib/types.h +++ b/source/lib/types.h @@ -29,16 +29,16 @@ #include "lib/posix/posix_types.h" -// defines instead of typedefs so we can #undef in case of conflicts +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; -#define i8 int8_t -#define i16 int16_t -#define i32 int32_t -#define i64 int64_t +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; -#define u8 uint8_t -#define u16 uint16_t -#define u32 uint32_t -#define u64 uint64_t +typedef unsigned int uint; #endif // #ifndef INCLUDED_TYPES diff --git a/source/lobby/XmppClient.cpp b/source/lobby/XmppClient.cpp index 2b827b8c06..b8ac7c2abe 100644 --- a/source/lobby/XmppClient.cpp +++ b/source/lobby/XmppClient.cpp @@ -309,7 +309,7 @@ void XmppClient::SendIqGetRatingList() void XmppClient::SendIqGameReport(ScriptInterface& scriptInterface, CScriptVal data) { glooxwrapper::JID xpartamuppJid(m_xpartamuppId); - jsval dataval = data.get(); + JS::RootedValue dataval(scriptInterface.GetContext(), data.get()); // Setup some base stanza attributes GameReport* game = new GameReport(); @@ -343,7 +343,7 @@ void XmppClient::SendIqGameReport(ScriptInterface& scriptInterface, CScriptVal d void XmppClient::SendIqRegisterGame(ScriptInterface& scriptInterface, CScriptVal data) { glooxwrapper::JID xpartamuppJid(m_xpartamuppId); - jsval dataval = data.get(); + JS::RootedValue dataval(scriptInterface.GetContext(), data.get()); // Setup some base stanza attributes GameListQuery* g = new GameListQuery(); diff --git a/source/main.cpp b/source/main.cpp index 59f15868a3..58f05d1c3c 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -184,8 +184,9 @@ static void PumpEvents() PROFILE2("event"); if (g_GUI) { - std::string data = g_GUI->GetScriptInterface()->StringifyJSON( - ScriptInterface::ToJSVal(g_GUI->GetScriptInterface()->GetContext(), ev)); + JS::Value tmpVal; + ScriptInterface::ToJSVal(g_GUI->GetScriptInterface()->GetContext(), tmpVal, ev); + std::string data = g_GUI->GetScriptInterface()->StringifyJSON(tmpVal); PROFILE2_ATTR("%s", data.c_str()); } in_dispatch_event(&ev); diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp index 14c613e184..32cf47426f 100644 --- a/source/network/NetServer.cpp +++ b/source/network/NetServer.cpp @@ -342,6 +342,9 @@ void* CNetServerWorker::RunThread(void* data) void CNetServerWorker::Run() { + // The script runtime uses the profiler and therefore the thread must be registered before the runtime is created + g_Profiler2.RegisterCurrentThread("Net server"); + // To avoid the need for JS_SetContextThread, we create and use and destroy // the script interface entirely within this network thread m_ScriptInterface = new ScriptInterface("Engine", "Net server", ScriptInterface::CreateRuntime()); diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 996fe4d228..59c50a04c9 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -560,6 +560,10 @@ static void ShutdownPs() SAFE_DELETE(g_GUI); SAFE_DELETE(g_Console); + + // This is needed to ensure that no callbacks from the JSAPI try to use + // the profiler when it's already destructed + g_ScriptRuntime.reset(); // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y, false); @@ -856,7 +860,7 @@ void Init(const CmdLineArgs& args, int flags) g_Logger = new CLogger; // Workaround until Simulation and AI use their own threads and also their own runtimes - g_ScriptRuntime = ScriptInterface::CreateRuntime(128 * 1024 * 1024); + g_ScriptRuntime = ScriptInterface::CreateRuntime(384 * 1024 * 1024); // Special command-line mode to dump the entity schemas instead of running the game. // (This must be done after loading VFS etc, but should be done before wasting time @@ -1100,7 +1104,7 @@ bool Autostart(const CmdLineArgs& args) CStr seedArg = args.Get("autostart-random"); // Default seed is 0 - uint32 seed = 0; + u32 seed = 0; if (!seedArg.empty()) { if (seedArg.compare("-1") == 0) diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp index 59de30c928..7721289b0e 100644 --- a/source/ps/Replay.cpp +++ b/source/ps/Replay.cpp @@ -127,7 +127,7 @@ void CReplayPlayer::Replay() new CProfileManager; g_ScriptStatsTable = new CScriptStatsTable; g_ProfileViewer.AddRootTable(g_ScriptStatsTable); - g_ScriptRuntime = ScriptInterface::CreateRuntime(128 * 1024 * 1024); + g_ScriptRuntime = ScriptInterface::CreateRuntime(384 * 1024 * 1024); CGame game(true); g_Game = &game; @@ -243,6 +243,10 @@ void CReplayPlayer::Replay() timer_DisplayClientTotals(); + // Must be explicitly destructed here to avoid callbacks from the JSAPI trying to use g_Profiler2 when + // it's already destructed. + g_ScriptRuntime.reset(); + // Clean up delete &g_TexMan; diff --git a/source/ps/scripting/JSInterface_VFS.cpp b/source/ps/scripting/JSInterface_VFS.cpp index 96517f2edb..6f83c667a6 100644 --- a/source/ps/scripting/JSInterface_VFS.cpp +++ b/source/ps/scripting/JSInterface_VFS.cpp @@ -68,8 +68,9 @@ static Status BuildDirEntListCB(const VfsPath& pathname, const CFileInfo& UNUSED { BuildDirEntListState* s = (BuildDirEntListState*)cbData; - jsval val = ScriptInterface::ToJSVal(s->cx, CStrW(pathname.string())); - JS_SetElement(s->cx, s->filename_array, s->cur_idx++, &val); + JS::RootedValue val(s->cx); + ScriptInterface::ToJSVal( s->cx, val.get(), CStrW(pathname.string()) ); + JS_SetElement(s->cx, s->filename_array, s->cur_idx++, val.address()); return INFO::OK; } @@ -155,7 +156,9 @@ CScriptVal JSI_VFS::ReadFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstrin contents.Replace("\r\n", "\n"); // Decode as UTF-8 - return ScriptInterface::ToJSVal( pCxPrivate->pScriptInterface->GetContext(), contents.FromUTF8() ); + JS::Value ret; + ScriptInterface::ToJSVal( pCxPrivate->pScriptInterface->GetContext(), ret, contents.FromUTF8() ); + return ret; } @@ -191,8 +194,9 @@ CScriptVal JSI_VFS::ReadFileLines(ScriptInterface::CxPrivate* pCxPrivate, std::w while (std::getline(ss, line)) { // Decode each line as UTF-8 - jsval val = ScriptInterface::ToJSVal(cx, CStr(line).FromUTF8()); - JS_SetElement(cx, line_array, cur_line++, &val); + JS::RootedValue val(cx); + ScriptInterface::ToJSVal(cx, val.get(), CStr(line).FromUTF8()); + JS_SetElement(cx, line_array, cur_line++, val.address()); } return OBJECT_TO_JSVAL( line_array ); diff --git a/source/scriptinterface/AutoRooters.cpp b/source/scriptinterface/AutoRooters.cpp index ab7c3d886b..c47d278ec2 100644 --- a/source/scriptinterface/AutoRooters.cpp +++ b/source/scriptinterface/AutoRooters.cpp @@ -40,21 +40,21 @@ void AutoGCRooter::Trace(JSTracer* trc) for (size_t i = 0; i < m_Objects.size(); ++i) { - JS_CALL_OBJECT_TRACER(trc, m_Objects[i], "AutoGCRooter object"); + JS_CallObjectTracer(trc, &m_Objects[i], "AutoGCRooter object"); } for (size_t i = 0; i < m_Vals.size(); ++i) { - JS_CALL_VALUE_TRACER(trc, m_Vals[i], "AutoGCRooter val"); + JS_CallValueTracer(trc, &m_Vals[i], "AutoGCRooter val"); } for (size_t i = 0; i < m_IdArrays.size(); ++i) { - for (jsint j = 0; j < m_IdArrays[i]->length; ++j) + for (int j = 0; j < JS_IdArrayLength(m_ScriptInterface.GetContext(), m_IdArrays[i]); ++j) { jsval val = JSVAL_VOID; - JS_IdToValue(m_ScriptInterface.GetContext(), m_IdArrays[i]->vector[j], &val); - JS_CALL_VALUE_TRACER(trc, val, "AutoGCRooter id array"); + JS_IdToValue(m_ScriptInterface.GetContext(), JS_IdArrayGet(m_ScriptInterface.GetContext(), m_IdArrays[i], j), &val); + JS_CallValueTracer(trc, &val, "AutoGCRooter id array"); } } diff --git a/source/scriptinterface/AutoRooters.h b/source/scriptinterface/AutoRooters.h index 3cf94ad256..06be5536df 100644 --- a/source/scriptinterface/AutoRooters.h +++ b/source/scriptinterface/AutoRooters.h @@ -20,7 +20,6 @@ #include "scriptinterface/ScriptTypes.h" -#include "js/jsapi.h" /** * Helper for rooting large groups of script values. diff --git a/source/scriptinterface/NativeWrapperDecls.h b/source/scriptinterface/NativeWrapperDecls.h index d051d08457..13d686f52e 100644 --- a/source/scriptinterface/NativeWrapperDecls.h +++ b/source/scriptinterface/NativeWrapperDecls.h @@ -26,7 +26,7 @@ #define NUMBERED_LIST_BALANCED(z, i, data) BOOST_PP_COMMA_IF(i) data##i // Some other things #define TYPED_ARGS(z, i, data) , T##i a##i -#define CONVERT_ARG(z, i, data) T##i a##i; if (! ScriptInterface::FromJSVal(cx, i < argc ? JS_ARGV(cx, vp)[i] : JSVAL_VOID, a##i)) return JS_FALSE; +#define CONVERT_ARG(z, i, data) T##i a##i; if (! ScriptInterface::FromJSVal(cx, i < argc ? JS_ARGV(cx, vp)[i] : JS::UndefinedValue(), a##i)) return false; // List-generating macros, named roughly after their first list item #define TYPENAME_T0_HEAD(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_HEAD, typename T) // "typename T0, typename T1, " @@ -51,14 +51,14 @@ BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) // (Definition comes later, since it depends on some things we haven't defined yet) #define OVERLOADS(z, i, data) \ template \ - static JSBool call(JSContext* cx, uintN argc, jsval* vp); + static JSBool call(JSContext* cx, uint argc, jsval* vp); BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS // Similar, for class methods #define OVERLOADS(z, i, data) \ template \ - static JSBool callMethod(JSContext* cx, uintN argc, jsval* vp); + static JSBool callMethod(JSContext* cx, uint argc, jsval* vp); BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS diff --git a/source/scriptinterface/NativeWrapperDefns.h b/source/scriptinterface/NativeWrapperDefns.h index 59ee35bbac..fe37f93655 100644 --- a/source/scriptinterface/NativeWrapperDefns.h +++ b/source/scriptinterface/NativeWrapperDefns.h @@ -27,7 +27,7 @@ struct ScriptInterface_NativeWrapper { #define OVERLOADS(z, i, data) \ template \ static void call(JSContext* cx, jsval& rval, F fptr T0_A0(z,i)) { \ - rval = ScriptInterface::ToJSVal(cx, fptr(ScriptInterface::GetScriptInterfaceAndCBData(cx) A0_TAIL(z,i))); \ + ScriptInterface::ToJSVal(cx, rval, fptr(ScriptInterface::GetScriptInterfaceAndCBData(cx) A0_TAIL(z,i))); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) @@ -53,7 +53,7 @@ struct ScriptInterface_NativeMethodWrapper { #define OVERLOADS(z, i, data) \ template \ static void call(JSContext* cx, jsval& rval, TC* c, F fptr T0_A0(z,i)) { \ - rval = ScriptInterface::ToJSVal(cx, (c->*fptr)( A0(z,i) )); \ + ScriptInterface::ToJSVal(cx, rval, (c->*fptr)( A0(z,i) )); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) @@ -79,11 +79,11 @@ struct ScriptInterface_NativeMethodWrapper { #define SCRIPT_PROFILE \ if (g_ScriptProfilingEnabled) \ { \ - ENSURE(JSVAL_IS_OBJECT(JS_CALLEE(cx, vp)) && JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)))); \ + ENSURE(JS_CALLEE(cx, vp).isObject() && JS_ObjectIsFunction(cx, &JS_CALLEE(cx, vp).toObject())); \ const char* name = "(unknown)"; \ jsval nameval; \ - if (JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), 0, &nameval) \ - && !JSVAL_IS_VOID(nameval)) \ + nameval = JS_GetReservedSlot( &JS_CALLEE(cx, vp).toObject(), 0); \ + if (!nameval.isUndefined()) \ name = static_cast(JSVAL_TO_PRIVATE(nameval)); \ CProfileSampleScript profile(name); \ } @@ -91,14 +91,14 @@ struct ScriptInterface_NativeMethodWrapper { // JSFastNative-compatible function that wraps the function identified in the template argument list #define OVERLOADS(z, i, data) \ template \ - JSBool ScriptInterface::call(JSContext* cx, uintN argc, jsval* vp) { \ + JSBool ScriptInterface::call(JSContext* cx, uint argc, jsval* vp) { \ UNUSED2(argc); \ SCRIPT_PROFILE \ BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \ jsval rval = JSVAL_VOID; \ ScriptInterface_NativeWrapper::call(cx, rval, fptr A0_TAIL(z,i)); \ JS_SET_RVAL(cx, vp, rval); \ - return (ScriptInterface::IsExceptionPending(cx) ? JS_FALSE : JS_TRUE); \ + return !ScriptInterface::IsExceptionPending(cx); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS @@ -106,17 +106,17 @@ BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) // Same idea but for methods #define OVERLOADS(z, i, data) \ template \ - JSBool ScriptInterface::callMethod(JSContext* cx, uintN argc, jsval* vp) { \ + JSBool ScriptInterface::callMethod(JSContext* cx, uint argc, jsval* vp) { \ UNUSED2(argc); \ SCRIPT_PROFILE \ - if (ScriptInterface::GetClass(cx, JS_THIS_OBJECT(cx, vp)) != CLS) return JS_FALSE; \ - TC* c = static_cast(ScriptInterface::GetPrivate(cx, JS_THIS_OBJECT(cx, vp))); \ - if (! c) return JS_FALSE; \ + if (ScriptInterface::GetClass(JS_THIS_OBJECT(cx, vp)) != CLS) return false; \ + TC* c = static_cast(ScriptInterface::GetPrivate(JS_THIS_OBJECT(cx, vp))); \ + if (! c) return false; \ BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \ jsval rval = JSVAL_VOID; \ ScriptInterface_NativeMethodWrapper::call(cx, rval, c, fptr A0_TAIL(z,i)); \ JS_SET_RVAL(cx, vp, rval); \ - return (ScriptInterface::IsExceptionPending(cx) ? JS_FALSE : JS_TRUE); \ + return !ScriptInterface::IsExceptionPending(cx); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS diff --git a/source/scriptinterface/ScriptConversions.cpp b/source/scriptinterface/ScriptConversions.cpp index 22e1cf52d1..35951bc198 100644 --- a/source/scriptinterface/ScriptConversions.cpp +++ b/source/scriptinterface/ScriptConversions.cpp @@ -32,88 +32,84 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, bool& out) { - JSBool ret; - WARN_IF_NOT(JSVAL_IS_BOOLEAN(v), v); - if (!JS_ValueToBoolean(cx, v, &ret)) - return false; - out = (ret ? true : false); + JSAutoRequest rq(cx); + WARN_IF_NOT(v.isBoolean(), v); + out = JS::ToBoolean(v); return true; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, float& out) { - jsdouble ret; - WARN_IF_NOT(JSVAL_IS_NUMBER(v), v); - if (!JS_ValueToNumber(cx, v, &ret)) + JSAutoRequest rq(cx); + double tmp; + WARN_IF_NOT(v.isNumber(), v); + if (!JS::ToNumber(cx, v, &tmp)) return false; - out = ret; + out = tmp; return true; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, double& out) { - jsdouble ret; - WARN_IF_NOT(JSVAL_IS_NUMBER(v), v); - if (!JS_ValueToNumber(cx, v, &ret)) + JSAutoRequest rq(cx); + WARN_IF_NOT(v.isNumber(), v); + if (!JS::ToNumber(cx, v, &out)) return false; - out = ret; return true; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, i32& out) { - int32 ret; - WARN_IF_NOT(JSVAL_IS_NUMBER(v), v); - if (!JS_ValueToECMAInt32(cx, v, &ret)) + JSAutoRequest rq(cx); + WARN_IF_NOT(v.isNumber(), v); + if (!JS::ToInt32(cx, v, &out)) return false; - out = ret; return true; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, u32& out) { - uint32 ret; - WARN_IF_NOT(JSVAL_IS_NUMBER(v), v); - if (!JS_ValueToECMAUint32(cx, v, &ret)) + JSAutoRequest rq(cx); + WARN_IF_NOT(v.isNumber(), v); + if (!JS::ToUint32(cx, v, &out)) return false; - out = ret; return true; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, u16& out) { - uint16 ret; - WARN_IF_NOT(JSVAL_IS_NUMBER(v), v); - if (!JS_ValueToUint16(cx, v, &ret)) + JSAutoRequest rq(cx); + WARN_IF_NOT(v.isNumber(), v); + if (!JS::ToUint16(cx, v, &out)) return false; - out = ret; return true; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, u8& out) { - uint16 ret; - WARN_IF_NOT(JSVAL_IS_NUMBER(v), v); - if (!JS_ValueToUint16(cx, v, &ret)) + JSAutoRequest rq(cx); + u16 tmp; + WARN_IF_NOT(v.isNumber(), v); + if (!JS::ToUint16(cx, v, &tmp)) return false; - out = (u8)ret; + out = (u8)tmp; return true; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, long& out) { - int32 tmp; - JSBool ok = JS_ValueToInt32(cx, v, &tmp); + i64 tmp; + bool ok = JS::ToInt64(cx, v, &tmp); out = (long)tmp; - return ok == JS_TRUE; + return ok; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, unsigned long& out) { - int32 tmp; - JSBool ok = JS_ValueToInt32(cx, v, &tmp); + u64 tmp; + bool ok = JS::ToUint64(cx, v, &tmp); out = (unsigned long)tmp; - return ok == JS_TRUE; + return ok; } // see comment below (where the same preprocessor condition is used) @@ -121,29 +117,28 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, size_t& out) { - int temp; - if(!FromJSVal(cx, v, temp)) + int tmp; + if(!FromJSVal(cx, v, tmp)) return false; if(temp < 0) return false; - out = (size_t)temp; + out = (size_t)tmp; return true; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, ssize_t& out) { - int temp; - if(!FromJSVal(cx, v, temp)) + int tmp; + if(!FromJSVal(cx, v, tmp)) return false; - if(temp < 0) + if(tmp < 0) return false; - out = (ssize_t)temp; + out = (ssize_t)tmp; return true; } #endif -// NOTE: we can't define a jsval specialisation, because that conflicts with integer types template<> bool ScriptInterface::FromJSVal(JSContext* UNUSED(cx), jsval v, CScriptVal& out) { out = v; @@ -158,12 +153,13 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsva template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, std::wstring& out) { + JSAutoRequest rq(cx); WARN_IF_NOT(JSVAL_IS_STRING(v) || JSVAL_IS_NUMBER(v), v); // allow implicit number conversions - JSString* ret = JS_ValueToString(cx, v); - if (!ret) + JSString* str = JS_ValueToString(cx, v); + if (!str) FAIL("Argument must be convertible to a string"); size_t length; - const jschar* ch = JS_GetStringCharsAndLength(cx, ret, &length); + const jschar* ch = JS_GetStringCharsAndLength(cx, str, &length); if (!ch) FAIL("JS_GetStringsCharsAndLength failed"); // out of memory out = std::wstring(ch, ch + length); @@ -181,14 +177,15 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, Path& o template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, std::string& out) { - WARN_IF_NOT(JSVAL_IS_STRING(v) || JSVAL_IS_NUMBER(v), v); // allow implicit number conversions - JSString* ret = JS_ValueToString(cx, v); - if (!ret) + JSAutoRequest rq(cx); + WARN_IF_NOT(v.isString() || v.isNumber(), v); // allow implicit number conversions + JSString* str = JS_ValueToString(cx, v); + if (!str) FAIL("Argument must be convertible to a string"); - char* ch = JS_EncodeString(cx, ret); // chops off high byte of each jschar + char* ch = JS_EncodeString(cx, str); // chops off high byte of each jschar if (!ch) FAIL("JS_EncodeString failed"); // out of memory - out = std::string(ch, ch + JS_GetStringLength(ret)); + out = std::string(ch, ch + JS_GetStringLength(str)); JS_free(cx, ch); return true; } @@ -205,22 +202,27 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, CStrW& template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, Entity& out) { - JSObject* obj; - if (!JS_ValueToObject(cx, v, &obj) || obj == NULL) + JSAutoRequest rq(cx); + if (!v.isObject()) FAIL("Argument must be an object"); - jsval templateName, id, player, position, rotation; + JS::RootedObject obj(cx, &v.toObject()); + JS::RootedValue templateName(cx); + JS::RootedValue id(cx); + JS::RootedValue player(cx); + JS::RootedValue position(cx); + JS::RootedValue rotation(cx); // TODO: Report type errors - if(!JS_GetProperty(cx, obj, "player", &player) || !FromJSVal(cx, player, out.playerID)) + if(!JS_GetProperty(cx, obj, "player", player.address()) || !FromJSVal(cx, player, out.playerID)) FAIL("Failed to read Entity.player property"); - if (!JS_GetProperty(cx, obj, "templateName", &templateName) || !FromJSVal(cx, templateName, out.templateName)) + if (!JS_GetProperty(cx, obj, "templateName", templateName.address()) || !FromJSVal(cx, templateName, out.templateName)) FAIL("Failed to read Entity.templateName property"); - if (!JS_GetProperty(cx, obj, "id", &id) || !FromJSVal(cx, id, out.entityID)) + if (!JS_GetProperty(cx, obj, "id", id.address()) || !FromJSVal(cx, id, out.entityID)) FAIL("Failed to read Entity.id property"); - if (!JS_GetProperty(cx, obj, "position", &position) || !FromJSVal(cx, position, out.position)) + if (!JS_GetProperty(cx, obj, "position", position.address()) || !FromJSVal(cx, position, out.position)) FAIL("Failed to read Entity.position property"); - if (!JS_GetProperty(cx, obj, "rotation", &rotation) || !FromJSVal(cx, rotation, out.rotation)) + if (!JS_GetProperty(cx, obj, "rotation", rotation.address()) || !FromJSVal(cx, rotation, out.rotation)) FAIL("Failed to read Entity.rotation property"); return true; @@ -229,58 +231,49 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, Entit //////////////////////////////////////////////////////////////// // Primitive types: -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const bool& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const bool& val) { - return val ? JSVAL_TRUE : JSVAL_FALSE; + ret = val ? JSVAL_TRUE : JSVAL_FALSE; } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const float& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const float& val) { - jsval rval = JSVAL_VOID; - JS_NewNumberValue(cx, val, &rval); // ignore return value - return rval; + ret = JS::NumberValue(val); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const double& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const double& val) { - jsval rval = JSVAL_VOID; - JS_NewNumberValue(cx, val, &rval); // ignore return value - return rval; + ret = JS::NumberValue(val); } -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const i32& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const i32& val) { - cassert(JSVAL_INT_BITS == 32); - return INT_TO_JSVAL(val); + ret = JS::NumberValue(val); } -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const u16& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const u16& val) { - return INT_TO_JSVAL(val); + ret = JS::NumberValue(val); } -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const u8& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const u8& val) { - return INT_TO_JSVAL(val); + ret = JS::NumberValue(val); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const u32& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const u32& val) { - if (val <= JSVAL_INT_MAX) - return INT_TO_JSVAL(val); - jsval rval = JSVAL_VOID; - JS_NewNumberValue(cx, val, &rval); // ignore return value - return rval; + ret = JS::NumberValue(val); } -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const long& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const long& val) { - return INT_TO_JSVAL((int)val); + ret = JS::NumberValue((int)val); } -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const unsigned long& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const unsigned long& val) { - return INT_TO_JSVAL((int)val); + ret = JS::NumberValue((int)val); } // (s)size_t are considered to be identical to (unsigned) int by GCC and @@ -290,103 +283,118 @@ template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), // for some reason, x64 MSC treats size_t as distinct from unsigned long: #if MSC_VERSION && ARCH_AMD64 -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const size_t& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const size_t& val) { - return INT_TO_JSVAL((int)val); + ret = JS::NumberValue((int)val); } -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const ssize_t& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const ssize_t& val) { - return INT_TO_JSVAL((int)val); + ret = JS::NumberValue((int)val); } #endif -// NOTE: we can't define a jsval specialisation, because that conflicts with integer types -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const CScriptVal& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const CScriptVal& val) { - return val.get(); + ret = val.get(); } -template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), const CScriptValRooted& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const CScriptValRooted& val) { - return val.get(); + ret = val.get(); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const std::wstring& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const std::wstring& val) { + JSAutoRequest rq(cx); utf16string utf16(val.begin(), val.end()); JSString* str = JS_NewUCStringCopyN(cx, reinterpret_cast (utf16.c_str()), utf16.length()); if (str) - return STRING_TO_JSVAL(str); - return JSVAL_VOID; + ret = JS::StringValue(str); + else + ret = JS::UndefinedValue(); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const Path& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const Path& val) { - return ToJSVal(cx, val.string()); + ToJSVal(cx, ret, val.string()); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const std::string& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const std::string& val) { + JSAutoRequest rq(cx); JSString* str = JS_NewStringCopyN(cx, val.c_str(), val.length()); if (str) - return STRING_TO_JSVAL(str); - return JSVAL_VOID; + ret = JS::StringValue(str); + else + ret = JS::UndefinedValue(); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const wchar_t* const& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const wchar_t* const& val) { - return ToJSVal(cx, std::wstring(val)); + ToJSVal(cx, ret, std::wstring(val)); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const char* const& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const char* const& val) { + JSAutoRequest rq(cx); JSString* str = JS_NewStringCopyZ(cx, val); if (str) - return STRING_TO_JSVAL(str); - return JSVAL_VOID; + ret = JS::StringValue(str); + else + ret = JS::UndefinedValue(); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const CStrW& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const CStrW& val) { - return ToJSVal(cx, static_cast(val)); + ToJSVal(cx, ret, static_cast(val)); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const CStr8& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const CStr8& val) { - return ToJSVal(cx, static_cast(val)); + ToJSVal(cx, ret, static_cast(val)); } //////////////////////////////////////////////////////////////// // Compound types: -template static jsval ToJSVal_vector(JSContext* cx, const std::vector& val) +template static void ToJSVal_vector(JSContext* cx, JS::Value& ret, const std::vector& val) { - JSObject* obj = JS_NewArrayObject(cx, (jsint)val.size(), NULL); + JSAutoRequest rq(cx); + JSObject* obj = JS_NewArrayObject(cx, val.size(), NULL); if (!obj) - return JSVAL_VOID; - for (size_t i = 0; i < val.size(); ++i) { - jsval el = ScriptInterface::ToJSVal(cx, val[i]); - JS_SetElement(cx, obj, (jsint)i, &el); + ret = JS::UndefinedValue(); + return; } - return OBJECT_TO_JSVAL(obj); + for (u32 i = 0; i < val.size(); ++i) + { + JS::RootedValue el(cx); + ScriptInterface::ToJSVal(cx, el.get(), val[i]); + JS_SetElement(cx, obj, i, el.address()); + } + ret = JS::ObjectValue(*obj); } template static bool FromJSVal_vector(JSContext* cx, jsval v, std::vector& out) { + JSAutoRequest rq(cx); JSObject* obj; - if (!JS_ValueToObject(cx, v, &obj) || obj == NULL || !(JS_IsArrayObject(cx, obj) || js_IsTypedArray(obj))) + if (!v.isObject()) FAIL("Argument must be an array"); - jsuint length; + obj = &v.toObject(); + if (!(JS_IsArrayObject(cx, obj) || JS_IsTypedArrayObject(obj))) + FAIL("Argument must be an array"); + + u32 length; if (!JS_GetArrayLength(cx, obj, &length)) FAIL("Failed to get array length"); out.reserve(length); - for (jsuint i = 0; i < length; ++i) + for (u32 i = 0; i < length; ++i) { - jsval el; - if (!JS_GetElement(cx, obj, i, &el)) + JS::RootedValue el(cx); + if (!JS_GetElement(cx, obj, i, el.address())) FAIL("Failed to read array element"); T el2; if (!ScriptInterface::FromJSVal(cx, el, el2)) @@ -399,9 +407,9 @@ template static bool FromJSVal_vector(JSContext* cx, jsval v, std::v // Instantiate various vector types: #define VECTOR(T) \ - template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const std::vector& val) \ + template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::Value& ret, const std::vector& val) \ { \ - return ToJSVal_vector(cx, val); \ + ToJSVal_vector(cx, ret, val); \ } \ template<> bool ScriptInterface::FromJSVal >(JSContext* cx, jsval v, std::vector& out) \ { \ @@ -417,9 +425,9 @@ VECTOR(CScriptValRooted) class IComponent; -template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const std::vector& val) +template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::Value& ret, const std::vector& val) { - return ToJSVal_vector(cx, val); + ToJSVal_vector(cx, ret, val); } template<> bool ScriptInterface::FromJSVal >(JSContext* cx, jsval v, std::vector& out) diff --git a/source/scriptinterface/ScriptExtraHeaders.h b/source/scriptinterface/ScriptExtraHeaders.h index 79d6f4ea1b..c1c3b41100 100644 --- a/source/scriptinterface/ScriptExtraHeaders.h +++ b/source/scriptinterface/ScriptExtraHeaders.h @@ -25,13 +25,19 @@ #include "scriptinterface/ScriptTypes.h" // Ignore some harmless warnings -#if GCC_VERSION >= 402 // (older GCCs don't support this pragma) +// Not all of these warnings can be disabled in older versions of GCC. +// The version checking was taken from the manual here: +// http://gcc.gnu.org/onlinedocs/ +// Choose the version and navigate to "Options to Request or Suppress Warnings" +// or for some flags "Options Controlling C++ Dialect". +#if GCC_VERSION >= 402 # if GCC_VERSION >= 406 // store user flags # pragma GCC diagnostic push # endif # pragma GCC diagnostic ignored "-Wunused-parameter" # pragma GCC diagnostic ignored "-Wredundant-decls" -# if GCC_VERSION >= 408 +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +# if GCC_VERSION >= 407 # pragma GCC diagnostic ignored "-Wunused-local-typedefs" // caused by js/debug.h # endif #endif @@ -46,8 +52,9 @@ # define signbit std::signbit #endif -#include "js/jstypedarray.h" -#include "js/jsdbgapi.h" +#include "jsfriendapi.h" +#include "jsdbgapi.h" +#include "js/GCAPI.h" #undef signbit @@ -57,8 +64,10 @@ #if GCC_VERSION >= 402 # pragma GCC diagnostic warning "-Wunused-parameter" # pragma GCC diagnostic warning "-Wredundant-decls" +# pragma GCC diagnostic warning "-Wnon-virtual-dtor" # if GCC_VERSION >= 406 -# pragma GCC diagnostic pop // restore user flags +// restore user flags (and we don't have to manually restore the warning levels for GCC >= 4.6) +# pragma GCC diagnostic pop # endif #endif diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp index b8efb32a3b..a034608fc9 100644 --- a/source/scriptinterface/ScriptInterface.cpp +++ b/source/scriptinterface/ScriptInterface.cpp @@ -65,15 +65,85 @@ * (One means to share data between threads and runtimes is to create * a ScriptInterface::StructuredClone.) */ + + +void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc)) +{ + /* + * During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END + * callbacks. During an incremental GC, the sequence of callbacks is as + * follows: + * JSGC_CYCLE_BEGIN, JSGC_SLICE_END (first slice) + * JSGC_SLICE_BEGIN, JSGC_SLICE_END (second slice) + * ... + * JSGC_SLICE_BEGIN, JSGC_CYCLE_END (last slice) + */ + + + if (progress == JS::GC_SLICE_BEGIN) + { + if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) + g_Profiler.Start("GCSlice"); + g_Profiler2.RecordRegionEnter("GCSlice"); + } + else if (progress == JS::GC_SLICE_END) + { + if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) + g_Profiler.Stop(); + g_Profiler2.RecordRegionLeave("GCSlice"); + } + else if (progress == JS::GC_CYCLE_BEGIN) + { + if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) + g_Profiler.Start("GCSlice"); + g_Profiler2.RecordRegionEnter("GCSlice"); + } + else if (progress == JS::GC_CYCLE_END) + { + if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) + g_Profiler.Stop(); + g_Profiler2.RecordRegionLeave("GCSlice"); + } + + // The following code can be used to print some information aobut garbage collection + // Search for "Nonincremental reason" if there are problems running GC incrementally. + #if 0 + if (progress == JS::GCProgress::GC_CYCLE_BEGIN) + printf("starting cycle ===========================================\n"); + + const jschar* str = desc.formatMessage(rt); + int len = 0; + + for(int i = 0; i < 10000; i++) + { + len++; + if(!str[i]) + break; + } + + wchar_t outstring[len]; + + for(int i = 0; i < len; i++) + { + outstring[i] = (wchar_t)str[i]; + } + + printf("---------------------------------------\n: %ls \n---------------------------------------\n", outstring); + #endif +} + class ScriptRuntime { public: - ScriptRuntime(int runtimeSize) : - m_rooter(NULL) + ScriptRuntime(int runtimeSize): + m_rooter(NULL), + m_LastGCBytes(0) { - m_rt = JS_NewRuntime(runtimeSize); + m_rt = JS_NewRuntime(runtimeSize, JS_USE_HELPER_THREADS); + ENSURE(m_rt); // TODO: error handling + JS_SetNativeStackQuota(m_rt, 128 * sizeof(size_t) * 1024); if (g_ScriptProfilingEnabled) { // Profiler isn't thread-safe, so only enable this on the main thread @@ -86,22 +156,100 @@ public: } } } - - JS_SetExtraGCRoots(m_rt, jshook_trace, this); + + JS::SetGCSliceCallback(m_rt, GCSliceCallbackHook); + + JS_SetGCParameter(m_rt, JSGC_MAX_MALLOC_BYTES, 384 * 1024 * 1024); + JS_SetGCParameter(m_rt, JSGC_MAX_BYTES, 384 * 1024 * 1024); + JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); + //JS_SetGCParameter(m_rt, JSGC_SLICE_TIME_BUDGET, 5); + JS_SetGCParameter(m_rt, JSGC_ALLOCATION_THRESHOLD, 256); + + // The whole heap-growth mechanism seems to work only for non-incremental GCs. + // We disable it to make it more clear if full GCs happen triggered by this JSAPI internal mechanism. + JS_SetGCParameter(m_rt, JSGC_DYNAMIC_HEAP_GROWTH, false); + + JS_AddExtraGCRootsTracer(m_rt, jshook_trace, this); + + m_dummyContext = JS_NewContext(m_rt, STACK_CHUNK_SIZE); + ENSURE(m_dummyContext); + } + + void MaybeIncrementalGC() + { + PROFILE3("MaybeIncrementalGC"); + if (JS::IsIncrementalGCEnabled(m_rt)) + { + // The idea is to get the heap size after a completed GC and trigger the next GC when the heap size has + // reached m_LastGCBytes + X. + // In practice it doesn't quite work like that. When the incremental marking is completed, the sweeping kicks in. + // The sweeping actually frees memory and it does this in a background thread (if JS_USE_HELPER_THREADS is set). + // While the sweeping is happening we already run scripts again and produce new garbage. + int gcBytes = JS_GetGCParameter(m_rt, JSGC_BYTES); + //printf("gcBytes: %d \n", gcBytes); + if (m_LastGCBytes > gcBytes || m_LastGCBytes == 0) + { + //printf("Setting m_LastGCBytes: %d \n", gcBytes); // debugging + m_LastGCBytes = gcBytes; + } + + // Run an additional incremental GC slice if the currently running incremental GC isn't over yet + // ... or + // start a new incremental GC if the JS heap size has grown enough for a GC to make sense + if (JS::IsIncrementalGCInProgress(m_rt) || (gcBytes - m_LastGCBytes > 20 * 1024 * 1024)) + { + /* Use for debugging + if (JS::IsIncrementalGCInProgress(m_rt)) + printf("Running incremental garbage collection because an incremental cycle is in progress. \n"); + else + printf("Running incremental garbage collection because JSGC_BYTES - m_LastGCBytes > X ---- JSGC_BYTES: %d m_LastGCBytes: %d", gcBytes, m_LastGCBytes); + */ + PrepareContextsForIncrementalGC(); + JS::IncrementalGC(m_rt, JS::gcreason::REFRESH_FRAME, 10); + m_LastGCBytes = gcBytes; + } + } + } + + void RegisterContext(JSContext* cx) + { + m_Contexts.push_back(cx); + } + + void UnRegisterContext(JSContext* cx) + { + m_Contexts.remove(cx); } ~ScriptRuntime() { + JS_RemoveExtraGCRootsTracer(m_rt, jshook_trace, this); + JS_DestroyContext(m_dummyContext); JS_DestroyRuntime(m_rt); } - + JSRuntime* m_rt; AutoGCRooter* m_rooter; private: + + // Workaround for: https://bugzilla.mozilla.org/show_bug.cgi?id=890243 + JSContext* m_dummyContext; + + int m_LastGCBytes; + + void PrepareContextsForIncrementalGC() + { + for (std::list::iterator itr = m_Contexts.begin(); itr != m_Contexts.end(); itr++) + { + JS::PrepareZoneForGC(js::GetCompartmentZone(js::GetContextCompartment(*itr))); + } + } + + std::list m_Contexts; - static void* jshook_script(JSContext* UNUSED(cx), JSStackFrame* UNUSED(fp), JSBool before, JSBool* UNUSED(ok), void* closure) + static void* jshook_script(JSContext* UNUSED(cx), JSAbstractFramePtr UNUSED(fp), bool UNUSED(isConstructing), JSBool before, JSBool* UNUSED(ok), void* closure) { if (before) g_Profiler.StartScript("script invocation"); @@ -154,7 +302,7 @@ private: if (slash != filename.npos) filename = filename.substr(slash+1); - uintN line = JS_PCToLineNumber(cx, script, pc); + uint line = JS_PCToLineNumber(cx, script, pc); std::stringstream ss; ss << "(" << filename << ":" << line << ")"; @@ -178,7 +326,7 @@ private: boost::flyweights::no_locking > LocFlyweight; - static void* jshook_function(JSContext* cx, JSStackFrame* fp, JSBool before, JSBool* UNUSED(ok), void* closure) + static void* jshook_function(JSContext* cx, JSAbstractFramePtr fp, bool UNUSED(isConstructing), JSBool before, JSBool* UNUSED(ok), void* closure) { if (!before) { @@ -186,7 +334,7 @@ private: return closure; } - JSFunction* fn = JS_GetFrameFunction(cx, fp); + JSFunction* fn = fp.maybeFun(); if (!fn) { g_Profiler.StartScript("(function)"); @@ -207,7 +355,11 @@ private: } // No name - compute from the location instead - ScriptLocation loc = { cx, JS_GetFrameScript(cx, fp), JS_GetFramePC(cx, fp) }; + JSScript* script; + uint lineno; + JS_DescribeScriptedCaller(cx, &script, &lineno); + ENSURE(script == fp.script()); + ScriptLocation loc = { cx, fp.script(), JS_LineNumberToPC(cx, script, lineno) }; g_Profiler.StartScript(LocFlyweight(loc).get().name.c_str()); return closure; @@ -233,11 +385,13 @@ struct ScriptInterface_impl { ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& runtime); ~ScriptInterface_impl(); - void Register(const char* name, JSNative fptr, uintN nargs); + void Register(const char* name, JSNative fptr, uint nargs); shared_ptr m_runtime; JSContext* m_cx; JSObject* m_glob; // global scope object + JSCompartment* m_comp; + boost::rand48* m_rng; JSObject* m_nativeScope; // native function scope object typedef std::map ScriptValCache; @@ -249,19 +403,15 @@ namespace JSClass global_class = { "global", JSCLASS_GLOBAL_FLAGS, - JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub, - JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, - NULL, NULL, NULL, NULL, + JS_PropertyStub, JS_DeletePropertyStub, + JS_PropertyStub, JS_StrictPropertyStub, + JS_EnumerateStub, JS_ResolveStub, + JS_ConvertStub, NULL, NULL, NULL, NULL, NULL }; void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) { - // XXX Ugly hack: we want to compile code with 'use strict' and with JSOPTION_STRICT, - // but the latter causes the former to be reported as a useless expression, so - // ignore that specific warning here - if (report->flags == 5 && report->lineno == 0 && report->errorNumber == 163) - return; std::stringstream msg; bool isWarning = JSREPORT_IS_WARNING(report->flags); @@ -276,7 +426,7 @@ void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) // If there is an exception, then print its stack trace jsval excn; - if (JS_GetPendingException(cx, &excn) && JSVAL_IS_OBJECT(excn)) + if (JS_GetPendingException(cx, &excn) && excn.isObject()) { // TODO: this violates the docs ("The error reporter callback must not reenter the JSAPI.") @@ -286,7 +436,7 @@ void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) jsval rval; const char dumpStack[] = "this.stack.trimRight().replace(/^/mg, ' ')"; // indent each line - if (JS_EvaluateScript(cx, JSVAL_TO_OBJECT(excn), dumpStack, ARRAY_SIZE(dumpStack)-1, "(eval)", 1, &rval)) + if (JS_EvaluateScript(cx, &excn.toObject(), dumpStack, ARRAY_SIZE(dumpStack)-1, "(eval)", 1, &rval)) { std::string stackTrace; if (ScriptInterface::FromJSVal(cx, rval, stackTrace)) @@ -312,9 +462,9 @@ void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) // Functions in the global namespace: -JSBool print(JSContext* cx, uintN argc, jsval* vp) +JSBool print(JSContext* cx, uint argc, jsval* vp) { - for (uintN i = 0; i < argc; ++i) + for (uint i = 0; i < argc; ++i) { std::wstring str; if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[i], str)) @@ -326,7 +476,7 @@ JSBool print(JSContext* cx, uintN argc, jsval* vp) return JS_TRUE; } -JSBool logmsg(JSContext* cx, uintN argc, jsval* vp) +JSBool logmsg(JSContext* cx, uint argc, jsval* vp) { if (argc < 1) { @@ -342,7 +492,7 @@ JSBool logmsg(JSContext* cx, uintN argc, jsval* vp) return JS_TRUE; } -JSBool warn(JSContext* cx, uintN argc, jsval* vp) +JSBool warn(JSContext* cx, uint argc, jsval* vp) { if (argc < 1) { @@ -358,7 +508,7 @@ JSBool warn(JSContext* cx, uintN argc, jsval* vp) return JS_TRUE; } -JSBool error(JSContext* cx, uintN argc, jsval* vp) +JSBool error(JSContext* cx, uint argc, jsval* vp) { if (argc < 1) { @@ -374,7 +524,7 @@ JSBool error(JSContext* cx, uintN argc, jsval* vp) return JS_TRUE; } -JSBool deepcopy(JSContext* cx, uintN argc, jsval* vp) +JSBool deepcopy(JSContext* cx, uint argc, jsval* vp) { if (argc < 1) { @@ -384,21 +534,14 @@ JSBool deepcopy(JSContext* cx, uintN argc, jsval* vp) jsval ret; - // We'd usually do: - // if (!JS_StructuredClone(cx, JS_ARGV(cx, vp)[0], &ret, NULL, NULL)) - // return JS_FALSE; - // but that function is broken in the 1.8.5 release - // (https://bugzilla.mozilla.org/show_bug.cgi?id=651510) - // so do an equivalent operation with a different API: - JSAutoStructuredCloneBuffer buf; - if (!buf.write(cx, JS_ARGV(cx, vp)[0]) || !buf.read(&ret, cx)) + if (!JS_StructuredClone(cx, JS_ARGV(cx, vp)[0], &ret, NULL, NULL)) return JS_FALSE; JS_SET_RVAL(cx, vp, ret); return JS_TRUE; } -JSBool ProfileStart(JSContext* cx, uintN argc, jsval* vp) +JSBool ProfileStart(JSContext* cx, uint argc, jsval* vp) { const char* name = "(ProfileStart)"; @@ -426,7 +569,7 @@ JSBool ProfileStart(JSContext* cx, uintN argc, jsval* vp) return JS_TRUE; } -JSBool ProfileStop(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* vp) +JSBool ProfileStop(JSContext* UNUSED(cx), uint UNUSED(argc), jsval* vp) { if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) g_Profiler.Stop(); @@ -455,33 +598,37 @@ static double generate_uniform_real(boost::rand48& rng, double min, double max) } } -JSBool Math_random(JSContext* cx, uintN UNUSED(argc), jsval* vp) +JSBool Math_random(JSContext* cx, uint UNUSED(argc), jsval* vp) { - // Grab the RNG that was hidden in our slot - jsval rngp; - if (!JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), 0, &rngp)) + double r; + if(!ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->MathRandom(r)) return JS_FALSE; - boost::rand48* rng = static_cast(JSVAL_TO_PRIVATE(rngp)); - double r = generate_uniform_real(*rng, 0.0, 1.0); - - jsval rv; - if (!JS_NewNumberValue(cx, r, &rv)) - return JS_FALSE; + jsval rv = JS::NumberValue(r); JS_SET_RVAL(cx, vp, rv); return JS_TRUE; } } // anonymous namespace +bool ScriptInterface::MathRandom(double& nbr) +{ + if (m->m_rng == NULL) + return false; + nbr = generate_uniform_real(*(m->m_rng), 0.0, 1.0); + return true; +} + ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& runtime) : m_runtime(runtime) { - JSBool ok; + bool ok; m_cx = JS_NewContext(m_runtime->m_rt, STACK_CHUNK_SIZE); ENSURE(m_cx); + JS_SetParallelCompilationEnabled(m_cx, true); + // For GC debugging: // JS_SetGCZeal(m_cx, 2); @@ -489,9 +636,11 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh JS_SetErrorReporter(m_cx, ErrorReporter); - uint32 options = 0; - options |= JSOPTION_STRICT; // "warn on dubious practice" - options |= JSOPTION_XML; // "ECMAScript for XML support: parse as a token" + u32 options = 0; + options |= JSOPTION_EXTRA_WARNINGS; // "warn on dubious practice" + // We use strict mode to encourage better coding practices and + //to get code that can be optimized better by Spidermonkey's JIT compiler. + options |= JSOPTION_STRICT_MODE; options |= JSOPTION_VAROBJFIX; // "recommended" (fixes variable scoping) // Enable method JIT, unless script profiling/debugging is enabled (since profiling/debugging @@ -499,22 +648,30 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh // TODO: Verify what exactly is incompatible if (!g_ScriptProfilingEnabled && !g_JSDebuggerEnabled) { - options |= JSOPTION_METHODJIT; + options |= JSOPTION_BASELINE; + options |= JSOPTION_ION; + options |= JSOPTION_TYPE_INFERENCE; + options |= JSOPTION_COMPILE_N_GO; // Some other JIT flags to experiment with: - options |= JSOPTION_JIT; - options |= JSOPTION_PROFILING; + //options |= JSOPTION_METHODJIT_ALWAYS; } JS_SetOptions(m_cx, options); - JS_SetVersion(m_cx, JSVERSION_LATEST); - m_glob = JS_NewCompartmentAndGlobalObject(m_cx, &global_class, NULL); + JSAutoRequest rq(m_cx); + + JS::CompartmentOptions opt; + opt.setVersion(JSVERSION_LATEST); + m_glob = JS_NewGlobalObject(m_cx, &global_class, NULL, opt); + m_comp = JS_EnterCompartment(m_cx, m_glob); + JS_SetGlobalObject(m_cx, m_glob); ok = JS_InitStandardClasses(m_cx, m_glob); ENSURE(ok); - JS_DefineProperty(m_cx, m_glob, "global", OBJECT_TO_JSVAL(m_glob), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY + + JS_DefineProperty(m_cx, m_glob, "global", JS::ObjectValue(*m_glob), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY @@ -528,18 +685,26 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh Register("ProfileStart", ::ProfileStart, 1); Register("ProfileStop", ::ProfileStop, 0); + + runtime->RegisterContext(m_cx); } ScriptInterface_impl::~ScriptInterface_impl() { // Important: this must come before JS_DestroyContext because CScriptValRooted needs the context to unroot the values! - // TODO: Check again when SpiderMonkey is upgraded and when/if CScriptValRooted gets replaces by JS::Heap. - m_ScriptValCache.clear(); + m_ScriptValCache.clear(); + + m_runtime->UnRegisterContext(m_cx); + { + JSAutoRequest rq(m_cx); + JS_LeaveCompartment(m_cx, m_comp); + } JS_DestroyContext(m_cx); } -void ScriptInterface_impl::Register(const char* name, JSNative fptr, uintN nargs) +void ScriptInterface_impl::Register(const char* name, JSNative fptr, uint nargs) { + JSAutoRequest rq(m_cx); JSFunction* func = JS_DefineFunction(m_cx, m_nativeScope, name, fptr, nargs, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); if (!func) @@ -558,7 +723,7 @@ void ScriptInterface_impl::Register(const char* name, JSNative fptr, uintN nargs > LockedStringFlyweight; LockedStringFlyweight fw(name); - JS_SetReservedSlot(m_cx, JS_GetFunctionObject(func), 0, PRIVATE_TO_JSVAL((void*)fw.get().c_str())); + JS_SetReservedSlot(JS_GetFunctionObject(func), 0, PRIVATE_TO_JSVAL((void*)fw.get().c_str())); } } @@ -639,26 +804,28 @@ bool ScriptInterface::LoadGlobalScripts() return false; } } + + JSAutoRequest rq(m->m_cx); jsval proto; - if (JS_GetProperty(m->m_cx, JS_GetGlobalObject(m->m_cx), "Vector2Dprototype", &proto)) + if (JS_GetProperty(m->m_cx, m->m_glob, "Vector2Dprototype", &proto)) m->m_ScriptValCache[CACHE_VECTOR2DPROTO] = CScriptValRooted(m->m_cx, proto); - if (JS_GetProperty(m->m_cx, JS_GetGlobalObject(m->m_cx), "Vector3Dprototype", &proto)) + if (JS_GetProperty(m->m_cx, m->m_glob, "Vector3Dprototype", &proto)) m->m_ScriptValCache[CACHE_VECTOR3DPROTO] = CScriptValRooted(m->m_cx, proto); return true; } bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng) { - jsval math; - if (JS_GetProperty(m->m_cx, m->m_glob, "Math", &math) && JSVAL_IS_OBJECT(math)) + JSAutoRequest rq(m->m_cx); + JS::RootedValue math(m->m_cx); + if (JS_GetProperty(m->m_cx, m->m_glob, "Math", math.address()) && math.isObject()) { - JSFunction* random = JS_DefineFunction(m->m_cx, JSVAL_TO_OBJECT(math), "random", Math_random, 0, + JSFunction* random = JS_DefineFunction(m->m_cx, &math.toObject(), "random", Math_random, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); if (random) { - // Store the RNG in a slot which is sort-of-guaranteed to be unused by the JS engine - if (JS_SetReservedSlot(m->m_cx, JS_GetFunctionObject(random), 0, PRIVATE_TO_JSVAL(&rng))) - return true; + m->m_rng = &rng; + return true; } } @@ -668,7 +835,7 @@ bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng) void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs) { - m->Register(name, fptr, (uintN)nargs); + m->Register(name, fptr, (uint)nargs); } JSContext* ScriptInterface::GetContext() const @@ -693,56 +860,68 @@ AutoGCRooter* ScriptInterface::ReplaceAutoGCRooter(AutoGCRooter* rooter) return ret; } - -jsval ScriptInterface::CallConstructor(jsval ctor, jsval arg) +jsval ScriptInterface::CallConstructor(jsval ctor, uint argc, jsval argv) { - if (!JSVAL_IS_OBJECT(ctor)) + JSAutoRequest rq(m->m_cx); + if (!ctor.isObject()) { LOGERROR(L"CallConstructor: ctor is not an object"); - return JSVAL_VOID; + return JS::UndefinedValue(); } - return OBJECT_TO_JSVAL(JS_New(m->m_cx, JSVAL_TO_OBJECT(ctor), 1, &arg)); + // Passing argc 0 and argv JSVAL_VOID causes a crash in mozjs24 + if (argc == 0) + return JS::ObjectValue(*JS_New(m->m_cx, &ctor.toObject(), 0, NULL)); + else + return JS::ObjectValue(*JS_New(m->m_cx, &ctor.toObject(), argc, &argv)); } jsval ScriptInterface::NewObjectFromConstructor(jsval ctor) { + JSAutoRequest rq(m->m_cx); + + if (!ctor.isObject()) + { + LOGERROR(L"NewObjectFromConstructor: ctor is not an object"); + return JS::UndefinedValue(); + } // Get the constructor's prototype // (Can't use JS_GetPrototype, since we want .prototype not .__proto__) - jsval protoVal; - if (!JS_GetProperty(m->m_cx, JSVAL_TO_OBJECT(ctor), "prototype", &protoVal)) + JS::RootedValue protoVal(m->m_cx); + if (!JS_GetProperty(m->m_cx, &ctor.toObject(), "prototype", protoVal.address())) { LOGERROR(L"NewObjectFromConstructor: can't get prototype"); - return JSVAL_VOID; + return JS::UndefinedValue(); } - if (!JSVAL_IS_OBJECT(protoVal)) + if (!protoVal.isObject()) { LOGERROR(L"NewObjectFromConstructor: prototype is not an object"); - return JSVAL_VOID; + return JS::UndefinedValue(); } - JSObject* proto = JSVAL_TO_OBJECT(protoVal); - JSObject* parent = JS_GetParent(m->m_cx, JSVAL_TO_OBJECT(ctor)); - // TODO: rooting? + JSObject* proto = &protoVal.toObject(); + JSObject* parent = JS_GetParent(&ctor.toObject()); + if (!proto || !parent) { LOGERROR(L"NewObjectFromConstructor: null proto/parent"); - return JSVAL_VOID; + return JS::UndefinedValue(); } JSObject* obj = JS_NewObject(m->m_cx, NULL, proto, parent); if (!obj) { LOGERROR(L"NewObjectFromConstructor: object creation failed"); - return JSVAL_VOID; + return JS::UndefinedValue(); } - return OBJECT_TO_JSVAL(obj); + return JS::ObjectValue(*obj); } void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs) { + JSAutoRequest rq(m->m_cx); std::string typeName = clasp->name; if (m_CustomObjectTypes.find(typeName) != m_CustomObjectTypes.end()) @@ -751,7 +930,7 @@ void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructo throw PSERROR_Scripting_DefineType_AlreadyExists(); } - JSObject * obj = JS_InitClass( m->m_cx, JSVAL_TO_OBJECT(GetGlobalObject()), 0, + JSObject * obj = JS_InitClass( m->m_cx, &GetGlobalObject().toObject(), 0, clasp, constructor, minArgs, // Constructor, min args ps, fs, // Properties, methods @@ -788,26 +967,28 @@ bool ScriptInterface::CallFunctionVoid(jsval val, const char* name) return CallFunction_(val, name, 0, NULL, jsRet); } -bool ScriptInterface::CallFunction_(jsval val, const char* name, size_t argc, jsval* argv, jsval& ret) +bool ScriptInterface::CallFunction_(jsval val, const char* name, uint argc, jsval* argv, jsval& ret) { - JSObject* obj; - if (!JS_ValueToObject(m->m_cx, val, &obj) || obj == NULL) + JSAutoRequest rq(m->m_cx); + JS::RootedObject obj(m->m_cx); + if (!JS_ValueToObject(m->m_cx, val, obj.address()) || !obj) return false; - + // Check that the named function actually exists, to avoid ugly JS error reports // when calling an undefined value JSBool found; if (!JS_HasProperty(m->m_cx, obj, name, &found) || !found) - return false; + return JS_FALSE; - JSBool ok = JS_CallFunctionName(m->m_cx, obj, name, (uintN)argc, argv, &ret); + bool ok = JS_CallFunctionName(m->m_cx, obj, name, argc, argv, &ret); - return ok ? true : false; + return ok; } jsval ScriptInterface::GetGlobalObject() { - return OBJECT_TO_JSVAL(JS_GetGlobalObject(m->m_cx)); + JSAutoRequest rq(m->m_cx); + return JS::ObjectValue(*JS_GetGlobalForScopeChain(m->m_cx)); } JSClass* ScriptInterface::GetGlobalClass() @@ -817,34 +998,45 @@ JSClass* ScriptInterface::GetGlobalClass() bool ScriptInterface::SetGlobal_(const char* name, jsval value, bool replace) { + JSAutoRequest rq(m->m_cx); if (!replace) { JSBool found; if (!JS_HasProperty(m->m_cx, m->m_glob, name, &found)) - return false; + return JS_FALSE; if (found) { JS_ReportError(m->m_cx, "SetGlobal \"%s\" called multiple times", name); - return false; + return JS_FALSE; } } - JSBool ok = JS_DefineProperty(m->m_cx, m->m_glob, name, value, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY - | JSPROP_PERMANENT); - return ok ? true : false; + bool ok = JS_DefineProperty(m->m_cx, m->m_glob, name, value, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY + | JSPROP_PERMANENT); + return ok; +} + +bool ScriptInterface::GetPropertyJS(jsval obj, const char* name, JS::MutableHandleValue out) +{ + JSContext* cx = GetContext(); + JSAutoRequest rq(cx); + if (! GetProperty_(obj, name, out)) + return false; + return true; } bool ScriptInterface::SetProperty_(jsval obj, const char* name, jsval value, bool constant, bool enumerate) { - uintN attrs = 0; + JSAutoRequest rq(m->m_cx); + uint attrs = 0; if (constant) attrs |= JSPROP_READONLY | JSPROP_PERMANENT; if (enumerate) attrs |= JSPROP_ENUMERATE; - if (! JSVAL_IS_OBJECT(obj)) + if (!obj.isObject()) return false; - JSObject* object = JSVAL_TO_OBJECT(obj); + JS::RootedObject object(m->m_cx, &obj.toObject()); if (! JS_DefineProperty(m->m_cx, object, name, value, NULL, NULL, attrs)) return false; @@ -853,15 +1045,16 @@ bool ScriptInterface::SetProperty_(jsval obj, const char* name, jsval value, boo bool ScriptInterface::SetProperty_(jsval obj, const wchar_t* name, jsval value, bool constant, bool enumerate) { - uintN attrs = 0; + JSAutoRequest rq(m->m_cx); + uint attrs = 0; if (constant) attrs |= JSPROP_READONLY | JSPROP_PERMANENT; if (enumerate) attrs |= JSPROP_ENUMERATE; - if (! JSVAL_IS_OBJECT(obj)) + if (!obj.isObject()) return false; - JSObject* object = JSVAL_TO_OBJECT(obj); + JS::RootedObject object(m->m_cx, &obj.toObject()); utf16string name16(name, name + wcslen(name)); if (! JS_DefineUCProperty(m->m_cx, object, reinterpret_cast(name16.c_str()), name16.length(), value, NULL, NULL, attrs)) @@ -871,152 +1064,173 @@ bool ScriptInterface::SetProperty_(jsval obj, const wchar_t* name, jsval value, bool ScriptInterface::SetPropertyInt_(jsval obj, int name, jsval value, bool constant, bool enumerate) { - uintN attrs = 0; + JSAutoRequest rq(m->m_cx); + uint attrs = 0; if (constant) attrs |= JSPROP_READONLY | JSPROP_PERMANENT; if (enumerate) attrs |= JSPROP_ENUMERATE; - if (! JSVAL_IS_OBJECT(obj)) + if (!obj.isObject()) return false; - JSObject* object = JSVAL_TO_OBJECT(obj); + JS::RootedObject object(m->m_cx, &obj.toObject()); if (! JS_DefinePropertyById(m->m_cx, object, INT_TO_JSID(name), value, NULL, NULL, attrs)) return false; return true; } -bool ScriptInterface::GetProperty_(jsval obj, const char* name, jsval& out) +bool ScriptInterface::GetProperty_(jsval obj, const char* name, JS::MutableHandleValue out) { - if (! JSVAL_IS_OBJECT(obj)) + JSAutoRequest rq(m->m_cx); + if (!obj.isObject()) return false; - JSObject* object = JSVAL_TO_OBJECT(obj); - - if (!JS_GetProperty(m->m_cx, object, name, &out)) + JS::RootedObject object(m->m_cx, &obj.toObject()); + + if (!JS_GetProperty(m->m_cx, object, name, out.address())) return false; return true; } -bool ScriptInterface::GetPropertyInt_(jsval obj, int name, jsval& out) +bool ScriptInterface::GetPropertyInt_(jsval obj, int name, JS::MutableHandleValue out) { - if (! JSVAL_IS_OBJECT(obj)) + JSAutoRequest rq(m->m_cx); + if (!obj.isObject()) return false; - JSObject* object = JSVAL_TO_OBJECT(obj); - - if (!JS_GetPropertyById(m->m_cx, object, INT_TO_JSID(name), &out)) + JS::RootedObject object(m->m_cx, &obj.toObject()); + + if (!JS_GetPropertyById(m->m_cx, object, INT_TO_JSID(name), out.address())) return false; return true; } bool ScriptInterface::HasProperty(jsval obj, const char* name) { - if (! JSVAL_IS_OBJECT(obj)) - return false; - JSObject* object = JSVAL_TO_OBJECT(obj); + // TODO: proper errorhandling + JSAutoRequest rq(m->m_cx); + if (!obj.isObject()) + return JS_FALSE; + JS::RootedObject object(m->m_cx, &obj.toObject()); JSBool found; if (!JS_HasProperty(m->m_cx, object, name, &found)) - return false; - return (found != JS_FALSE); + return JS_FALSE; + return found; } -bool ScriptInterface::EnumeratePropertyNamesWithPrefix(jsval obj, const char* prefix, std::vector& out) +bool ScriptInterface::EnumeratePropertyNamesWithPrefix(JS::HandleValue objVal, const char* prefix, std::vector& out) { - utf16string prefix16 (prefix, prefix+strlen(prefix)); - - if (! JSVAL_IS_OBJECT(obj)) - return false; // TODO: log error messages - - JSObject* it = JS_NewPropertyIterator(m->m_cx, JSVAL_TO_OBJECT(obj)); + JSAutoRequest rq(m->m_cx); + + if (!objVal.isObjectOrNull()) + { + LOGERROR(L"EnumeratePropertyNamesWithPrefix expected object type!"); + return false; + } + + if(objVal.isNull()) + return true; // reached the end of the prototype chain + + JS::RootedObject obj(m->m_cx, &objVal.toObject()); + JS::RootedObject it(m->m_cx, JS_NewPropertyIterator(m->m_cx, obj)); if (!it) return false; while (true) { - jsid idp; - jsval val; - if (! JS_NextProperty(m->m_cx, it, &idp) || ! JS_IdToValue(m->m_cx, idp, &val)) + JS::RootedId idp(m->m_cx); + JS::RootedValue val(m->m_cx); + if (! JS_NextProperty(m->m_cx, it, idp.address()) || ! JS_IdToValue(m->m_cx, idp, val.address())) return false; - if (val == JSVAL_VOID) + + if (val.isUndefined()) break; // end of iteration - if (! JSVAL_IS_STRING(val)) + if (!val.isString()) continue; // ignore integer properties - JSString* name = JSVAL_TO_STRING(val); - size_t len; - const jschar* chars = JS_GetStringCharsAndLength(m->m_cx, name, &len); - if (chars && len >= prefix16.size() && memcmp(chars, prefix16.c_str(), prefix16.size()*2) == 0) - out.push_back(std::string(chars, chars+len)); // handles Unicode poorly + JS::RootedString name(m->m_cx, val.toString()); + size_t len = strlen(prefix)+1; + std::vector buf(len); + size_t prefixLen = strlen(prefix) * sizeof(char); + JS_EncodeStringToBuffer(m->m_cx, name, &buf[0], prefixLen); + buf[len-1]= '\0'; + if(0 == strcmp(&buf[0], prefix)) + { + size_t len; + const jschar* chars = JS_GetStringCharsAndLength(m->m_cx, name, &len); + out.push_back(std::string(chars, chars+len)); + } } // Recurse up the prototype chain - JSObject* prototype = JS_GetPrototype(m->m_cx, JSVAL_TO_OBJECT(obj)); - if (prototype) + JS::RootedObject prototype(m->m_cx); + if (JS_GetPrototype(m->m_cx, obj, prototype.address())) { - if (! EnumeratePropertyNamesWithPrefix(OBJECT_TO_JSVAL(prototype), prefix, out)) + JS::RootedValue prototypeVal(m->m_cx, JS::ObjectOrNullValue(prototype)); + if (! EnumeratePropertyNamesWithPrefix(prototypeVal, prefix, out)) return false; } return true; } -bool ScriptInterface::SetPrototype(jsval obj, jsval proto) +bool ScriptInterface::SetPrototype(JS::HandleValue obj, JS::HandleValue proto) { - if (!JSVAL_IS_OBJECT(obj) || !JSVAL_IS_OBJECT(proto)) + JSAutoRequest rq(m->m_cx); + if (!obj.isObject() || !proto.isObject()) return false; - return JS_SetPrototype(m->m_cx, JSVAL_TO_OBJECT(obj), JSVAL_TO_OBJECT(proto)) ? true : false; + return JS_SetPrototype(m->m_cx, &obj.toObject(), &proto.toObject()); } bool ScriptInterface::FreezeObject(jsval obj, bool deep) { - if (!JSVAL_IS_OBJECT(obj)) + JSAutoRequest rq(m->m_cx); + if (!obj.isObject()) return false; if (deep) - return JS_DeepFreezeObject(m->m_cx, JSVAL_TO_OBJECT(obj)) ? true : false; + return JS_DeepFreezeObject(m->m_cx, &obj.toObject()); else - return JS_FreezeObject(m->m_cx, JSVAL_TO_OBJECT(obj)) ? true : false; + return JS_FreezeObject(m->m_cx, &obj.toObject()); } bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code) { - // Compile the code in strict mode, to encourage better coding practices and - // to possibly help SpiderMonkey with optimisations - std::wstring codeStrict = L"\"use strict\";\n" + wstring_from_utf8(code); - utf16string codeUtf16(codeStrict.begin(), codeStrict.end()); - uintN lineNo = 0; // put the automatic 'use strict' on line 0, so the real code starts at line 1 - - JSFunction* func = JS_CompileUCFunction(m->m_cx, NULL, NULL, 0, NULL, - reinterpret_cast (codeUtf16.c_str()), (uintN)(codeUtf16.length()), - utf8_from_wstring(filename.string()).c_str(), lineNo); + JSAutoRequest rq(m->m_cx); + utf16string codeUtf16(code.begin(), code.end()); + uint lineNo = 1; + JS::RootedFunction func(m->m_cx, + JS_CompileUCFunction(m->m_cx, m->m_glob, utf8_from_wstring(filename.string()).c_str(), 0, NULL, + reinterpret_cast (codeUtf16.c_str()), (uint)(codeUtf16.length()), + utf8_from_wstring(filename.string()).c_str(), lineNo) + ); if (!func) return false; - jsval scriptRval; - JSBool ok = JS_CallFunction(m->m_cx, NULL, func, 0, NULL, &scriptRval); + JS::RootedValue val(m->m_cx); + bool ok = JS_CallFunction(m->m_cx, NULL, func, 0, NULL, val.address()); - return ok ? true : false; + return ok; } bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::wstring& code) { - // Compile the code in strict mode, to encourage better coding practices and - // to possibly help SpiderMonkey with optimisations - std::wstring codeStrict = L"\"use strict\";\n" + code; - utf16string codeUtf16(codeStrict.begin(), codeStrict.end()); - uintN lineNo = 0; // put the automatic 'use strict' on line 0, so the real code starts at line 1 + JSAutoRequest rq(m->m_cx); + utf16string codeUtf16(code.begin(), code.end()); + uint lineNo = 1; jsval rval; - JSBool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, - reinterpret_cast (codeUtf16.c_str()), (uintN)(codeUtf16.length()), + bool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, + reinterpret_cast (codeUtf16.c_str()), (uint)(codeUtf16.length()), utf8_from_wstring(filename.string()).c_str(), lineNo, &rval); - return ok ? true : false; + return ok; } bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) { + JSAutoRequest rq(m->m_cx); if (!VfsFileExists(path)) { LOGERROR(L"File '%ls' does not exist", path.string().c_str()); @@ -1035,18 +1249,15 @@ bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) std::wstring code = wstring_from_utf8(file.DecodeUTF8()); // assume it's UTF-8 - // Compile the code in strict mode, to encourage better coding practices and - // to possibly help SpiderMonkey with optimisations - std::wstring codeStrict = L"\"use strict\";\n" + code; - utf16string codeUtf16(codeStrict.begin(), codeStrict.end()); - uintN lineNo = 0; // put the automatic 'use strict' on line 0, so the real code starts at line 1 + utf16string codeUtf16(code.begin(), code.end()); + uint lineNo = 1; jsval rval; - JSBool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, - reinterpret_cast (codeUtf16.c_str()), (uintN)(codeUtf16.length()), + bool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, + reinterpret_cast (codeUtf16.c_str()), (uint)(codeUtf16.length()), utf8_from_wstring(path.string()).c_str(), lineNo, &rval); - return ok ? true : false; + return ok; } bool ScriptInterface::Eval(const char* code) @@ -1057,45 +1268,30 @@ bool ScriptInterface::Eval(const char* code) bool ScriptInterface::Eval_(const char* code, jsval& rval) { + JSAutoRequest rq(m->m_cx); utf16string codeUtf16(code, code+strlen(code)); - JSBool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, (const jschar*)codeUtf16.c_str(), (uintN)codeUtf16.length(), "(eval)", 1, &rval); - return ok ? true : false; + bool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, (const jschar*)codeUtf16.c_str(), (uint)codeUtf16.length(), "(eval)", 1, &rval); + return ok; } bool ScriptInterface::Eval_(const wchar_t* code, jsval& rval) { + JSAutoRequest rq(m->m_cx); utf16string codeUtf16(code, code+wcslen(code)); - JSBool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, (const jschar*)codeUtf16.c_str(), (uintN)codeUtf16.length(), "(eval)", 1, &rval); - return ok ? true : false; + bool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, (const jschar*)codeUtf16.c_str(), (uint)codeUtf16.length(), "(eval)", 1, &rval); + return ok; } CScriptValRooted ScriptInterface::ParseJSON(const std::string& string_utf8) { + JSAutoRequest rq(m->m_cx); std::wstring attrsW = wstring_from_utf8(string_utf8); - utf16string string(attrsW.begin(), attrsW.end()); - - jsval vp; - JSONParser* parser = JS_BeginJSONParse(m->m_cx, &vp); - if (!parser) - { - LOGERROR(L"ParseJSON failed to begin"); - return CScriptValRooted(); - } - - if (!JS_ConsumeJSONText(m->m_cx, parser, reinterpret_cast(string.c_str()), (uint32)string.size())) - { - LOGERROR(L"ParseJSON failed to consume"); - return CScriptValRooted(); - } - - if (!JS_FinishJSONParse(m->m_cx, parser, JSVAL_NULL)) - { - LOGERROR(L"ParseJSON failed to finish"); - return CScriptValRooted(); - } - + utf16string string(attrsW.begin(), attrsW.end()); + JS::RootedValue vp(m->m_cx); + if (!JS_ParseJSON(m->m_cx, reinterpret_cast(string.c_str()), (u32)string.size(), &vp)) + LOGERROR(L"JS_ParseJSON failed!"); return CScriptValRooted(m->m_cx, vp); } @@ -1124,7 +1320,7 @@ CScriptValRooted ScriptInterface::ReadJSONFile(const VfsPath& path) struct Stringifier { - static JSBool callback(const jschar* buf, uint32 len, void* data) + static JSBool callback(const jschar* buf, u32 len, void* data) { utf16string str(buf, buf+len); std::wstring strw(str.begin(), str.end()); @@ -1139,7 +1335,7 @@ struct Stringifier struct StringifierW { - static JSBool callback(const jschar* buf, uint32 len, void* data) + static JSBool callback(const jschar* buf, u32 len, void* data) { utf16string str(buf, buf+len); static_cast(data)->stream << std::wstring(str.begin(), str.end()); @@ -1151,6 +1347,7 @@ struct StringifierW std::string ScriptInterface::StringifyJSON(jsval obj, bool indent) { + JSAutoRequest rq(m->m_cx); Stringifier str; if (!JS_Stringify(m->m_cx, &obj, NULL, indent ? INT_TO_JSVAL(2) : JSVAL_VOID, &Stringifier::callback, &str)) { @@ -1166,6 +1363,7 @@ std::string ScriptInterface::StringifyJSON(jsval obj, bool indent) std::wstring ScriptInterface::ToString(jsval obj, bool pretty) { + JSAutoRequest rq(m->m_cx); if (JSVAL_IS_VOID(obj)) return L"(void 0)"; @@ -1178,7 +1376,7 @@ std::wstring ScriptInterface::ToString(jsval obj, bool pretty) // Temporary disable the error reporter, so we don't print complaints about cyclic values JSErrorReporter er = JS_SetErrorReporter(m->m_cx, NULL); - bool ok = JS_Stringify(m->m_cx, &obj, NULL, INT_TO_JSVAL(2), &StringifierW::callback, &str) == JS_TRUE; + JSBool ok = JS_Stringify(m->m_cx, &obj, NULL, INT_TO_JSVAL(2), &StringifierW::callback, &str); // Restore error reporter JS_SetErrorReporter(m->m_cx, er); @@ -1200,6 +1398,7 @@ std::wstring ScriptInterface::ToString(jsval obj, bool pretty) void ScriptInterface::ReportError(const char* msg) { + JSAutoRequest rq(m->m_cx); // JS_ReportError by itself doesn't seem to set a JS-style exception, and so // script callers will be unable to catch anything. So use JS_SetPendingException // to make sure there really is a script-level exception. But just set it to undefined @@ -1213,184 +1412,78 @@ void ScriptInterface::ReportError(const char* msg) bool ScriptInterface::IsExceptionPending(JSContext* cx) { + JSAutoRequest rq(cx); return JS_IsExceptionPending(cx) ? true : false; } -JSClass* ScriptInterface::GetClass(JSContext* cx, JSObject* obj) +JSClass* ScriptInterface::GetClass(JSObject* obj) { - UNUSED2(cx); // unused if not JS_THREADSAFE - - return JS_GET_CLASS(cx, obj); + return JS_GetClass(obj); } -void* ScriptInterface::GetPrivate(JSContext* cx, JSObject* obj) +void* ScriptInterface::GetPrivate(JSObject* obj) { // TODO: use JS_GetInstancePrivate - return JS_GetPrivate(cx, obj); + return JS_GetPrivate(obj); } void ScriptInterface::DumpHeap() { #if MOZJS_DEBUG_ABI - JS_DumpHeap(m->m_cx, stderr, NULL, 0, NULL, (size_t)-1, NULL); + JS_DumpHeap(GetJSRuntime(), stderr, NULL, JSTRACE_OBJECT, NULL, (size_t)-1, NULL); #endif fprintf(stderr, "# Bytes allocated: %u\n", JS_GetGCParameter(GetJSRuntime(), JSGC_BYTES)); - JS_GC(m->m_cx); + JS_GC(GetJSRuntime()); fprintf(stderr, "# Bytes allocated after GC: %u\n", JS_GetGCParameter(GetJSRuntime(), JSGC_BYTES)); } +void ScriptInterface::MaybeIncrementalRuntimeGC() +{ + m->m_runtime->MaybeIncrementalGC(); +} + void ScriptInterface::MaybeGC() { JS_MaybeGC(m->m_cx); } -class ValueCloner +void ScriptInterface::ForceGC() { -public: - ValueCloner(ScriptInterface& from, ScriptInterface& to) : - scriptInterfaceFrom(from), cxFrom(from.GetContext()), cxTo(to.GetContext()), m_RooterFrom(from), m_RooterTo(to) - { - } - - // Return the cloned object (or an already-computed object if we've cloned val before) - jsval GetOrClone(jsval val) - { - if (!JSVAL_IS_GCTHING(val) || JSVAL_IS_NULL(val)) - return val; - - std::map::iterator it = m_Mapping.find(JSVAL_TO_GCTHING(val)); - if (it != m_Mapping.end()) - return it->second; - - m_RooterFrom.Push(val); // root it so our mapping doesn't get invalidated - - return Clone(val); - } - -private: - -#define CLONE_REQUIRE(expr, msg) if (!(expr)) { debug_warn(L"Internal error in CloneValueFromOtherContext: " msg); return JSVAL_VOID; } - - // Clone a new value (and root it and add it to the mapping) - jsval Clone(jsval val) - { - if (JSVAL_IS_DOUBLE(val)) - { - jsval rval; - CLONE_REQUIRE(JS_NewNumberValue(cxTo, JSVAL_TO_DOUBLE(val), &rval), L"JS_NewNumberValue"); - m_RooterTo.Push(rval); - return rval; - } - - if (JSVAL_IS_STRING(val)) - { - size_t len; - const jschar* chars = JS_GetStringCharsAndLength(cxFrom, JSVAL_TO_STRING(val), &len); - CLONE_REQUIRE(chars, L"JS_GetStringCharsAndLength"); - JSString* str = JS_NewUCStringCopyN(cxTo, chars, len); - CLONE_REQUIRE(str, L"JS_NewUCStringCopyN"); - jsval rval = STRING_TO_JSVAL(str); - m_Mapping[JSVAL_TO_GCTHING(val)] = rval; - m_RooterTo.Push(rval); - return rval; - } - - ENSURE(JSVAL_IS_OBJECT(val)); - - JSObject* newObj; - if (JS_IsArrayObject(cxFrom, JSVAL_TO_OBJECT(val))) - { - jsuint length; - CLONE_REQUIRE(JS_GetArrayLength(cxFrom, JSVAL_TO_OBJECT(val), &length), L"JS_GetArrayLength"); - newObj = JS_NewArrayObject(cxTo, length, NULL); - CLONE_REQUIRE(newObj, L"JS_NewArrayObject"); - } - else - { - newObj = JS_NewObject(cxTo, NULL, NULL, NULL); - CLONE_REQUIRE(newObj, L"JS_NewObject"); - } - - m_Mapping[JSVAL_TO_GCTHING(val)] = OBJECT_TO_JSVAL(newObj); - m_RooterTo.Push(newObj); - - AutoJSIdArray ida (cxFrom, JS_Enumerate(cxFrom, JSVAL_TO_OBJECT(val))); - CLONE_REQUIRE(ida.get(), L"JS_Enumerate"); - - AutoGCRooter idaRooter(scriptInterfaceFrom); - idaRooter.Push(ida.get()); - - for (size_t i = 0; i < ida.length(); ++i) - { - jsid id = ida[i]; - jsval idval, propval; - CLONE_REQUIRE(JS_IdToValue(cxFrom, id, &idval), L"JS_IdToValue"); - CLONE_REQUIRE(JS_GetPropertyById(cxFrom, JSVAL_TO_OBJECT(val), id, &propval), L"JS_GetPropertyById"); - jsval newPropval = GetOrClone(propval); - - if (JSVAL_IS_INT(idval)) - { - // int jsids are portable across runtimes - CLONE_REQUIRE(JS_SetPropertyById(cxTo, newObj, id, &newPropval), L"JS_SetPropertyById"); - } - else if (JSVAL_IS_STRING(idval)) - { - // string jsids are runtime-specific, so we need to copy the string content - JSString* idstr = JS_ValueToString(cxFrom, idval); - CLONE_REQUIRE(idstr, L"JS_ValueToString (id)"); - size_t len; - const jschar* chars = JS_GetStringCharsAndLength(cxFrom, idstr, &len); - CLONE_REQUIRE(idstr, L"JS_GetStringCharsAndLength (id)"); - CLONE_REQUIRE(JS_SetUCProperty(cxTo, newObj, chars, len, &newPropval), L"JS_SetUCProperty"); - } - else - { - // this apparently could be an XML object; ignore it - } - } - - return OBJECT_TO_JSVAL(newObj); - } - - ScriptInterface& scriptInterfaceFrom; - JSContext* cxFrom; - JSContext* cxTo; - std::map m_Mapping; - AutoGCRooter m_RooterFrom; - AutoGCRooter m_RooterTo; -}; + PROFILE2("JS_GC"); + JS_GC(this->GetJSRuntime()); +} jsval ScriptInterface::CloneValueFromOtherContext(ScriptInterface& otherContext, jsval val) { PROFILE("CloneValueFromOtherContext"); - - ValueCloner cloner(otherContext, *this); - return cloner.GetOrClone(val); + shared_ptr structuredClone = otherContext.WriteStructuredClone(val); + jsval clone = ReadStructuredClone(structuredClone); + return clone; } ScriptInterface::StructuredClone::StructuredClone() : - m_Context(NULL), m_Data(NULL), m_Size(0) + m_Data(NULL), m_Size(0) { } ScriptInterface::StructuredClone::~StructuredClone() { if (m_Data) - JS_free(m_Context, m_Data); + JS_ClearStructuredClone(m_Data, m_Size); } shared_ptr ScriptInterface::WriteStructuredClone(jsval v) { - uint64* data = NULL; + JSAutoRequest rq(m->m_cx); + u64* data = NULL; size_t nbytes = 0; - if (!JS_WriteStructuredClone(m->m_cx, v, &data, &nbytes, NULL, NULL)) + if (!JS_WriteStructuredClone(m->m_cx, v, &data, &nbytes, NULL, NULL, JSVAL_VOID)) { debug_warn(L"Writing a structured clone with JS_WriteStructuredClone failed!"); return shared_ptr(); } shared_ptr ret (new StructuredClone); - ret->m_Context = m->m_cx; ret->m_Data = data; ret->m_Size = nbytes; return ret; @@ -1398,6 +1491,7 @@ shared_ptr ScriptInterface::WriteStructuredClo jsval ScriptInterface::ReadStructuredClone(const shared_ptr& ptr) { + JSAutoRequest rq(m->m_cx); jsval ret = JSVAL_VOID; JS_ReadStructuredClone(m->m_cx, ptr->m_Data, ptr->m_Size, JS_STRUCTURED_CLONE_VERSION, &ret, NULL, NULL); return ret; diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h index 8aa569a28f..1b5e3330fd 100644 --- a/source/scriptinterface/ScriptInterface.h +++ b/source/scriptinterface/ScriptInterface.h @@ -25,8 +25,6 @@ #include "ScriptTypes.h" #include "ScriptVal.h" -#include "js/jsapi.h" - #include "ps/Errors.h" ERROR_GROUP(Scripting); ERROR_TYPE(Scripting, SetupFailed); @@ -140,7 +138,7 @@ public: * Call a constructor function, equivalent to JS "new ctor(arg)". * @return The new object; or JSVAL_VOID on failure, and logs an error message */ - jsval CallConstructor(jsval ctor, jsval arg); + jsval CallConstructor(jsval ctor, uint argc, jsval argv); /** * Create an object as with CallConstructor except don't actually execute the @@ -243,6 +241,14 @@ public: */ template bool GetProperty(jsval obj, const char* name, T& out); + + /** + * This function overload is used for JS::MutableHandleValue type. + * If we use JS::RootedValue with the GetProperty function template, it will generate an overload for the type + * JS::RootedValue*, but JS::MutableHandleValue needs to be used when passing JS::RootedValue& to a function. + * Check the SpiderMonkey rooting guide for details. + */ + bool GetPropertyJS(jsval obj, const char* name, JS::MutableHandleValue out); /** * Get the integer-named property on the given object. @@ -255,9 +261,9 @@ public: */ bool HasProperty(jsval obj, const char* name); - bool EnumeratePropertyNamesWithPrefix(jsval obj, const char* prefix, std::vector& out); + bool EnumeratePropertyNamesWithPrefix(JS::HandleValue objVal, const char* prefix, std::vector& out); - bool SetPrototype(jsval obj, jsval proto); + bool SetPrototype(JS::HandleValue obj, JS::HandleValue proto); bool FreezeObject(jsval obj, bool deep); @@ -327,8 +333,11 @@ public: /** * Convert a C++ type to a jsval. (This might trigger GC. The return * value must be rooted if you don't want it to be collected.) + * NOTE: We are passing the JS::Value by reference instead of returning it by value. + * The reason is a memory corruption problem that appears to be caused by a bug in Visual Studio. + * Details here: http://www.wildfiregames.com/forum/index.php?showtopic=17289&p=285921 */ - template static jsval ToJSVal(JSContext* cx, T const& val); + template static void ToJSVal(JSContext* cx, JS::Value& ret, T const& val); AutoGCRooter* ReplaceAutoGCRooter(AutoGCRooter* rooter); @@ -338,9 +347,34 @@ public: void DumpHeap(); /** - * MaybeGC tries to determine whether garbage collection in cx's runtime would free up enough memory to be worth the amount of time it would take + * MaybeGC tries to determine whether garbage collection in cx's runtime would free up enough memory to be worth the amount of time it would take. + * This calls JS_MaybeGC directly, which does not do incremental GC. Usually you should prefer MaybeIncrementalRuntimeGC. */ void MaybeGC(); + + /** + * MaybeIncrementalRuntimeGC tries to determine whether a runtime-wide garbage collection would free up enough memory to + * be worth the amount of time it would take. It does this with our own logic and NOT some predefined JSAPI logic because + * such functionality currently isn't available out of the box. + * It does incremental GC which means it will collect one slice each time it's called until the garbage collection is done. + * This can and should be called quite regularly. It shouldn't cost much performance because it tries to run a GC only if + * necessary. + */ + void MaybeIncrementalRuntimeGC(); + + /** + * Triggers a full non-incremental garbage collection immediately. That should only be required in special cases and normally + * you should try to use MaybeIncrementalRuntimeGC instead. + */ + void ForceGC(); + + /** + * MathRandom (this function) calls the random number generator assigned to this ScriptInterface instance and + * returns the generated number. + * Math_random (with underscore, not this function) is a global function, but different random number generators can be + * stored per ScriptInterface. It calls MathRandom of the current ScriptInterface instance. + */ + bool MathRandom(double& nbr); /** * Structured clones are a way to serialize 'simple' JS values into a buffer @@ -355,8 +389,7 @@ public: public: StructuredClone(); ~StructuredClone(); - JSContext* m_Context; - uint64* m_Data; + u64* m_Data; size_t m_Size; }; @@ -364,18 +397,18 @@ public: jsval ReadStructuredClone(const shared_ptr& ptr); private: - bool CallFunction_(jsval val, const char* name, size_t argc, jsval* argv, jsval& ret); + bool CallFunction_(jsval val, const char* name, uint argc, jsval* argv, jsval& ret); bool Eval_(const char* code, jsval& ret); bool Eval_(const wchar_t* code, jsval& ret); bool SetGlobal_(const char* name, jsval value, bool replace); bool SetProperty_(jsval obj, const char* name, jsval value, bool readonly, bool enumerate); bool SetProperty_(jsval obj, const wchar_t* name, jsval value, bool readonly, bool enumerate); bool SetPropertyInt_(jsval obj, int name, jsval value, bool readonly, bool enumerate); - bool GetProperty_(jsval obj, const char* name, jsval& value); - bool GetPropertyInt_(jsval obj, int name, jsval& value); + bool GetProperty_(jsval obj, const char* name, JS::MutableHandleValue out); + bool GetPropertyInt_(jsval obj, int name, JS::MutableHandleValue value); static bool IsExceptionPending(JSContext* cx); - static JSClass* GetClass(JSContext* cx, JSObject* obj); - static void* GetPrivate(JSContext* cx, JSObject* obj); + static JSClass* GetClass(JSObject* obj); + static void* GetPrivate(JSObject* obj); class CustomType { @@ -387,6 +420,7 @@ private: void Register(const char* name, JSNative fptr, size_t nargs); std::auto_ptr m; + boost::rand48* m_rng; std::map m_CustomObjectTypes; // The nasty macro/template bits are split into a separate file so you don't have to look at them @@ -425,7 +459,7 @@ bool ScriptInterface::CallFunctionVoid(jsval val, const char* name, const T0& a0 { jsval jsRet; jsval argv[1]; - argv[0] = ToJSVal(GetContext(), a0); + ToJSVal(GetContext(), argv[0], a0); return CallFunction_(val, name, 1, argv, jsRet); } @@ -434,8 +468,8 @@ bool ScriptInterface::CallFunctionVoid(jsval val, const char* name, const T0& a0 { jsval jsRet; jsval argv[2]; - argv[0] = ToJSVal(GetContext(), a0); - argv[1] = ToJSVal(GetContext(), a1); + ToJSVal(GetContext(), argv[0], a0); + ToJSVal(GetContext(), argv[1], a1); return CallFunction_(val, name, 2, argv, jsRet); } @@ -444,9 +478,9 @@ bool ScriptInterface::CallFunctionVoid(jsval val, const char* name, const T0& a0 { jsval jsRet; jsval argv[3]; - argv[0] = ToJSVal(GetContext(), a0); - argv[1] = ToJSVal(GetContext(), a1); - argv[2] = ToJSVal(GetContext(), a2); + ToJSVal(GetContext(), argv[0], a0); + ToJSVal(GetContext(), argv[1], a1); + ToJSVal(GetContext(), argv[2], a2); return CallFunction_(val, name, 3, argv, jsRet); } @@ -455,7 +489,7 @@ bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, R& { jsval jsRet; jsval argv[1]; - argv[0] = ToJSVal(GetContext(), a0); + ToJSVal(GetContext(), argv[0], a0); bool ok = CallFunction_(val, name, 1, argv, jsRet); if (!ok) return false; @@ -467,8 +501,8 @@ bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, co { jsval jsRet; jsval argv[2]; - argv[0] = ToJSVal(GetContext(), a0); - argv[1] = ToJSVal(GetContext(), a1); + ToJSVal(GetContext(), argv[0], a0); + ToJSVal(GetContext(), argv[1], a1); bool ok = CallFunction_(val, name, 2, argv, jsRet); if (!ok) return false; @@ -480,9 +514,9 @@ bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, co { jsval jsRet; jsval argv[3]; - argv[0] = ToJSVal(GetContext(), a0); - argv[1] = ToJSVal(GetContext(), a1); - argv[2] = ToJSVal(GetContext(), a2); + ToJSVal(GetContext(), argv[0], a0); + ToJSVal(GetContext(), argv[1], a1); + ToJSVal(GetContext(), argv[2], a2); bool ok = CallFunction_(val, name, 3, argv, jsRet); if (!ok) return false; @@ -494,10 +528,10 @@ bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, co { jsval jsRet; jsval argv[4]; - argv[0] = ToJSVal(GetContext(), a0); - argv[1] = ToJSVal(GetContext(), a1); - argv[2] = ToJSVal(GetContext(), a2); - argv[3] = ToJSVal(GetContext(), a3); + ToJSVal(GetContext(), argv[0], a0); + ToJSVal(GetContext(), argv[1], a1); + ToJSVal(GetContext(), argv[2], a2); + ToJSVal(GetContext(), argv[3], a3); bool ok = CallFunction_(val, name, 4, argv, jsRet); if (!ok) return false; @@ -507,13 +541,17 @@ bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, co template bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace) { - return SetGlobal_(name, ToJSVal(GetContext(), value), replace); + JS::Value val; + ToJSVal(GetContext(), val, value); + return SetGlobal_(name, val, replace); } template bool ScriptInterface::SetProperty(jsval obj, const char* name, const T& value, bool readonly, bool enumerate) { - return SetProperty_(obj, name, ToJSVal(GetContext(), value), readonly, enumerate); + JS::Value val; + ToJSVal(GetContext(), val, value); + return SetProperty_(obj, name, val, readonly, enumerate); } template @@ -525,23 +563,28 @@ bool ScriptInterface::SetProperty(jsval obj, const wchar_t* name, const T& value template bool ScriptInterface::SetPropertyInt(jsval obj, int name, const T& value, bool readonly, bool enumerate) { - return SetPropertyInt_(obj, name, ToJSVal(GetContext(), value), readonly, enumerate); + JS::Value val; + ToJSVal(GetContext(), val, value); + return SetPropertyInt_(obj, name, val, readonly, enumerate); } template bool ScriptInterface::GetProperty(jsval obj, const char* name, T& out) { - jsval val; - if (! GetProperty_(obj, name, val)) + JSContext* cx = GetContext(); + JSAutoRequest rq(cx); + JS::RootedValue val(cx); + if (! GetProperty_(obj, name, &val)) return false; - return FromJSVal(GetContext(), val, out); + return FromJSVal(cx, val, out); } template bool ScriptInterface::GetPropertyInt(jsval obj, int name, T& out) { - jsval val; - if (! GetPropertyInt_(obj, name, val)) + JSAutoRequest rq(GetContext()); + JS::RootedValue val(GetContext()); + if (! GetPropertyInt_(obj, name, &val)) return false; return FromJSVal(GetContext(), val, out); } diff --git a/source/scriptinterface/ScriptStats.cpp b/source/scriptinterface/ScriptStats.cpp index 9fe9ec024d..7fbff92b15 100644 --- a/source/scriptinterface/ScriptStats.cpp +++ b/source/scriptinterface/ScriptStats.cpp @@ -21,8 +21,6 @@ #include "scriptinterface/ScriptInterface.h" -#include "js/jsapi.h" - CScriptStatsTable* g_ScriptStatsTable; enum diff --git a/source/scriptinterface/ScriptTypes.h b/source/scriptinterface/ScriptTypes.h index da03a1d268..2b825ce2d2 100644 --- a/source/scriptinterface/ScriptTypes.h +++ b/source/scriptinterface/ScriptTypes.h @@ -23,60 +23,53 @@ # ifndef WIN32 # define WIN32 // SpiderMonkey expects this # endif - -// The jsval struct type causes crashes due to weird miscompilation -// issues in (at least) VC2008, so force it to be the less-type-safe -// non-struct type instead -# define JS_NO_JSVAL_JSID_STRUCT_TYPES - -// Make JS think the int8_t etc types are defined, since wposix_types.h emulates -// the ones that are needed and this avoids conflicting definitions -# define JS_SYS_TYPES_H_DEFINES_EXACT_SIZE_TYPES - -#else // If not Windows, then Unix: - -# define XP_UNIX - -// In DEBUG mode, jsval defaults to struct types. Normally we build separate -// debug/release mode versions of the library, but when using --with-system-mozjs185 -// it's always a release mode library, so we have to disable struct types for -// ABI compatibility -# if defined(DEBUG) && defined(WITH_SYSTEM_MOZJS185) -# define JS_NO_JSVAL_JSID_STRUCT_TYPES -# endif - #endif -// (we don't support XP_OS2 or XP_BEOS) - // Guess whether the library was compiled with the release-mode or debug-mode ABI // (for JS_DumpHeap etc) -#if defined(DEBUG) && !defined(WITH_SYSTEM_MOZJS185) +#if defined(DEBUG) && !defined(WITH_SYSTEM_MOZJS24) # define MOZJS_DEBUG_ABI 1 #else # define MOZJS_DEBUG_ABI 0 #endif - -// SpiderMonkey wants the DEBUG flag -#ifndef NDEBUG -# ifndef DEBUG -# define DEBUG -# endif -#endif - // Ignore some harmless warnings triggered by jsapi.h -#if GCC_VERSION >= 402 // (older GCCs don't support this pragma) +// Not all of these warnings can be disabled in older versions of GCC. +// The version checking was taken from the manual here: +// http://gcc.gnu.org/onlinedocs/ +// Choose the version and navigate to "Options to Request or Suppress Warnings" +// or for some flags "Options Controlling C++ Dialect". +#if GCC_VERSION >= 402 +# if GCC_VERSION >= 406 // store user flags +# pragma GCC diagnostic push +# endif # pragma GCC diagnostic ignored "-Wunused-parameter" # pragma GCC diagnostic ignored "-Wredundant-decls" +# pragma GCC diagnostic ignored "-Wundef" // Some versions of GCC will still print warnings (see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431). +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +# if GCC_VERSION >= 403 +# pragma GCC diagnostic ignored "-Wignored-qualifiers" +# endif #endif #if MSC_VERSION +// warnings which are also disabled for the files that include this header + +// For the transition from JSBool to bool +# pragma warning(disable:4800) // "forcing value to bool 'true' or 'false' (performance warning) +# pragma warning(disable:4805) // '==' : unsafe mix of type 'JSBool' and type 'bool' in operation + +// warnings only disabled for the SpiderMonkey headers # pragma warning(push) # pragma warning(disable:4480) // "nonstandard extension used: specifying underlying type for enum" # pragma warning(disable:4100) // "unreferenced formal parameter" +# pragma warning(disable:4512) // "assignment operator could not be generated" +# pragma warning(disable:4265) // "class has virtual functions, but destructor is not virtual" +# pragma warning(disable:4251) // "class 'X' needs to have dll-interface to be used by clients of struct 'Y'" #endif -#include "js/jsapi.h" +#include "jspubtd.h" +#include "jsapi.h" + #if MSC_VERSION # pragma warning(pop) @@ -84,15 +77,34 @@ #if GCC_VERSION >= 402 # pragma GCC diagnostic warning "-Wunused-parameter" # pragma GCC diagnostic warning "-Wredundant-decls" +# pragma GCC diagnostic warning "-Wundef" +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +# if GCC_VERSION >= 403 +# pragma GCC diagnostic warning "-Wignored-qualifiers" +# endif +# if GCC_VERSION >= 406 +// restore user flags (and we don't have to manually restore the warning levels for GCC >= 4.6) +# pragma GCC diagnostic pop +# endif #endif -#if JS_VERSION != 185 -#error Your compiler is trying to use an incorrect version of the SpiderMonkey library. +#if MOZJS_MAJOR_VERSION != 24 +#error Your compiler is trying to use an incorrect major version of the SpiderMonkey library. #error The only version that works is the one in the libraries/spidermonkey/ directory, #error and it will not work with a typical system-installed version. #error Make sure you have got all the right files and include paths. #endif +#if MOZJS_MINOR_VERSION != 2 +#error Your compiler is trying to use an untested minor version of the SpiderMonkey library. +#error If you are a package maintainer, please make sure to check very carefully that this version does not change +#error the behaviour of the code executed by SpiderMonkey. +#error Different parts of the game (e.g. the multiplayer mode) rely on deterministic behaviour of the JavaScript engine. +#error A simple way for testing this would be playing a network game with one player using the old version and one player using the new version. +#error Another way for testing is running replays and comparing the final hash (check trac.wildfiregames.com/wiki/Debugging#Replaymode). +#error For more information check this link: trac.wildfiregames.com/wiki/Debugging#Outofsync +#endif + class ScriptInterface; class CScriptVal; class CScriptValRooted; diff --git a/source/scriptinterface/ScriptVal.cpp b/source/scriptinterface/ScriptVal.cpp index 8cad9d0645..029d054501 100644 --- a/source/scriptinterface/ScriptVal.cpp +++ b/source/scriptinterface/ScriptVal.cpp @@ -17,19 +17,24 @@ #include "precompiled.h" +#include "ScriptInterface.h" #include "ScriptVal.h" -#include "js/jsapi.h" struct Unrooter { Unrooter(JSContext* cx) : cx(cx) { } - void operator()(jsval* p) { JS_RemoveValueRoot(cx, p); delete p; } + void operator()(jsval* p) + { + JSAutoRequest rq(cx); + JS_RemoveValueRoot(cx, p); delete p; + } JSContext* cx; }; CScriptValRooted::CScriptValRooted(JSContext* cx, jsval val) { + JSAutoRequest rq(cx); jsval* p = new jsval(val); JS_AddNamedValueRoot(cx, p, "CScriptValRooted"); m_Val = boost::shared_ptr(p, Unrooter(cx)); @@ -37,6 +42,7 @@ CScriptValRooted::CScriptValRooted(JSContext* cx, jsval val) CScriptValRooted::CScriptValRooted(JSContext* cx, CScriptVal val) { + JSAutoRequest rq(cx); jsval* p = new jsval(val.get()); JS_AddNamedValueRoot(cx, p, "CScriptValRooted"); m_Val = boost::shared_ptr(p, Unrooter(cx)); @@ -64,33 +70,3 @@ bool CScriptValRooted::uninitialised() const { return !m_Val; } - -AutoJSIdArray::AutoJSIdArray(JSContext* cx, JSIdArray* ida) : - m_Context(cx), m_IdArray(ida) -{ -} - -AutoJSIdArray::~AutoJSIdArray() -{ - if (m_IdArray) - JS_DestroyIdArray(m_Context, m_IdArray); -} - -JSIdArray* AutoJSIdArray::get() const -{ - return m_IdArray; -} - -size_t AutoJSIdArray::length() const -{ - if (!m_IdArray) - return 0; - return m_IdArray->length; -} - -jsid AutoJSIdArray::operator[](size_t i) const -{ - if (!(m_IdArray && i < (size_t)m_IdArray->length)) - return JSID_VOID; - return m_IdArray->vector[i]; -} diff --git a/source/scriptinterface/ScriptVal.h b/source/scriptinterface/ScriptVal.h index d992d43d90..bfb03b868e 100644 --- a/source/scriptinterface/ScriptVal.h +++ b/source/scriptinterface/ScriptVal.h @@ -78,23 +78,4 @@ private: boost::shared_ptr m_Val; }; -/** - * RAII wrapper for JSIdArray* - */ -class AutoJSIdArray -{ - NONCOPYABLE(AutoJSIdArray); -public: - AutoJSIdArray(JSContext* cx, JSIdArray* ida); - ~AutoJSIdArray(); - - JSIdArray* get() const; - size_t length() const; - jsid operator[](size_t i) const; - -private: - JSContext* m_Context; - JSIdArray* m_IdArray; -}; - #endif // INCLUDED_SCRIPTVAL diff --git a/source/scriptinterface/tests/test_ScriptConversions.h b/source/scriptinterface/tests/test_ScriptConversions.h index 9926653527..7c5a7e03f3 100644 --- a/source/scriptinterface/tests/test_ScriptConversions.h +++ b/source/scriptinterface/tests/test_ScriptConversions.h @@ -23,7 +23,7 @@ #include "maths/MathUtil.h" #include "ps/CLogger.h" -#include "js/jsapi.h" +#include "jsapi.h" class TestScriptConversions : public CxxTest::TestSuite { @@ -32,13 +32,15 @@ class TestScriptConversions : public CxxTest::TestSuite { ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime()); JSContext* cx = script.GetContext(); + JSAutoRequest rq(cx); - jsval v1 = ScriptInterface::ToJSVal(cx, value); + JS::Value v1; + ScriptInterface::ToJSVal(cx, v1, value); // We want to convert values to strings, but can't just call toSource() on them // since they might not be objects. So just use uneval. std::string source; - TS_ASSERT(script.CallFunction(OBJECT_TO_JSVAL(JS_GetGlobalObject(cx)), "uneval", CScriptVal(v1), source)); + TS_ASSERT(script.CallFunction(OBJECT_TO_JSVAL(JS_GetGlobalForScopeChain(cx)), "uneval", CScriptVal(v1), source)); TS_ASSERT_STR_EQUALS(source, expected); } @@ -48,11 +50,13 @@ class TestScriptConversions : public CxxTest::TestSuite { ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime()); JSContext* cx = script.GetContext(); + JSAutoRequest rq(cx); - jsval v1 = ScriptInterface::ToJSVal(cx, value); + JS::Value v1; + ScriptInterface::ToJSVal(cx, v1, value); std::string source; - TS_ASSERT(script.CallFunction(OBJECT_TO_JSVAL(JS_GetGlobalObject(cx)), "uneval", CScriptVal(v1), source)); + TS_ASSERT(script.CallFunction(OBJECT_TO_JSVAL(JS_GetGlobalForScopeChain(cx)), "uneval", CScriptVal(v1), source)); if (expected) TS_ASSERT_STR_EQUALS(source, expected); @@ -101,12 +105,12 @@ public: roundtrip("", "\"\""); roundtrip("test", "\"test\""); - roundtrip(s1, "\"t\\0st\""); + roundtrip(s1, "\"t\\x00st\""); // TODO: should test non-ASCII strings roundtrip(L"", "\"\""); roundtrip(L"test", "\"test\""); - roundtrip(w1, "\"t\\0st\""); + roundtrip(w1, "\"t\\x00st\""); // TODO: should test non-ASCII strings convert_to("", "\"\""); @@ -123,19 +127,29 @@ public: { ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime()); JSContext* cx = script.GetContext(); + JSAutoRequest rq(cx); + + // using new uninitialized variables each time to be sure the test doesn't succeeed if ToJSVal doesn't touch the value at all. + JS::Value val0, val1, val2, val3, val4, val5, val6, val7, val8; + ScriptInterface::ToJSVal(cx, val0, 0); + ScriptInterface::ToJSVal(cx, val1, 2147483646); // JSVAL_INT_MAX-1 + ScriptInterface::ToJSVal(cx, val2, 2147483647); // JSVAL_INT_MAX + ScriptInterface::ToJSVal(cx, val3, -2147483647); // JSVAL_INT_MIN+1 + ScriptInterface::ToJSVal(cx, val4, -(i64)2147483648u); // JSVAL_INT_MIN + TS_ASSERT(JSVAL_IS_INT(val0)); + TS_ASSERT(JSVAL_IS_INT(val1)); + TS_ASSERT(JSVAL_IS_INT(val2)); + TS_ASSERT(JSVAL_IS_INT(val3)); + TS_ASSERT(JSVAL_IS_INT(val4)); - TS_ASSERT(JSVAL_IS_INT(ScriptInterface::ToJSVal(cx, 0))); - - TS_ASSERT(JSVAL_IS_INT(ScriptInterface::ToJSVal(cx, 2147483646))); // JSVAL_INT_MAX-1 - TS_ASSERT(JSVAL_IS_INT(ScriptInterface::ToJSVal(cx, 2147483647))); // JSVAL_INT_MAX - TS_ASSERT(JSVAL_IS_INT(ScriptInterface::ToJSVal(cx, -2147483647))); // JSVAL_INT_MIN+1 - TS_ASSERT(JSVAL_IS_INT(ScriptInterface::ToJSVal(cx, -(i64)2147483648u))); // JSVAL_INT_MIN - - TS_ASSERT(JSVAL_IS_INT(ScriptInterface::ToJSVal(cx, 0))); - - TS_ASSERT(JSVAL_IS_INT(ScriptInterface::ToJSVal(cx, 2147483646u))); // JSVAL_INT_MAX-1 - TS_ASSERT(JSVAL_IS_INT(ScriptInterface::ToJSVal(cx, 2147483647u))); // JSVAL_INT_MAX - TS_ASSERT(JSVAL_IS_DOUBLE(ScriptInterface::ToJSVal(cx, 2147483648u))); // JSVAL_INT_MAX+1 + ScriptInterface::ToJSVal(cx, val5, 0); + ScriptInterface::ToJSVal(cx, val6, 2147483646u); // JSVAL_INT_MAX-1 + ScriptInterface::ToJSVal(cx, val7, 2147483647u); // JSVAL_INT_MAX + ScriptInterface::ToJSVal(cx, val8, 2147483648u); // JSVAL_INT_MAX+1 + TS_ASSERT(JSVAL_IS_INT(val5)); + TS_ASSERT(JSVAL_IS_INT(val6)); + TS_ASSERT(JSVAL_IS_INT(val7)); + TS_ASSERT(JSVAL_IS_DOUBLE(val8)); } void test_nonfinite() @@ -146,9 +160,12 @@ public: ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime()); JSContext* cx = script.GetContext(); + JSAutoRequest rq(cx); float f = 0; - TS_ASSERT(ScriptInterface::FromJSVal(cx, ScriptInterface::ToJSVal(cx, NAN), f)); + JS::Value testNANVal; + ScriptInterface::ToJSVal(cx, testNANVal, NAN); + TS_ASSERT(ScriptInterface::FromJSVal(cx, testNANVal, f)); TS_ASSERT(isnan(f)); } diff --git a/source/scriptinterface/tests/test_ScriptInterface.h b/source/scriptinterface/tests/test_ScriptInterface.h index 8168306d18..86065ef136 100644 --- a/source/scriptinterface/tests/test_ScriptInterface.h +++ b/source/scriptinterface/tests/test_ScriptInterface.h @@ -61,8 +61,9 @@ public: void test_clone_basic() { - ScriptInterface script1("Test", "Test", ScriptInterface::CreateRuntime()); - ScriptInterface script2("Test", "Test", ScriptInterface::CreateRuntime()); + shared_ptr runtime = ScriptInterface::CreateRuntime(); + ScriptInterface script1("Test", "Test", runtime); + ScriptInterface script2("Test", "Test", runtime); CScriptVal obj1; TS_ASSERT(script1.Eval("({'x': 123, 'y': [1, 1.5, '2', 'test', undefined, null, true, false]})", obj1)); @@ -78,8 +79,9 @@ public: { // The tests should be run with JS_SetGCZeal so this can try to find GC bugs - ScriptInterface script1("Test", "Test", ScriptInterface::CreateRuntime()); - ScriptInterface script2("Test", "Test", ScriptInterface::CreateRuntime()); + shared_ptr runtime = ScriptInterface::CreateRuntime(); + ScriptInterface script1("Test", "Test", runtime); + ScriptInterface script2("Test", "Test", runtime); CScriptVal obj1; TS_ASSERT(script1.Eval("var s = '?'; var v = ({get x() { return 123 }, 'y': {'w':{get z() { delete v.y; delete v.n; v = null; s += s; return 4 }}}, 'n': 100}); v", obj1)); @@ -88,22 +90,33 @@ public: std::string source; TS_ASSERT(script2.CallFunction(obj2.get(), "toSource", source)); - TS_ASSERT_STR_EQUALS(source, "({x:123, y:{w:{z:4}}, n:(void 0)})"); + TS_ASSERT_STR_EQUALS(source, "({x:123, y:{w:{z:4}}})"); } void test_clone_cyclic() { - ScriptInterface script1("Test", "Test", ScriptInterface::CreateRuntime()); - ScriptInterface script2("Test", "Test", ScriptInterface::CreateRuntime()); + shared_ptr runtime = ScriptInterface::CreateRuntime(); + ScriptInterface script1("Test", "Test", runtime); + ScriptInterface script2("Test", "Test", runtime); CScriptVal obj1; TS_ASSERT(script1.Eval("var x = []; x[0] = x; ({'a': x, 'b': x})", obj1)); CScriptVal obj2 = script2.CloneValueFromOtherContext(script1, obj1.get()); - std::string source; - TS_ASSERT(script2.CallFunction(obj2.get(), "toSource", source)); - TS_ASSERT_STR_EQUALS(source, "({a:#1=[#1#], b:#1#})"); + // Use JSAPI function to check if the values of the properties "a", "b" are equals a.x[0] + JSContext* cx2 = script2.GetContext(); + JSAutoRequest rq(cx2); + JS::RootedValue prop_a(cx2); + JS::RootedValue prop_b(cx2); + JS::RootedValue prop_x1(cx2); + TS_ASSERT(JS_GetProperty(cx2, &(obj2.get().toObject()), "a", prop_a.address())); + TS_ASSERT(JS_GetProperty(cx2, &(obj2.get().toObject()), "b", prop_b.address())); + TS_ASSERT(prop_a.get().isObject()); + TS_ASSERT(prop_b.get().isObject()); + TS_ASSERT(JS_GetProperty(cx2, &(prop_a.get().toObject()), "0", prop_x1.address())); + TS_ASSERT_EQUALS(prop_x1.get(), prop_a.get()); + TS_ASSERT_EQUALS(prop_x1.get(), prop_b.get()); } void test_random() diff --git a/source/scriptinterface/tests/test_ScriptVal.h b/source/scriptinterface/tests/test_ScriptVal.h index 7d1a7271c4..77fa10b83c 100644 --- a/source/scriptinterface/tests/test_ScriptVal.h +++ b/source/scriptinterface/tests/test_ScriptVal.h @@ -20,7 +20,7 @@ #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptVal.h" -#include "js/jsapi.h" +#include "jsapi.h" class TestScriptVal : public CxxTest::TestSuite { @@ -29,18 +29,19 @@ public: { ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime()); JSContext* cx = script.GetContext(); + JSAutoRequest rq(cx); JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); TS_ASSERT(obj); CScriptValRooted root(cx, OBJECT_TO_JSVAL(obj)); - JS_GC(cx); + JS_GC(script.GetJSRuntime()); jsval val = INT_TO_JSVAL(123); TS_ASSERT(JS_SetProperty(cx, obj, "test", &val)); - JS_GC(cx); + JS_GC(script.GetJSRuntime()); jsval rval; TS_ASSERT(JS_GetProperty(cx, obj, "test", &rval)); diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp index de99018fd2..6eef06c515 100644 --- a/source/simulation2/Simulation2.cpp +++ b/source/simulation2/Simulation2.cpp @@ -19,6 +19,8 @@ #include "Simulation2.h" +#include "scriptinterface/ScriptInterface.h" + #include "simulation2/MessageTypes.h" #include "simulation2/system/ComponentManager.h" #include "simulation2/system/ParamNode.h" @@ -451,8 +453,8 @@ void CSimulation2Impl::Update(int turnLength, const std::vectorSetProperty(settings.get(), "templates", m_Worker.m_EntityTemplates, false); - obj = m_ScriptInterface->CallConstructor(ctor.get(), settings.get()); + JS::AutoValueVector argv(m_ScriptInterface->GetContext()); + argv.append(settings.get()); + obj = m_ScriptInterface->CallConstructor(ctor.get(), argv.length(), argv.handleAt(0)); } else { @@ -209,12 +211,7 @@ public: }; CAIWorker() : - // TODO: Passing a 32 MB argument to CreateRuntime() is a temporary fix - // to prevent frequent AI out-of-memory crashes. The argument should be - // removed as soon whenever the new pathfinder is committed - // And the AIs can stop relying on their own little hands. - m_ScriptRuntime(ScriptInterface::CreateRuntime(33554432)), - m_ScriptInterface(new ScriptInterface("Engine", "AI", m_ScriptRuntime)), + m_ScriptInterface(new ScriptInterface("Engine", "AI", g_ScriptRuntime)), m_TurnNum(0), m_CommandsComputed(true), m_HasLoadedEntityTemplates(false), @@ -305,7 +302,7 @@ public: static void ForceGC(ScriptInterface::CxPrivate* pCxPrivate) { PROFILE3("AI compute GC"); - JS_GC(pCxPrivate->pScriptInterface->GetContext()); + JS_GC(pCxPrivate->pScriptInterface->GetJSRuntime()); } /** @@ -388,7 +385,8 @@ public: for (size_t i = 0; i < m_Players.size(); ++i) { - jsval val = m_ScriptInterface->ToJSVal(m_ScriptInterface->GetContext(), m_Players[i]->m_Player); + JS::Value val; + m_ScriptInterface->ToJSVal(m_ScriptInterface->GetContext(), val, m_Players[i]->m_Player); m_ScriptInterface->SetPropertyInt(playersID.get(), i, CScriptVal(val), true); } @@ -407,7 +405,10 @@ public: m_ScriptInterface->Eval("({})", fakeTech); m_ScriptInterface->SetProperty(settings.get(), "techTemplates", fakeTech, false); } - m_SharedAIObj = CScriptValRooted(m_ScriptInterface->GetContext(),m_ScriptInterface->CallConstructor(ctor.get(), settings.get())); + + JS::AutoValueVector argv(m_ScriptInterface->GetContext()); + argv.append(settings.get()); + m_SharedAIObj = CScriptValRooted(m_ScriptInterface->GetContext(),m_ScriptInterface->CallConstructor(ctor.get(), argv.length(), argv.handleAt(0))); if (m_SharedAIObj.undefined()) @@ -429,8 +430,6 @@ public: if (!m_HasSharedComponent) m_HasSharedComponent = ai->m_UseSharedComponent; - m_ScriptInterface->MaybeGC(); - m_Players.push_back(ai); return true; @@ -444,15 +443,17 @@ public: CScriptVal state = m_ScriptInterface->ReadStructuredClone(gameState); JSContext* cx = m_ScriptInterface->GetContext(); - m_PassabilityMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, passabilityMap)); - m_TerritoryMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, territoryMap)); + JS::RootedValue tmpVal(cx); + ScriptInterface::ToJSVal(cx, tmpVal.get(), passabilityMap); + m_PassabilityMapVal = CScriptValRooted(cx, tmpVal.get()); + ScriptInterface::ToJSVal(cx, tmpVal.get(), territoryMap); + m_TerritoryMapVal = CScriptValRooted(cx, tmpVal); if (m_HasSharedComponent) { m_ScriptInterface->SetProperty(state.get(), "passabilityMap", m_PassabilityMapVal, true); m_ScriptInterface->SetProperty(state.get(), "territoryMap", m_TerritoryMapVal, true); m_ScriptInterface->CallFunctionVoid(m_SharedAIObj.get(), "init", state); - m_ScriptInterface->MaybeGC(); for (size_t i = 0; i < m_Players.size(); ++i) { @@ -468,21 +469,24 @@ public: ENSURE(m_CommandsComputed); m_GameState = gameState; + + JSContext* cx = m_ScriptInterface->GetContext(); + JSAutoRequest rq(cx); if (passabilityMap.m_DirtyID != m_PassabilityMap.m_DirtyID) { m_PassabilityMap = passabilityMap; - - JSContext* cx = m_ScriptInterface->GetContext(); - m_PassabilityMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, m_PassabilityMap)); + JS::RootedValue tmpVal(cx); + ScriptInterface::ToJSVal(cx, tmpVal.get(), m_PassabilityMap); + m_PassabilityMapVal = CScriptValRooted(cx, tmpVal); } if (territoryMapDirty) { m_TerritoryMap = territoryMap; - - JSContext* cx = m_ScriptInterface->GetContext(); - m_TerritoryMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, m_TerritoryMap)); + JS::RootedValue tmpVal(cx); + ScriptInterface::ToJSVal(cx, tmpVal.get(), m_TerritoryMap); + m_TerritoryMapVal = CScriptValRooted(cx, tmpVal); } m_CommandsComputed = false; @@ -705,20 +709,7 @@ private: } void PerformComputation() - { - if (m_Players.size() == 0) - { - // Run the GC every so often. - // (This isn't particularly necessary, but it makes profiling clearer - // since it avoids random GC delays while running other scripts) - if (m_TurnNum++ % 50 == 0) - { - PROFILE3("AI compute GC"); - m_ScriptInterface->MaybeGC(); - } - return; - } - + { // Deserialize the game state, to pass to the AI's HandleMessage CScriptVal state; { @@ -752,23 +743,6 @@ private: else m_Players[i]->Run(state, m_Players[i]->m_Player); } - - // Run GC if we are about to overflow - if (JS_GetGCParameter(m_ScriptInterface->GetJSRuntime(), JSGC_BYTES) > 33000000) - { - PROFILE3("AI compute GC"); - - JS_GC(m_ScriptInterface->GetContext()); - } - - // Run the GC every so often. - // (This isn't particularly necessary, but it makes profiling clearer - // since it avoids random GC delays while running other scripts) - /*if (m_TurnNum++ % 20 == 0) - { - PROFILE3("AI compute GC"); - m_ScriptInterface->MaybeGC(); - }*/ } shared_ptr m_ScriptRuntime; diff --git a/source/simulation2/components/ICmpFootprint.cpp b/source/simulation2/components/ICmpFootprint.cpp index 374ae44edd..0c100ca252 100644 --- a/source/simulation2/components/ICmpFootprint.cpp +++ b/source/simulation2/components/ICmpFootprint.cpp @@ -38,23 +38,30 @@ CScriptVal ICmpFootprint::GetShape_wrapper() if (shape == CIRCLE) { - jsval ptype = ScriptInterface::ToJSVal(cx, "circle"); - jsval pradius = ScriptInterface::ToJSVal(cx, size0); - jsval pheight = ScriptInterface::ToJSVal(cx, height); - JS_SetProperty(cx, obj, "type", &ptype); - JS_SetProperty(cx, obj, "radius", &pradius); - JS_SetProperty(cx, obj, "height", &pheight); + JS::RootedValue ptype(cx); + JS::RootedValue pradius(cx); + JS::RootedValue pheight(cx); + ScriptInterface::ToJSVal(cx, ptype.get(), "circle"); + ScriptInterface::ToJSVal(cx, pradius.get(), size0); + ScriptInterface::ToJSVal(cx, pheight.get(), height); + JS_SetProperty(cx, obj, "type", ptype.address()); + JS_SetProperty(cx, obj, "radius", pradius.address()); + JS_SetProperty(cx, obj, "height", pheight.address()); } else { - jsval ptype = ScriptInterface::ToJSVal(cx, "square"); - jsval pwidth = ScriptInterface::ToJSVal(cx, size0); - jsval pdepth = ScriptInterface::ToJSVal(cx, size1); - jsval pheight = ScriptInterface::ToJSVal(cx, height); - JS_SetProperty(cx, obj, "type", &ptype); - JS_SetProperty(cx, obj, "width", &pwidth); - JS_SetProperty(cx, obj, "depth", &pdepth); - JS_SetProperty(cx, obj, "height", &pheight); + JS::RootedValue ptype(cx); + JS::RootedValue pwidth(cx); + JS::RootedValue pdepth(cx); + JS::RootedValue pheight(cx); + ScriptInterface::ToJSVal(cx, ptype.get(), "square"); + ScriptInterface::ToJSVal(cx, pwidth.get(), size0); + ScriptInterface::ToJSVal(cx, pdepth.get(), size1); + ScriptInterface::ToJSVal(cx, pheight.get(), height); + JS_SetProperty(cx, obj, "type", ptype.address()); + JS_SetProperty(cx, obj, "width", pwidth.address()); + JS_SetProperty(cx, obj, "depth", pdepth.address()); + JS_SetProperty(cx, obj, "height", pheight.address()); } return OBJECT_TO_JSVAL(obj); diff --git a/source/simulation2/components/tests/test_CommandQueue.h b/source/simulation2/components/tests/test_CommandQueue.h index ec0998c9ce..c010cc8486 100644 --- a/source/simulation2/components/tests/test_CommandQueue.h +++ b/source/simulation2/components/tests/test_CommandQueue.h @@ -34,7 +34,7 @@ public: void test_basic() { - ComponentTestHelper test; + ComponentTestHelper test(ScriptInterface::CreateRuntime()); std::vector empty; diff --git a/source/simulation2/components/tests/test_ObstructionManager.h b/source/simulation2/components/tests/test_ObstructionManager.h index 9108723a2d..b909b78fae 100644 --- a/source/simulation2/components/tests/test_ObstructionManager.h +++ b/source/simulation2/components/tests/test_ObstructionManager.h @@ -68,7 +68,7 @@ public: ent3z = ent2z + ent2r + ent3r; // ensure it just touches the border of ent2 ent3g = ent3; - testHelper = new ComponentTestHelper; + testHelper = new ComponentTestHelper(ScriptInterface::CreateRuntime()); cmp = testHelper->Add(CID_ObstructionManager, ""); cmp->SetBounds(fixed::FromInt(0), fixed::FromInt(0), fixed::FromInt(1000), fixed::FromInt(1000)); diff --git a/source/simulation2/components/tests/test_Position.h b/source/simulation2/components/tests/test_Position.h index 54837c5993..bd2f7b7e29 100644 --- a/source/simulation2/components/tests/test_Position.h +++ b/source/simulation2/components/tests/test_Position.h @@ -39,7 +39,7 @@ public: void test_basic() { - ComponentTestHelper test; + ComponentTestHelper test(ScriptInterface::CreateRuntime()); MockTerrain terrain; test.AddMock(SYSTEM_ENTITY, IID_Terrain, terrain); @@ -111,7 +111,7 @@ public: void test_serialize() { - ComponentTestHelper test; + ComponentTestHelper test(ScriptInterface::CreateRuntime()); MockTerrain terrain; test.AddMock(SYSTEM_ENTITY, IID_Terrain, terrain); diff --git a/source/simulation2/components/tests/test_RangeManager.h b/source/simulation2/components/tests/test_RangeManager.h index f5c3460da4..5b081af2b3 100644 --- a/source/simulation2/components/tests/test_RangeManager.h +++ b/source/simulation2/components/tests/test_RangeManager.h @@ -77,7 +77,7 @@ public: void test_basic() { - ComponentTestHelper test; + ComponentTestHelper test(ScriptInterface::CreateRuntime()); ICmpRangeManager* cmp = test.Add(CID_RangeManager, ""); diff --git a/source/simulation2/scripting/EngineScriptConversions.cpp b/source/simulation2/scripting/EngineScriptConversions.cpp index 5e87a5ba20..2319dfe18a 100644 --- a/source/simulation2/scripting/EngineScriptConversions.cpp +++ b/source/simulation2/scripting/EngineScriptConversions.cpp @@ -32,15 +32,22 @@ #define FAIL(msg) STMT(JS_ReportError(cx, msg); return false) -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, IComponent* const& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, IComponent* const& val) { + JSAutoRequest rq(cx); if (val == NULL) - return JSVAL_NULL; + { + ret = JS::NullValue(); + return; + } // If this is a scripted component, just return the JS object directly - jsval instance = val->GetJSInstance(); - if (!JSVAL_IS_NULL(instance)) - return instance; + JS::RootedValue instance(cx, val->GetJSInstance()); + if (!instance.isNull()) + { + ret = instance; + return; + } // Otherwise we need to construct a wrapper object // (TODO: cache wrapper objects?) @@ -49,84 +56,97 @@ template<> jsval ScriptInterface::ToJSVal(JSContext* cx, IComponent { // Report as an error, since scripts really shouldn't try to use unscriptable interfaces LOGERROR(L"IComponent does not have a scriptable interface"); - return JSVAL_VOID; + ret = JS::UndefinedValue(); + return; } - JSObject* obj = JS_NewObject(cx, cls, NULL, NULL); + JS::RootedObject obj(cx, JS_NewObject(cx, cls, NULL, NULL)); if (!obj) { LOGERROR(L"Failed to construct IComponent script object"); - return JSVAL_VOID; + ret = JS::UndefinedValue(); + return; } - JS_SetPrivate(cx, obj, static_cast(val)); + JS_SetPrivate(obj, static_cast(val)); - return OBJECT_TO_JSVAL(obj); + ret = JS::ObjectValue(*obj); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, CParamNode const& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, CParamNode const& val) { - jsval rval = val.ToJSVal(cx, true); + JSAutoRequest rq(cx); + ret = val.ToJSVal(cx, true); // Prevent modifications to the object, so that it's safe to share between // components and to reconstruct on deserialization - if (JSVAL_IS_OBJECT(rval)) - JS_DeepFreezeObject(cx, JSVAL_TO_OBJECT(rval)); - - return rval; + if (ret.isObject()) + JS_DeepFreezeObject(cx, &ret.toObject()); } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const CParamNode* const& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const CParamNode* const& val) { if (val) - return ToJSVal(cx, *val); + ToJSVal(cx, ret, *val); else - return JSVAL_VOID; + ret = JSVAL_VOID; } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, CColor& out) { - if (!JSVAL_IS_OBJECT(v)) + if (!v.isObject()) FAIL("jsval not an object"); - JSObject* obj = JSVAL_TO_OBJECT(v); + JSAutoRequest rq(cx); + JS::RootedObject obj(cx, &v.toObject()); - jsval r, g, b, a; - if (!JS_GetProperty(cx, obj, "r", &r) || !FromJSVal(cx, r, out.r)) + JS::RootedValue r(cx); + JS::RootedValue g(cx); + JS::RootedValue b(cx); + JS::RootedValue a(cx); + if (!JS_GetProperty(cx, obj, "r", r.address()) || !FromJSVal(cx, r, out.r)) FAIL("Failed to get property CColor.r"); - if (!JS_GetProperty(cx, obj, "g", &g) || !FromJSVal(cx, g, out.g)) + if (!JS_GetProperty(cx, obj, "g", g.address()) || !FromJSVal(cx, g, out.g)) FAIL("Failed to get property CColor.g"); - if (!JS_GetProperty(cx, obj, "b", &b) || !FromJSVal(cx, b, out.b)) + if (!JS_GetProperty(cx, obj, "b", b.address()) || !FromJSVal(cx, b, out.b)) FAIL("Failed to get property CColor.b"); - if (!JS_GetProperty(cx, obj, "a", &a) || !FromJSVal(cx, a, out.a)) + if (!JS_GetProperty(cx, obj, "a", a.address()) || !FromJSVal(cx, a, out.a)) FAIL("Failed to get property CColor.a"); - // TODO: this probably has GC bugs if a getter returns an unrooted value return true; } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, CColor const& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, CColor const& val) { - JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); + JSAutoRequest rq(cx); + JS::RootedObject obj(cx, JS_NewObject(cx, NULL, NULL, NULL)); if (!obj) - return JSVAL_VOID; + { + ret = JSVAL_VOID; + return; + } - jsval r = ToJSVal(cx, val.r); - jsval g = ToJSVal(cx, val.g); - jsval b = ToJSVal(cx, val.b); - jsval a = ToJSVal(cx, val.a); + JS::RootedValue r(cx); + JS::RootedValue g(cx); + JS::RootedValue b(cx); + JS::RootedValue a(cx); + ToJSVal(cx, r.get(), val.r); + ToJSVal(cx, g.get(), val.g); + ToJSVal(cx, b.get(), val.b); + ToJSVal(cx, a.get(), val.a); - JS_SetProperty(cx, obj, "r", &r); - JS_SetProperty(cx, obj, "g", &g); - JS_SetProperty(cx, obj, "b", &b); - JS_SetProperty(cx, obj, "a", &a); + JS_SetProperty(cx, obj, "r", r.address()); + JS_SetProperty(cx, obj, "g", g.address()); + JS_SetProperty(cx, obj, "b", b.address()); + JS_SetProperty(cx, obj, "a", a.address()); - return OBJECT_TO_JSVAL(obj); + ret = JS::ObjectValue(*obj); } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, fixed& out) { - jsdouble ret; - if (!JS_ValueToNumber(cx, v, &ret)) + JSAutoRequest rq(cx); + double ret; + if (!JS::ToNumber(cx, v, &ret)) return false; out = fixed::FromDouble(ret); // double can precisely represent the full range of fixed, so this is a non-lossy conversion @@ -134,121 +154,146 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, fixed& return true; } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const fixed& val) +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::Value& ret, const fixed& val) { - jsval rval = JSVAL_VOID; - JS_NewNumberValue(cx, val.ToDouble(), &rval); // ignore return value - return rval; + ret = JS::NumberValue(val.ToDouble()); } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, CFixedVector3D& out) { - if (!JSVAL_IS_OBJECT(v)) + if (!v.isObject()) return false; // TODO: report type error - JSObject* obj = JSVAL_TO_OBJECT(v); - jsval p; + JSAutoRequest rq(cx); + JS::RootedObject obj(cx, &v.toObject()); + JS::RootedValue p(cx); - if (!JS_GetProperty(cx, obj, "x", &p)) return false; // TODO: report type errors + if (!JS_GetProperty(cx, obj, "x", p.address())) return false; // TODO: report type errors if (!FromJSVal(cx, p, out.X)) return false; - if (!JS_GetProperty(cx, obj, "y", &p)) return false; + if (!JS_GetProperty(cx, obj, "y", p.address())) return false; if (!FromJSVal(cx, p, out.Y)) return false; - if (!JS_GetProperty(cx, obj, "z", &p)) return false; + if (!JS_GetProperty(cx, obj, "z", p.address())) return false; if (!FromJSVal(cx, p, out.Z)) return false; return true; } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const CFixedVector3D& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const CFixedVector3D& val) { + JSAutoRequest rq(cx); + // apply the Vector3D prototype to the return value; ScriptInterface::CxPrivate* pCxPrivate = ScriptInterface::GetScriptInterfaceAndCBData(cx); - JSObject* obj = JS_NewObject(cx, NULL, JSVAL_TO_OBJECT(pCxPrivate->pScriptInterface->GetCachedValue(ScriptInterface::CACHE_VECTOR3DPROTO).get()), NULL); + JS::RootedObject obj(cx, JS_NewObject(cx, NULL, + &pCxPrivate->pScriptInterface->GetCachedValue(ScriptInterface::CACHE_VECTOR3DPROTO).get().toObject(), + NULL)); if (!obj) - return JSVAL_VOID; + { + ret = JSVAL_VOID; + return; + } - jsval x = ToJSVal(cx, val.X); - jsval y = ToJSVal(cx, val.Y); - jsval z = ToJSVal(cx, val.Z); + JS::RootedValue x(cx); + JS::RootedValue y(cx); + JS::RootedValue z(cx); + ToJSVal(cx, x.get(), val.X); + ToJSVal(cx, y.get(), val.Y); + ToJSVal(cx, z.get(), val.Z); - JS_SetProperty(cx, obj, "x", &x); - JS_SetProperty(cx, obj, "y", &y); - JS_SetProperty(cx, obj, "z", &z); + JS_SetProperty(cx, obj, "x", x.address()); + JS_SetProperty(cx, obj, "y", y.address()); + JS_SetProperty(cx, obj, "z", z.address()); - return OBJECT_TO_JSVAL(obj); + ret = JS::ObjectValue(*obj); } template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, CFixedVector2D& out) { - if (!JSVAL_IS_OBJECT(v)) + JSAutoRequest rq(cx); + if (!v.isObject()) return false; // TODO: report type error - JSObject* obj = JSVAL_TO_OBJECT(v); + JS::RootedObject obj(cx, &v.toObject()); - jsval p; + JS::RootedValue p(cx); - if (!JS_GetProperty(cx, obj, "x", &p)) return false; // TODO: report type errors + if (!JS_GetProperty(cx, obj, "x", p.address())) return false; // TODO: report type errors if (!FromJSVal(cx, p, out.X)) return false; - if (!JS_GetProperty(cx, obj, "y", &p)) return false; + if (!JS_GetProperty(cx, obj, "y", p.address())) return false; if (!FromJSVal(cx, p, out.Y)) return false; return true; } -template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const CFixedVector2D& val) +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::Value& ret, const CFixedVector2D& val) { + JSAutoRequest rq(cx); + // apply the Vector2D prototype to the return value ScriptInterface::CxPrivate* pCxPrivate = ScriptInterface::GetScriptInterfaceAndCBData(cx); - JSObject* obj = JS_NewObject(cx, NULL, JSVAL_TO_OBJECT(pCxPrivate->pScriptInterface->GetCachedValue(ScriptInterface::CACHE_VECTOR2DPROTO).get()), NULL); - + JS::RootedObject obj(cx, JS_NewObject(cx, NULL, + &pCxPrivate->pScriptInterface->GetCachedValue(ScriptInterface::CACHE_VECTOR2DPROTO).get().toObject(), + NULL)); if (!obj) - return JSVAL_VOID; + { + ret = JSVAL_VOID; + return; + } - jsval x = ToJSVal(cx, val.X); - jsval y = ToJSVal(cx, val.Y); + JS::RootedValue x(cx); + JS::RootedValue y(cx); + ToJSVal(cx, x.get(), val.X); + ToJSVal(cx, y.get(), val.Y); - JS_SetProperty(cx, obj, "x", &x); - JS_SetProperty(cx, obj, "y", &y); + JS_SetProperty(cx, obj, "x", x.address()); + JS_SetProperty(cx, obj, "y", y.address()); - return OBJECT_TO_JSVAL(obj); + ret = JS::ObjectValue(*obj); } -template jsval ToJSVal_Grid(JSContext* cx, const Grid& val) +template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::Value& ret, const Grid& val) { - JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); - if (!obj) - return JSVAL_VOID; + JSAutoRequest rq(cx); + u32 length = (u32)(val.m_W * val.m_H); + u32 nbytes = (u32)(length * sizeof(u8)); + JS::RootedObject objArr(cx, JS_NewUint8Array(cx, length)); + memcpy((void*)JS_GetUint8ArrayData(objArr), val.m_Data, nbytes); - jsuint len = val.m_W * val.m_H; - JSObject *darray = js_CreateTypedArray(cx, atype, len); - if (!darray) - return JSVAL_VOID; + JS::RootedValue data(cx, JS::ObjectValue(*objArr)); + JS::RootedValue w(cx); + JS::RootedValue h(cx); + ScriptInterface::ToJSVal(cx, w.get(), val.m_W); + ScriptInterface::ToJSVal(cx, h.get(), val.m_H); - js::TypedArray *tdest = js::TypedArray::fromJSObject(darray); - ENSURE(tdest->byteLength == len*sizeof(T)); + JS::RootedObject obj(cx, JS_NewObject(cx, NULL, NULL, NULL)); + JS_SetProperty(cx, obj, "width", w.address()); + JS_SetProperty(cx, obj, "height", h.address()); + JS_SetProperty(cx, obj, "data", data.address()); - memcpy(tdest->data, val.m_Data, tdest->byteLength); - - jsval w = ScriptInterface::ToJSVal(cx, val.m_W); - jsval h = ScriptInterface::ToJSVal(cx, val.m_H); - jsval data = OBJECT_TO_JSVAL(darray); - - JS_SetProperty(cx, obj, "width", &w); - JS_SetProperty(cx, obj, "height", &h); - JS_SetProperty(cx, obj, "data", &data); - - return OBJECT_TO_JSVAL(obj); + ret = JS::ObjectValue(*obj); } + +template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::Value& ret, const Grid& val) + { + JSAutoRequest rq(cx); + u32 length = (u32)(val.m_W * val.m_H); + u32 nbytes = (u32)(length * sizeof(u16)); + JS::RootedObject objArr(cx, JS_NewUint16Array(cx, length)); + memcpy((void*)JS_GetUint16ArrayData(objArr), val.m_Data, nbytes); + + JS::RootedValue data(cx, JS::ObjectValue(*objArr)); + JS::RootedValue w(cx); + JS::RootedValue h(cx); + ScriptInterface::ToJSVal(cx, w.get(), val.m_W); + ScriptInterface::ToJSVal(cx, h.get(), val.m_H); -template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const Grid& val) -{ - return ToJSVal_Grid(cx, val); -} + JS::RootedObject obj(cx, JS_NewObject(cx, NULL, NULL, NULL)); + JS_SetProperty(cx, obj, "width", w.address()); + JS_SetProperty(cx, obj, "height", h.address()); + JS_SetProperty(cx, obj, "data", data.address()); -template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const Grid& val) -{ - return ToJSVal_Grid(cx, val); + ret = JS::ObjectValue(*obj); } diff --git a/source/simulation2/scripting/MessageTypeConversions.cpp b/source/simulation2/scripting/MessageTypeConversions.cpp index 400507aa48..aacc82068b 100644 --- a/source/simulation2/scripting/MessageTypeConversions.cpp +++ b/source/simulation2/scripting/MessageTypeConversions.cpp @@ -21,32 +21,40 @@ #include "scriptinterface/ScriptInterface.h" #include "simulation2/MessageTypes.h" -#include "js/jsapi.h" - #define TOJSVAL_SETUP() \ - JSObject* obj = JS_NewObject(scriptInterface.GetContext(), NULL, NULL, NULL); \ - if (! obj) \ - return JSVAL_VOID; + JSObject* obj; \ + {\ + JSAutoRequest rq(scriptInterface.GetContext()); \ + obj = JS_NewObject(scriptInterface.GetContext(), NULL, NULL, NULL); \ + if (!obj) \ + return JSVAL_VOID; \ + } #define SET_MSG_PROPERTY(name) \ do { \ - jsval prop = ScriptInterface::ToJSVal(scriptInterface.GetContext(), this->name); \ - if (! JS_SetProperty(scriptInterface.GetContext(), obj, #name, &prop)) \ + JSAutoRequest rq(scriptInterface.GetContext()); \ + JSContext* cx = scriptInterface.GetContext(); \ + JS::RootedValue prop(cx);\ + ScriptInterface::ToJSVal(cx, prop.get(), this->name); \ + if (! JS_SetProperty(cx, obj, #name, prop.address())) \ return JSVAL_VOID; \ } while (0); #define FROMJSVAL_SETUP() \ - if (! JSVAL_IS_OBJECT(val)) \ + if ( JSVAL_IS_PRIMITIVE(val)) \ return NULL; \ JSObject* obj = JSVAL_TO_OBJECT(val); \ - jsval prop; + JS::RootedValue prop(scriptInterface.GetContext()); #define GET_MSG_PROPERTY(type, name) \ - if (! JS_GetProperty(scriptInterface.GetContext(), obj, #name, &prop)) \ - return NULL; \ type name; \ - if (! ScriptInterface::FromJSVal(scriptInterface.GetContext(), prop, name)) \ - return NULL; + { \ + JSAutoRequest rq(scriptInterface.GetContext()); \ + if (! JS_GetProperty(scriptInterface.GetContext(), obj, #name, prop.address())) \ + return NULL; \ + if (! ScriptInterface::FromJSVal(scriptInterface.GetContext(), prop.get(), name)) \ + return NULL; \ + } jsval CMessage::ToJSValCached(ScriptInterface& scriptInterface) const { diff --git a/source/simulation2/serialization/BinarySerializer.cpp b/source/simulation2/serialization/BinarySerializer.cpp index 64e8622547..994ac5b84a 100644 --- a/source/simulation2/serialization/BinarySerializer.cpp +++ b/source/simulation2/serialization/BinarySerializer.cpp @@ -19,35 +19,34 @@ #include "BinarySerializer.h" -#include "SerializedScriptTypes.h" - #include "lib/alignment.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptExtraHeaders.h" // for JSDOUBLE_IS_INT32, typed arrays +#include "SerializedScriptTypes.h" -static u8 GetArrayType(uint32 arrayType) +static u8 GetArrayType(JSArrayBufferViewType arrayType) { switch(arrayType) { - case js::TypedArray::TYPE_INT8: + case js::ArrayBufferView::TYPE_INT8: return SCRIPT_TYPED_ARRAY_INT8; - case js::TypedArray::TYPE_UINT8: + case js::ArrayBufferView::TYPE_UINT8: return SCRIPT_TYPED_ARRAY_UINT8; - case js::TypedArray::TYPE_INT16: + case js::ArrayBufferView::TYPE_INT16: return SCRIPT_TYPED_ARRAY_INT16; - case js::TypedArray::TYPE_UINT16: + case js::ArrayBufferView::TYPE_UINT16: return SCRIPT_TYPED_ARRAY_UINT16; - case js::TypedArray::TYPE_INT32: + case js::ArrayBufferView::TYPE_INT32: return SCRIPT_TYPED_ARRAY_INT32; - case js::TypedArray::TYPE_UINT32: + case js::ArrayBufferView::TYPE_UINT32: return SCRIPT_TYPED_ARRAY_UINT32; - case js::TypedArray::TYPE_FLOAT32: + case js::ArrayBufferView::TYPE_FLOAT32: return SCRIPT_TYPED_ARRAY_FLOAT32; - case js::TypedArray::TYPE_FLOAT64: + case js::ArrayBufferView::TYPE_FLOAT64: return SCRIPT_TYPED_ARRAY_FLOAT64; - case js::TypedArray::TYPE_UINT8_CLAMPED: + case js::ArrayBufferView::TYPE_UINT8_CLAMPED: return SCRIPT_TYPED_ARRAY_UINT8_CLAMPED; default: LOGERROR(L"Cannot serialize unrecognized typed array view: %d", arrayType); @@ -64,6 +63,7 @@ CBinarySerializerScriptImpl::CBinarySerializerScriptImpl(ScriptInterface& script void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) { JSContext* cx = m_ScriptInterface.GetContext(); + JSAutoRequest rq(cx); switch (JS_TypeOfValue(cx, val)) { @@ -79,13 +79,13 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) } case JSTYPE_OBJECT: { - if (JSVAL_IS_NULL(val)) + if (val.isNull()) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NULL); break; } - JSObject* obj = JSVAL_TO_OBJECT(val); + JS::RootedObject obj(cx, &val.toObject()); // If we've already serialized this object, just output a reference to it u32 tag = GetScriptBackrefTag(obj); @@ -104,53 +104,50 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) // Arrays like [1, 2, ] have an 'undefined' at the end which is part of the // length but seemingly isn't enumerated, so store the length explicitly - jsuint length = 0; + uint length = 0; if (!JS_GetArrayLength(cx, obj, &length)) throw PSERROR_Serialize_ScriptError("JS_GetArrayLength failed"); m_Serializer.NumberU32_Unbounded("array length", length); } - else if (js_IsTypedArray(obj)) + else if (JS_IsTypedArrayObject(obj)) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_TYPED_ARRAY); - js::TypedArray* typedArray = js::TypedArray::fromJSObject(obj); - - m_Serializer.NumberU8_Unbounded("array type", GetArrayType(typedArray->type)); - m_Serializer.NumberU32_Unbounded("byte offset", typedArray->byteOffset); - m_Serializer.NumberU32_Unbounded("length", typedArray->length); + m_Serializer.NumberU8_Unbounded("array type", GetArrayType(JS_GetArrayBufferViewType(obj))); + m_Serializer.NumberU32_Unbounded("byte offset", JS_GetTypedArrayByteOffset(obj)); + m_Serializer.NumberU32_Unbounded("length", JS_GetTypedArrayLength(obj)); // Now handle its array buffer // this may be a backref, since ArrayBuffers can be shared by multiple views - HandleScriptVal(OBJECT_TO_JSVAL(typedArray->bufferJS)); + HandleScriptVal(JS::ObjectValue(*JS_GetArrayBufferViewBuffer(obj))); break; } - else if (js_IsArrayBuffer(obj)) + else if (JS_IsArrayBufferObject(obj)) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY_BUFFER); - js::ArrayBuffer* arrayBuffer = js::ArrayBuffer::fromJSObject(obj); - #if BYTE_ORDER != LITTLE_ENDIAN #error TODO: need to convert JS ArrayBuffer data to little-endian #endif - u32 length = arrayBuffer->byteLength; + u32 length = JS_GetArrayBufferByteLength(obj); m_Serializer.NumberU32_Unbounded("buffer length", length); - m_Serializer.RawBytes("buffer data", (const u8*)arrayBuffer->data, length); + m_Serializer.RawBytes("buffer data", (const u8*)JS_GetArrayBufferData(obj), length); break; } else { // Find type of object - JSClass* jsclass = JS_GET_CLASS(cx, obj); + JSClass* jsclass = JS_GetClass(obj); if (!jsclass) - throw PSERROR_Serialize_ScriptError("JS_GET_CLASS failed"); + throw PSERROR_Serialize_ScriptError("JS_GetClass failed"); JSProtoKey protokey = JSCLASS_CACHED_PROTO_KEY(jsclass); if (protokey == JSProto_Object) { // Object class - check for user-defined prototype - JSObject* proto = JS_GetPrototype(cx, obj); + JS::RootedObject proto(cx); + JS_GetPrototype(cx, obj, proto.address()); if (!proto) throw PSERROR_Serialize_ScriptError("JS_GetPrototype failed"); @@ -175,15 +172,15 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) JSBool hasCustomSerialize; if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize)) throw PSERROR_Serialize_ScriptError("JS_HasProperty failed"); - + if (hasCustomSerialize) { - jsval serialize; - if (!JS_LookupProperty(cx, obj, "Serialize", &serialize)) + JS::RootedValue serialize(cx); + if (!JS_LookupProperty(cx, obj, "Serialize", serialize.address())) throw PSERROR_Serialize_ScriptError("JS_LookupProperty failed"); // If serialize is null, so don't serialize anything more - if (!JSVAL_IS_NULL(serialize)) + if (!serialize.isNull()) { CScriptValRooted data; if (!m_ScriptInterface.CallFunction(val, "Serialize", data)) @@ -199,9 +196,9 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) // Standard Number object m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_NUMBER); // Get primitive value - jsdouble d; - if (!JS_ValueToNumber(cx, val, &d)) - throw PSERROR_Serialize_ScriptError("JS_ValueToNumber failed"); + double d; + if (!JS::ToNumber(cx, val, &d)) + throw PSERROR_Serialize_ScriptError("JS::ToNumber failed"); m_Serializer.NumberDouble_Unbounded("value", d); break; } @@ -221,10 +218,8 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) // Standard Boolean object m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_BOOLEAN); // Get primitive value - JSBool b; - if (!JS_ValueToBoolean(cx, val, &b)) - throw PSERROR_Serialize_ScriptError("JS_ValueToBoolean failed"); - m_Serializer.Bool("value", b == JS_TRUE); + bool b = JS::ToBoolean(val); + m_Serializer.Bool("value", b); break; } else @@ -240,22 +235,23 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) // (Note that we don't do any rooting, because we assume nothing is going to trigger GC. // I'm not absolute certain that's necessarily a valid assumption.) - AutoJSIdArray ida (cx, JS_Enumerate(cx, obj)); - if (!ida.get()) + JS::AutoIdArray ida (cx, JS_Enumerate(cx, obj)); + if (!ida) throw PSERROR_Serialize_ScriptError("JS_Enumerate failed"); - m_Serializer.NumberU32_Unbounded("num props", (uint32_t)ida.length()); + m_Serializer.NumberU32_Unbounded("num props", (u32)ida.length()); for (size_t i = 0; i < ida.length(); ++i) { jsid id = ida[i]; - jsval idval, propval; - + JS::RootedValue idval(cx); + JS::RootedValue propval(cx); + // Get the property name as a string - if (!JS_IdToValue(cx, id, &idval)) + if (!JS_IdToValue(cx, id, idval.address())) throw PSERROR_Serialize_ScriptError("JS_IdToValue failed"); - JSString* idstr = JS_ValueToString(cx, idval); + JSString* idstr = JS_ValueToString(cx, idval.get()); if (!idstr) throw PSERROR_Serialize_ScriptError("JS_ValueToString failed"); @@ -263,7 +259,7 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) // Use LookupProperty instead of GetProperty to avoid the danger of getters // (they might delete values and trigger GC) - if (!JS_LookupPropertyById(cx, obj, id, &propval)) + if (!JS_LookupPropertyById(cx, obj, id, propval.address())) throw PSERROR_Serialize_ScriptError("JS_LookupPropertyById failed"); HandleScriptVal(propval); @@ -294,50 +290,40 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) case JSTYPE_STRING: { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_STRING); - ScriptString("string", JSVAL_TO_STRING(val)); + ScriptString("string", val.toString()); break; } case JSTYPE_NUMBER: { - // For efficiency, handle ints and doubles separately. - if (JSVAL_IS_INT(val)) + // To reduce the size of the serialized data, we handle integers and doubles separately. + // We can't check for val.isInt32 and val.isDouble directly, because integer numbers are not guaranteed + // to be represented as integers. A number like 33 could be stored as integer on the computer of one player + // and as double on the other player's computer. That would cause out of sync errors in multiplayer games because + // their binary representation and thus the hash would be different. + + double d; + d = val.toNumber(); + i32 integer; + + if (JS_DoubleIsInt32(d, &integer)) { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_INT); - m_Serializer.NumberI32_Unbounded("value", (int32_t)JSVAL_TO_INT(val)); + m_Serializer.NumberI32_Unbounded("value", integer); } else { - ENSURE(JSVAL_IS_DOUBLE(val)); - - // If the value fits in an int, serialise as an int - jsdouble d = JSVAL_TO_DOUBLE(val); - int32_t i; - if (JSDOUBLE_IS_INT32(d, &i)) - { - m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_INT); - m_Serializer.NumberI32_Unbounded("value", i); - } - // Otherwise serialise as a double - else - { - m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_DOUBLE); - m_Serializer.NumberDouble_Unbounded("value", d); - } + m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_DOUBLE); + m_Serializer.NumberDouble_Unbounded("value", d); } break; } case JSTYPE_BOOLEAN: { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BOOLEAN); - JSBool b = JSVAL_TO_BOOLEAN(val); + bool b = JSVAL_TO_BOOLEAN(val); m_Serializer.NumberU8_Unbounded("value", b ? 1 : 0); break; } - case JSTYPE_XML: - { - LOGERROR(L"Cannot serialise JS objects of type 'xml'"); - throw PSERROR_Serialize_InvalidScriptValue(); - } default: { debug_warn(L"Invalid TypeOfValue"); @@ -349,6 +335,8 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val) void CBinarySerializerScriptImpl::ScriptString(const char* name, JSString* string) { JSContext* cx = m_ScriptInterface.GetContext(); + JSAutoRequest rq(cx); + size_t length; const jschar* chars = JS_GetStringCharsAndLength(cx, string, &length); @@ -360,7 +348,7 @@ void CBinarySerializerScriptImpl::ScriptString(const char* name, JSString* strin #endif // Serialize strings directly as UTF-16, to avoid expensive encoding conversions - m_Serializer.NumberU32_Unbounded("string length", (uint32_t)length); + m_Serializer.NumberU32_Unbounded("string length", (u32)length); m_Serializer.RawBytes(name, (const u8*)chars, length*2); } diff --git a/source/simulation2/serialization/StdDeserializer.cpp b/source/simulation2/serialization/StdDeserializer.cpp index 93dabac5a0..928a9121eb 100644 --- a/source/simulation2/serialization/StdDeserializer.cpp +++ b/source/simulation2/serialization/StdDeserializer.cpp @@ -25,37 +25,8 @@ #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptExtraHeaders.h" // for typed arrays -#include "js/jsapi.h" - #include "lib/byte_order.h" -static uint32 GetJSArrayType(u8 arrayType) -{ - switch(arrayType) - { - case SCRIPT_TYPED_ARRAY_INT8: - return js::TypedArray::TYPE_INT8; - case SCRIPT_TYPED_ARRAY_UINT8: - return js::TypedArray::TYPE_UINT8; - case SCRIPT_TYPED_ARRAY_INT16: - return js::TypedArray::TYPE_INT16; - case SCRIPT_TYPED_ARRAY_UINT16: - return js::TypedArray::TYPE_UINT16; - case SCRIPT_TYPED_ARRAY_INT32: - return js::TypedArray::TYPE_INT32; - case SCRIPT_TYPED_ARRAY_UINT32: - return js::TypedArray::TYPE_UINT32; - case SCRIPT_TYPED_ARRAY_FLOAT32: - return js::TypedArray::TYPE_FLOAT32; - case SCRIPT_TYPED_ARRAY_FLOAT64: - return js::TypedArray::TYPE_FLOAT64; - case SCRIPT_TYPED_ARRAY_UINT8_CLAMPED: - return js::TypedArray::TYPE_UINT8_CLAMPED; - default: - throw PSERROR_Deserialize_ScriptError("Failed to deserialize unrecognized typed array view"); - } -} - CStdDeserializer::CStdDeserializer(ScriptInterface& scriptInterface, std::istream& stream) : m_ScriptInterface(scriptInterface), m_Stream(stream) { @@ -138,11 +109,13 @@ void CStdDeserializer::SetReservedScriptBackref(u32 tag, JSObject* obj) void CStdDeserializer::FreeScriptBackrefs() { + JSContext* cx = m_ScriptInterface.GetContext(); + JSAutoRequest rq(cx); + std::map::iterator it = m_ScriptBackrefs.begin(); for (; it != m_ScriptBackrefs.end(); ++it) { - if (!JS_RemoveObjectRoot(m_ScriptInterface.GetContext(), &it->second)) - throw PSERROR_Deserialize_ScriptError("JS_RemoveRoot failed"); + JS_RemoveObjectRoot(m_ScriptInterface.GetContext(), &it->second); } m_ScriptBackrefs.clear(); } @@ -153,6 +126,8 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append { JSContext* cx = m_ScriptInterface.GetContext(); + JSAutoRequest rq(cx); + uint8_t type; NumberU8_Unbounded("type", type); switch (type) @@ -192,25 +167,25 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append if (!proto) throw PSERROR_Deserialize_ScriptError("Failed to find serializable prototype for object"); - JSObject* parent = JS_GetParent(cx, proto); + JSObject* parent = JS_GetParent(proto); if (!proto || !parent) throw PSERROR_Deserialize_ScriptError(); obj = JS_NewObject(cx, NULL, proto, parent); if (!obj) throw PSERROR_Deserialize_ScriptError("JS_NewObject failed"); - CScriptValRooted objRoot(cx, OBJECT_TO_JSVAL(obj)); + CScriptValRooted objRoot(cx, JS::ObjectValue(*obj)); // Does it have custom Deserialize function? // if so, we let it handle the deserialized data, rather than adding properties directly JSBool hasCustomDeserialize, hasCustomSerialize; if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize) || !JS_HasProperty(cx, obj, "Deserialize", &hasCustomDeserialize)) throw PSERROR_Serialize_ScriptError("JS_HasProperty failed"); - + if (hasCustomDeserialize) { - jsval serialize; - if (!JS_LookupProperty(cx, obj, "Serialize", &serialize)) + JS::RootedValue serialize(cx); + if (!JS_LookupProperty(cx, obj, "Serialize", serialize.address())) throw PSERROR_Serialize_ScriptError("JS_LookupProperty failed"); bool hasNullSerialize = hasCustomSerialize && JSVAL_IS_NULL(serialize); @@ -219,17 +194,17 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append if (!hasNullSerialize) ScriptVal("data", data); - m_ScriptInterface.CallFunctionVoid(OBJECT_TO_JSVAL(obj), "Deserialize", data); + m_ScriptInterface.CallFunctionVoid(JS::ObjectValue(*obj), "Deserialize", data); AddScriptBackref(obj); - return OBJECT_TO_JSVAL(obj); + return JS::ObjectValue(*obj); } } if (!obj) throw PSERROR_Deserialize_ScriptError("Deserializer failed to create new object"); - CScriptValRooted objRoot(cx, OBJECT_TO_JSVAL(obj)); + CScriptValRooted objRoot(cx, JS::ObjectValue(*obj)); AddScriptBackref(obj); @@ -241,33 +216,33 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append utf16string propname; ReadStringUTF16("prop name", propname); - jsval propval = ReadScriptVal("prop value", NULL); + JS::RootedValue propval(cx, ReadScriptVal("prop value", NULL)); CScriptValRooted propvalRoot(cx, propval); - if (!JS_SetUCProperty(cx, obj, (const jschar*)propname.data(), propname.length(), &propval)) + if (!JS_SetUCProperty(cx, obj, (const jschar*)propname.data(), propname.length(), propval.address())) throw PSERROR_Deserialize_ScriptError(); } - return OBJECT_TO_JSVAL(obj); + return JS::ObjectValue(*obj); } case SCRIPT_TYPE_STRING: { JSString* str; ScriptString("string", str); - return STRING_TO_JSVAL(str); + return JS::StringValue(str); } case SCRIPT_TYPE_INT: { int32_t value; NumberI32("value", value, JSVAL_INT_MIN, JSVAL_INT_MAX); - return INT_TO_JSVAL(value); + return JS::NumberValue(value); } case SCRIPT_TYPE_DOUBLE: { double value; NumberDouble_Unbounded("value", value); - jsval rval; - if (!JS_NewNumberValue(cx, value, &rval)) + jsval rval = JS::NumberValue(value); + if (JSVAL_IS_NULL(rval)) throw PSERROR_Deserialize_ScriptError("JS_NewNumberValue failed"); return rval; } @@ -275,7 +250,7 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append { uint8_t value; NumberU8("value", value, 0, 1); - return BOOLEAN_TO_JSVAL(value ? JS_TRUE : JS_FALSE); + return JS::BooleanValue(value ? true : false); } case SCRIPT_TYPE_BACKREF: { @@ -284,26 +259,24 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append JSObject* obj = GetScriptBackref(tag); if (!obj) throw PSERROR_Deserialize_ScriptError("Invalid backref tag"); - return OBJECT_TO_JSVAL(obj); + return JS::ObjectValue(*obj); } case SCRIPT_TYPE_OBJECT_NUMBER: { double value; NumberDouble_Unbounded("value", value); - jsval val; - if (!JS_NewNumberValue(cx, value, &val)) - throw PSERROR_Deserialize_ScriptError(); + JS::RootedValue val(cx, JS::NumberValue(value)); CScriptValRooted objRoot(cx, val); JSObject* ctorobj; - if (!JS_GetClassObject(cx, JS_GetGlobalObject(cx), JSProto_Number, &ctorobj)) + if (!JS_GetClassObject(cx, JS_GetGlobalForScopeChain(cx), JSProto_Number, &ctorobj)) throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed"); - JSObject* obj = JS_New(cx, ctorobj, 1, &val); + JSObject* obj = JS_New(cx, ctorobj, 1, val.address()); if (!obj) throw PSERROR_Deserialize_ScriptError("JS_New failed"); AddScriptBackref(obj); - return OBJECT_TO_JSVAL(obj); + return JS::ObjectValue(*obj); } case SCRIPT_TYPE_OBJECT_STRING: { @@ -311,35 +284,34 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append ScriptString("value", str); if (!str) throw PSERROR_Deserialize_ScriptError(); - jsval val = STRING_TO_JSVAL(str); + JS::RootedValue val(cx, JS::StringValue(str)); CScriptValRooted valRoot(cx, val); JSObject* ctorobj; - if (!JS_GetClassObject(cx, JS_GetGlobalObject(cx), JSProto_String, &ctorobj)) + if (!JS_GetClassObject(cx, JS_GetGlobalForScopeChain(cx), JSProto_String, &ctorobj)) throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed"); - JSObject* obj = JS_New(cx, ctorobj, 1, &val); + JSObject* obj = JS_New(cx, ctorobj, 1, val.address()); if (!obj) throw PSERROR_Deserialize_ScriptError("JS_New failed"); AddScriptBackref(obj); - return OBJECT_TO_JSVAL(obj); + return JS::ObjectValue(*obj); } case SCRIPT_TYPE_OBJECT_BOOLEAN: { bool value; Bool("value", value); - jsval val = BOOLEAN_TO_JSVAL(value ? JS_TRUE : JS_FALSE); - CScriptValRooted objRoot(cx, val); + JS::RootedValue val(cx, JS::BooleanValue(value)); JSObject* ctorobj; - if (!JS_GetClassObject(cx, JS_GetGlobalObject(cx), JSProto_Boolean, &ctorobj)) + if (!JS_GetClassObject(cx, JS_GetGlobalForScopeChain(cx), JSProto_Boolean, &ctorobj)) throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed"); - JSObject* obj = JS_New(cx, ctorobj, 1, &val); + JSObject* obj = JS_New(cx, ctorobj, 1, val.address()); if (!obj) throw PSERROR_Deserialize_ScriptError("JS_New failed"); AddScriptBackref(obj); - return OBJECT_TO_JSVAL(obj); + return JS::ObjectValue(*obj); } case SCRIPT_TYPE_TYPED_ARRAY: { @@ -354,46 +326,71 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append // Get buffer object jsval bufferVal = ReadScriptVal("buffer", NULL); - CScriptValRooted bufferValRoot(cx, bufferVal); - if (!JSVAL_IS_OBJECT(bufferVal)) + if (!bufferVal.isObject()) throw PSERROR_Deserialize_ScriptError(); - JSObject* bufferObj = JSVAL_TO_OBJECT(bufferVal); - if (!js_IsArrayBuffer(bufferObj)) + JSObject* bufferObj = &bufferVal.toObject(); + if (!JS_IsArrayBufferObject(bufferObj)) throw PSERROR_Deserialize_ScriptError("js_IsArrayBuffer failed"); - JSObject* arrayObj = js_CreateTypedArrayWithBuffer(cx, GetJSArrayType(arrayType), bufferObj, byteOffset, length); + JSObject* arrayObj; + switch(arrayType) + { + case SCRIPT_TYPED_ARRAY_INT8: + arrayObj = JS_NewInt8ArrayWithBuffer(cx, bufferObj, byteOffset, length); + break; + case SCRIPT_TYPED_ARRAY_UINT8: + arrayObj = JS_NewUint8ArrayWithBuffer(cx, bufferObj, byteOffset, length); + break; + case SCRIPT_TYPED_ARRAY_INT16: + arrayObj = JS_NewInt16ArrayWithBuffer(cx, bufferObj, byteOffset, length); + break; + case SCRIPT_TYPED_ARRAY_UINT16: + arrayObj = JS_NewUint16ArrayWithBuffer(cx, bufferObj, byteOffset, length); + break; + case SCRIPT_TYPED_ARRAY_INT32: + arrayObj = JS_NewInt32ArrayWithBuffer(cx, bufferObj, byteOffset, length); + break; + case SCRIPT_TYPED_ARRAY_UINT32: + arrayObj = JS_NewUint32ArrayWithBuffer(cx, bufferObj, byteOffset, length); + break; + case SCRIPT_TYPED_ARRAY_FLOAT32: + arrayObj = JS_NewFloat32ArrayWithBuffer(cx, bufferObj, byteOffset, length); + break; + case SCRIPT_TYPED_ARRAY_FLOAT64: + arrayObj = JS_NewFloat64ArrayWithBuffer(cx, bufferObj, byteOffset, length); + break; + case SCRIPT_TYPED_ARRAY_UINT8_CLAMPED: + arrayObj = JS_NewUint8ClampedArrayWithBuffer(cx, bufferObj, byteOffset, length); + break; + default: + throw PSERROR_Deserialize_ScriptError("Failed to deserialize unrecognized typed array view"); + } if (!arrayObj) throw PSERROR_Deserialize_ScriptError("js_CreateTypedArrayWithBuffer failed"); SetReservedScriptBackref(arrayTag, arrayObj); - return OBJECT_TO_JSVAL(arrayObj); + return JS::ObjectValue(*arrayObj); } case SCRIPT_TYPE_ARRAY_BUFFER: { u32 length; NumberU32_Unbounded("buffer length", length); + u8* bufferData = NULL; - u8* bufferData = new u8[length]; - RawBytes("buffer data", bufferData, length); #if BYTE_ORDER != LITTLE_ENDIAN #error TODO: need to convert JS ArrayBuffer data from little-endian #endif - - JSObject* bufferObj = js_CreateArrayBuffer(cx, length); - if (!bufferObj) - throw PSERROR_Deserialize_ScriptError("js_CreateArrayBuffer failed"); - + void* contents = NULL; + JS_AllocateArrayBufferContents(cx, length, &contents, &bufferData); + RawBytes("buffer data", bufferData, length); + JSObject* bufferObj = JS_NewArrayBufferWithContents(cx, contents); AddScriptBackref(bufferObj); - js::ArrayBuffer* buffer = js::ArrayBuffer::fromJSObject(bufferObj); - memcpy(buffer->data, bufferData, length); - delete[] bufferData; - - return OBJECT_TO_JSVAL(bufferObj); + return JS::ObjectValue(*bufferObj); } default: throw PSERROR_Deserialize_OutOfBounds(); @@ -440,7 +437,7 @@ void CStdDeserializer::ScriptVal(const char* name, CScriptValRooted& out) void CStdDeserializer::ScriptObjectAppend(const char* name, jsval& obj) { - if (!JSVAL_IS_OBJECT(obj)) + if (!obj.isObject()) throw PSERROR_Deserialize_ScriptError(); ReadScriptVal(name, JSVAL_TO_OBJECT(obj)); diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp index b6fb06df44..d035c6f379 100644 --- a/source/simulation2/system/ComponentManager.cpp +++ b/source/simulation2/system/ComponentManager.cpp @@ -146,6 +146,8 @@ bool CComponentManager::LoadScript(const VfsPath& filename, bool hotload) void CComponentManager::Script_RegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, std::string cname, CScriptVal ctor) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); + JSContext* cx = componentManager->m_ScriptInterface.GetContext(); + JSAutoRequest rq(cx); // Find the C++ component that wraps the interface int cidWrapper = componentManager->GetScriptWrapper(iid); @@ -234,7 +236,7 @@ void CComponentManager::Script_RegisterComponentType(ScriptInterface::CxPrivate* ctWrapper.dealloc, cname, schema, - CScriptValRooted(componentManager->m_ScriptInterface.GetContext(), ctor) + CScriptValRooted(cx, ctor) }; componentManager->m_ComponentTypesById[cid] = ct; @@ -242,13 +244,18 @@ void CComponentManager::Script_RegisterComponentType(ScriptInterface::CxPrivate* // Find all the ctor prototype's On* methods, and subscribe to the appropriate messages: - - CScriptVal proto; - if (!componentManager->m_ScriptInterface.GetProperty(ctor.get(), "prototype", proto)) + JS::RootedValue protoVal(cx); + if (!componentManager->m_ScriptInterface.GetPropertyJS(ctor.get(), "prototype", &protoVal)) return; // error std::vector methods; - if (!componentManager->m_ScriptInterface.EnumeratePropertyNamesWithPrefix(proto.get(), "On", methods)) + JS::RootedObject proto(cx); + if (!protoVal.isObjectOrNull()) + return; // error + + proto = protoVal.toObjectOrNull(); + + if (!componentManager->m_ScriptInterface.EnumeratePropertyNamesWithPrefix(protoVal, "On", methods)) return; // error for (std::vector::const_iterator it = methods.begin(); it != methods.end(); ++it) @@ -287,9 +294,11 @@ void CComponentManager::Script_RegisterComponentType(ScriptInterface::CxPrivate* std::map::const_iterator eit = comps.begin(); for (; eit != comps.end(); ++eit) { - jsval instance = eit->second->GetJSInstance(); - if (!JSVAL_IS_NULL(instance)) - componentManager->m_ScriptInterface.SetPrototype(instance, proto.get()); + JS::RootedValue instance(cx, eit->second->GetJSInstance()); + if (!instance.isNull()) + { + componentManager->m_ScriptInterface.SetPrototype(instance, protoVal); + } } } } @@ -616,7 +625,7 @@ IComponent* CComponentManager::ConstructComponent(CEntityHandle ent, ComponentTy jsval obj = JSVAL_NULL; if (ct.type == CT_Script) { - obj = m_ScriptInterface.CallConstructor(ct.ctor.get(), JSVAL_VOID); + obj = m_ScriptInterface.CallConstructor(ct.ctor.get(), 0, JSVAL_VOID); if (JSVAL_IS_VOID(obj)) { LOGERROR(L"Script component constructor failed"); diff --git a/source/simulation2/system/ComponentTest.h b/source/simulation2/system/ComponentTest.h index d6acf420b7..2631a7eda4 100644 --- a/source/simulation2/system/ComponentTest.h +++ b/source/simulation2/system/ComponentTest.h @@ -52,8 +52,8 @@ class ComponentTestHelper EComponentTypeId m_Cid; public: - ComponentTestHelper() : - m_Context(), m_ComponentManager(m_Context, ScriptInterface::CreateRuntime()), m_Cmp(NULL) + ComponentTestHelper(shared_ptr runtime) : + m_Context(), m_ComponentManager(m_Context, runtime), m_Cmp(NULL) { m_ComponentManager.LoadComponentTypes(); } @@ -135,7 +135,7 @@ public: CStdSerializer std1(GetScriptInterface(), stdstr1); m_Cmp->Serialize(std1); - ComponentTestHelper test2; + ComponentTestHelper test2(GetScriptInterface().GetRuntime()); // (We should never need to add any mock objects etc to test2, since deserialization // mustn't depend on other components already existing) diff --git a/source/simulation2/system/InterfaceScripted.h b/source/simulation2/system/InterfaceScripted.h index 0b3d9e847b..f589849284 100644 --- a/source/simulation2/system/InterfaceScripted.h +++ b/source/simulation2/system/InterfaceScripted.h @@ -19,14 +19,12 @@ #define INCLUDED_INTERFACE_SCRIPTED #include "scriptinterface/ScriptInterface.h" -#include "js/jsapi.h" #define BEGIN_INTERFACE_WRAPPER(iname) \ JSClass class_ICmp##iname = { \ "ICmp" #iname, JSCLASS_HAS_PRIVATE, \ - JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub, \ - JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, \ - JSCLASS_NO_OPTIONAL_MEMBERS \ + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, \ + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub \ }; \ static JSFunctionSpec methods_ICmp##iname[] = { @@ -35,7 +33,8 @@ }; \ void ICmp##iname::InterfaceInit(ScriptInterface& scriptInterface) { \ JSContext* cx = scriptInterface.GetContext(); \ - JSObject* global = JS_GetGlobalObject(cx); \ + JSAutoRequest rq(cx); \ + JSObject* global = JS_GetGlobalForScopeChain(cx); \ JS_InitClass(cx, global, NULL, &class_ICmp##iname, NULL, 0, NULL, methods_ICmp##iname, NULL, NULL); \ } \ JSClass* ICmp##iname::GetJSClass() const { return &class_ICmp##iname; } \ @@ -45,49 +44,49 @@ #define DEFINE_INTERFACE_METHOD_0(scriptname, rettype, classname, methodname) \ { scriptname, \ - ScriptInterface::callMethod, \ + { ScriptInterface::callMethod, NULL }, \ 0, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT }, #define DEFINE_INTERFACE_METHOD_1(scriptname, rettype, classname, methodname, arg1) \ { scriptname, \ - ScriptInterface::callMethod, \ + { ScriptInterface::callMethod, NULL}, \ 1, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT }, #define DEFINE_INTERFACE_METHOD_2(scriptname, rettype, classname, methodname, arg1, arg2) \ { scriptname, \ - ScriptInterface::callMethod, \ + { ScriptInterface::callMethod, NULL }, \ 2, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT }, #define DEFINE_INTERFACE_METHOD_3(scriptname, rettype, classname, methodname, arg1, arg2, arg3) \ { scriptname, \ - ScriptInterface::callMethod, \ + { ScriptInterface::callMethod, NULL }, \ 3, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT }, #define DEFINE_INTERFACE_METHOD_4(scriptname, rettype, classname, methodname, arg1, arg2, arg3, arg4) \ { scriptname, \ - ScriptInterface::callMethod, \ + { ScriptInterface::callMethod, NULL }, \ 4, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT }, #define DEFINE_INTERFACE_METHOD_5(scriptname, rettype, classname, methodname, arg1, arg2, arg3, arg4, arg5) \ { scriptname, \ - ScriptInterface::callMethod, \ + { ScriptInterface::callMethod, NULL }, \ 5, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT }, #define DEFINE_INTERFACE_METHOD_6(scriptname, rettype, classname, methodname, arg1, arg2, arg3, arg4, arg5, arg6) \ { scriptname, \ - ScriptInterface::callMethod, \ + { ScriptInterface::callMethod, NULL }, \ 6, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT }, #define DEFINE_INTERFACE_METHOD_7(scriptname, rettype, classname, methodname, arg1, arg2, arg3, arg4, arg5, arg6, arg7) \ { scriptname, \ - ScriptInterface::callMethod, \ + { ScriptInterface::callMethod, NULL }, \ 7, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT }, diff --git a/source/simulation2/system/ParamNode.cpp b/source/simulation2/system/ParamNode.cpp index 97da12f595..67c404a727 100644 --- a/source/simulation2/system/ParamNode.cpp +++ b/source/simulation2/system/ParamNode.cpp @@ -25,8 +25,6 @@ #include "ps/Filesystem.h" #include "ps/XML/Xeromyces.h" -#include "js/jsapi.h" - #include // Disable "'boost::algorithm::detail::is_classifiedF' : assignment operator could not be generated" @@ -328,6 +326,7 @@ jsval CParamNode::ToJSVal(JSContext* cx, bool cacheValue) const jsval CParamNode::ConstructJSVal(JSContext* cx) const { + JSAutoRequest rq(cx); if (m_Childs.empty()) { // Empty node - map to undefined @@ -351,8 +350,8 @@ jsval CParamNode::ConstructJSVal(JSContext* cx) const for (std::map::const_iterator it = m_Childs.begin(); it != m_Childs.end(); ++it) { - jsval childVal = it->second.ConstructJSVal(cx); - if (!JS_SetProperty(cx, obj, it->first.c_str(), &childVal)) + JS::RootedValue childVal(cx, it->second.ConstructJSVal(cx)); + if (!JS_SetProperty(cx, obj, it->first.c_str(), childVal.address())) return JSVAL_VOID; // TODO: report error } @@ -363,8 +362,8 @@ jsval CParamNode::ConstructJSVal(JSContext* cx) const JSString* str = JS_InternUCStringN(cx, reinterpret_cast(text.data()), text.length()); if (!str) return JSVAL_VOID; // TODO: report error - jsval childVal = STRING_TO_JSVAL(str); - if (!JS_SetProperty(cx, obj, "_string", &childVal)) + JS::RootedValue childVal(cx, STRING_TO_JSVAL(str)); + if (!JS_SetProperty(cx, obj, "_string", childVal.address())) return JSVAL_VOID; // TODO: report error } diff --git a/source/simulation2/tests/test_CmpTemplateManager.h b/source/simulation2/tests/test_CmpTemplateManager.h index 72c1eaca74..5f3d082f99 100644 --- a/source/simulation2/tests/test_CmpTemplateManager.h +++ b/source/simulation2/tests/test_CmpTemplateManager.h @@ -127,25 +127,26 @@ public: TS_ASSERT(tempMan != NULL); tempMan->DisableValidation(); - CScriptValRooted val; JSContext* cx = man.GetScriptInterface().GetContext(); + JSAutoRequest rq(cx); // This is testing some bugs in the template JS object caching const CParamNode* inherit1 = tempMan->LoadTemplate(ent2, "inherit1", -1); - val = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, inherit1)); + JS::RootedValue val(cx); + ScriptInterface::ToJSVal(cx, val.get(), inherit1); TS_ASSERT_WSTR_EQUALS(man.GetScriptInterface().ToString(val.get()), L"({Test1A:{'@a':\"a1\", '@b':\"b1\", '@c':\"c1\", d:\"d1\", e:\"e1\", f:\"f1\"}})"); const CParamNode* inherit2 = tempMan->LoadTemplate(ent2, "inherit2", -1); - val = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, inherit2)); + ScriptInterface::ToJSVal(cx, val.get(), inherit2); TS_ASSERT_WSTR_EQUALS(man.GetScriptInterface().ToString(val.get()), L"({'@parent':\"inherit1\", Test1A:{'@a':\"a2\", '@b':\"b1\", '@c':\"c1\", d:\"d2\", e:\"e1\", f:\"f1\", g:\"g2\"}})"); const CParamNode* actor = tempMan->LoadTemplate(ent2, "actor|example1", -1); - val = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, &actor->GetChild("VisualActor"))); + ScriptInterface::ToJSVal(cx, val.get(), &actor->GetChild("VisualActor")); TS_ASSERT_WSTR_EQUALS(man.GetScriptInterface().ToString(val.get()), L"({Actor:\"example1\", ActorOnly:(void 0), SilhouetteDisplay:\"false\", SilhouetteOccluder:\"false\", VisibleInAtlasOnly:\"false\"})"); const CParamNode* foundation = tempMan->LoadTemplate(ent2, "foundation|actor|example1", -1); - val = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, &foundation->GetChild("VisualActor"))); + ScriptInterface::ToJSVal(cx, val.get(), &foundation->GetChild("VisualActor")); TS_ASSERT_WSTR_EQUALS(man.GetScriptInterface().ToString(val.get()), L"({Actor:\"example1\", ActorOnly:(void 0), Foundation:(void 0), SilhouetteDisplay:\"false\", SilhouetteOccluder:\"false\", VisibleInAtlasOnly:\"false\"})"); } diff --git a/source/simulation2/tests/test_ComponentManager.h b/source/simulation2/tests/test_ComponentManager.h index 31f6867e9e..4850b566cf 100644 --- a/source/simulation2/tests/test_ComponentManager.h +++ b/source/simulation2/tests/test_ComponentManager.h @@ -594,7 +594,8 @@ public: void test_serialization() { CSimContext context; - CComponentManager man(context, ScriptInterface::CreateRuntime()); + shared_ptr runtime = ScriptInterface::CreateRuntime(); + CComponentManager man(context, runtime); man.LoadComponentTypes(); entity_id_t ent1 = 1, ent2 = 2, ent3 = FIRST_LOCAL_ENTITY; @@ -664,7 +665,7 @@ public: ); CSimContext context2; - CComponentManager man2(context2, ScriptInterface::CreateRuntime()); + CComponentManager man2(context2, runtime); man2.LoadComponentTypes(); TS_ASSERT(man2.QueryInterface(ent1, IID_Test1) == NULL); @@ -683,7 +684,9 @@ public: void test_script_serialization() { CSimContext context; - CComponentManager man(context, ScriptInterface::CreateRuntime()); + shared_ptr runtime = ScriptInterface::CreateRuntime(); + + CComponentManager man(context, runtime); ScriptTestSetup(man.m_ScriptInterface); man.LoadComponentTypes(); TS_ASSERT(man.LoadScript(L"simulation/components/test-serialize.js")); @@ -700,7 +703,12 @@ public: man.AddComponent(hnd1, man.LookupCID("TestScript1_values"), testParam); man.AddComponent(hnd2, man.LookupCID("TestScript1_entity"), testParam); + + // TODO: Since the upgrade to SpiderMonkey v24 this test won't be able to correctly represent + // non-tree structures because sharp variables were removed (bug 566700). + // This also affects the debug serializer and it could make sense to implement correct serialization again. man.AddComponent(hnd3, man.LookupCID("TestScript1_nontree"), testParam); + man.AddComponent(hnd4, man.LookupCID("TestScript1_custom"), testParam); TS_ASSERT_EQUALS(static_cast (man.QueryInterface(ent1, IID_Test1))->GetX(), 1234); @@ -713,48 +721,48 @@ public: std::stringstream debugStream; TS_ASSERT(man.DumpDebugState(debugStream, true)); TS_ASSERT_STR_EQUALS(debugStream.str(), - "rng: \"78606\"\n" - "entities:\n" - "- id: 1\n" - " TestScript1_values:\n" - " object: {\n" - " \"x\": 1234,\n" - " \"str\": \"this is a string\",\n" - " \"things\": {\n" - " \"a\": 1,\n" - " \"b\": \"2\",\n" - " \"c\": [\n" - " 3,\n" - " \"4\",\n" - " [\n" - " 5,\n" - " []\n" - " ]\n" - " ]\n" - " }\n" - "}\n" - "\n" - "- id: 2\n" - " TestScript1_entity:\n" - " object: {}\n" - "\n" - "- id: 3\n" - " TestScript1_nontree:\n" - " object: ({x:#2=[#1=[2], #1#, #2#, {y:#1#}]})\n" - "\n" - "- id: 4\n" - " TestScript1_custom:\n" - " object: {\n" - " \"c\": 1\n" - "}\n" - "\n" + "rng: \"78606\"\n\ +entities:\n\ +- id: 1\n\ + TestScript1_values:\n\ + object: {\n\ + \"x\": 1234,\n\ + \"str\": \"this is a string\",\n\ + \"things\": {\n\ + \"a\": 1,\n\ + \"b\": \"2\",\n\ + \"c\": [\n\ + 3,\n\ + \"4\",\n\ + [\n\ + 5,\n\ + []\n\ + ]\n\ + ]\n\ + }\n\ +}\n\ +\n\ +- id: 2\n\ + TestScript1_entity:\n\ + object: {}\n\ +\n\ +- id: 3\n\ + TestScript1_nontree:\n\ + object: ({x:[[2], [2], [], {y:[2]}]})\n\ +\n\ +- id: 4\n\ + TestScript1_custom:\n\ + object: {\n\ + \"c\": 1\n\ +}\n\ +\n" ); std::stringstream stateStream; TS_ASSERT(man.SerializeState(stateStream)); CSimContext context2; - CComponentManager man2(context2, ScriptInterface::CreateRuntime()); + CComponentManager man2(context2, runtime); man2.LoadComponentTypes(); TS_ASSERT(man2.LoadScript(L"simulation/components/test-serialize.js")); @@ -789,7 +797,9 @@ public: void test_script_serialization_template() { CSimContext context; - CComponentManager man(context, ScriptInterface::CreateRuntime()); + shared_ptr runtime = ScriptInterface::CreateRuntime(); + + CComponentManager man(context, runtime); man.LoadComponentTypes(); TS_ASSERT(man.LoadScript(L"simulation/components/test-serialize.js")); man.InitSystemEntity(); @@ -813,7 +823,7 @@ public: TS_ASSERT(man.SerializeState(stateStream)); CSimContext context2; - CComponentManager man2(context2, ScriptInterface::CreateRuntime()); + CComponentManager man2(context2, runtime); man2.LoadComponentTypes(); TS_ASSERT(man2.LoadScript(L"simulation/components/test-serialize.js")); diff --git a/source/simulation2/tests/test_Serializer.h b/source/simulation2/tests/test_Serializer.h index 21d2b227e3..5be347fb77 100644 --- a/source/simulation2/tests/test_Serializer.h +++ b/source/simulation2/tests/test_Serializer.h @@ -574,9 +574,6 @@ public: TestLogger logger; - TS_ASSERT(script.Eval("({x:1, y:})", obj)); - TS_ASSERT_THROWS(serialize.ScriptVal("script", obj), PSERROR_Serialize_InvalidScriptValue); - TS_ASSERT(script.Eval("([1, 2, function () { }])", obj)); TS_ASSERT_THROWS(serialize.ScriptVal("script", obj), PSERROR_Serialize_InvalidScriptValue); }