From a2bd44b23a8107aa00d83b719df1eb18ff5592a1 Mon Sep 17 00:00:00 2001 From: Yves Date: Sat, 2 Aug 2014 22:21:50 +0000 Subject: [PATCH] Exact stack rooting for JSON related ScriptInterface functions. Refs #2415 Refs #2462 This was SVN commit r15603. --- source/graphics/MapGenerator.cpp | 3 ++- source/graphics/MapReader.cpp | 10 ++++++-- source/gui/GUIManager.cpp | 6 +++-- source/gui/scripting/ScriptFunctions.cpp | 8 +++++-- source/main.cpp | 2 +- source/network/NetServer.cpp | 23 +++++++++++++----- source/network/NetServer.h | 4 ++-- source/network/tests/test_Net.h | 18 +++++++++----- source/ps/Game.cpp | 2 +- source/ps/GameSetup/GameSetup.cpp | 14 +++++------ source/ps/GameSetup/HWDetect.cpp | 2 +- source/ps/Replay.cpp | 24 +++++++++++++------ source/ps/Replay.h | 6 +++-- source/ps/SavedGame.cpp | 9 +++++-- source/ps/TemplateLoader.cpp | 3 ++- source/scriptinterface/ScriptInterface.cpp | 24 +++++++++++-------- source/scriptinterface/ScriptInterface.h | 10 ++++---- .../tests/test_ScriptInterface.h | 10 ++++---- source/simulation2/Simulation2.cpp | 15 +++++++++--- .../simulation2/components/CCmpAIManager.cpp | 11 +++++---- .../components/CCmpCommandQueue.cpp | 8 +++++-- .../simulation2/components/ICmpAIManager.cpp | 4 +++- .../simulation2/system/ComponentManager.cpp | 9 ++++--- source/simulation2/system/ComponentManager.h | 2 +- .../GameInterface/Handlers/MapHandlers.cpp | 3 ++- 25 files changed, 151 insertions(+), 79 deletions(-) diff --git a/source/graphics/MapGenerator.cpp b/source/graphics/MapGenerator.cpp index ad47b8647d..c29d656ac8 100644 --- a/source/graphics/MapGenerator.cpp +++ b/source/graphics/MapGenerator.cpp @@ -108,7 +108,8 @@ bool CMapGeneratorWorker::Run() m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindActorTemplates>("FindActorTemplates"); // Parse settings - JS::RootedValue settingsVal(cx, m_ScriptInterface->ParseJSON(m_Settings).get()); + JS::RootedValue settingsVal(cx); + m_ScriptInterface->ParseJSON(m_Settings, &settingsVal); if (settingsVal.isUndefined()) { LOGERROR(L"CMapGeneratorWorker::Run: Failed to parse settings"); diff --git a/source/graphics/MapReader.cpp b/source/graphics/MapReader.cpp index 5464aaea6b..090b70e47d 100644 --- a/source/graphics/MapReader.cpp +++ b/source/graphics/MapReader.cpp @@ -392,7 +392,11 @@ CScriptValRooted CMapSummaryReader::GetMapSettings(ScriptInterface& scriptInterf JS::RootedValue data(cx); scriptInterface.Eval("({})", &data); if (!m_ScriptSettings.empty()) - scriptInterface.SetProperty(data, "settings", scriptInterface.ParseJSON(m_ScriptSettings), false); + { + JS::RootedValue scriptSettingsVal(cx); + scriptInterface.ParseJSON(m_ScriptSettings, &scriptSettingsVal); + scriptInterface.SetProperty(data, "settings", scriptSettingsVal, false); + } return CScriptValRooted(cx, data); } @@ -1269,8 +1273,10 @@ int CMapReader::GenerateMap() if (m_ScriptFile.length()) scriptPath = L"maps/random/"+m_ScriptFile; + // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade + JS::RootedValue tmpScriptSettings(cx, m_ScriptSettings.get()); // Stringify settings to pass across threads - std::string scriptSettings = pSimulation2->GetScriptInterface().StringifyJSON(m_ScriptSettings.get()); + std::string scriptSettings = pSimulation2->GetScriptInterface().StringifyJSON(&tmpScriptSettings); // Try to generate map m_MapGen->GenerateMap(scriptPath, scriptSettings); diff --git a/source/gui/GUIManager.cpp b/source/gui/GUIManager.cpp index 6f707d0bd7..ccd85094ef 100644 --- a/source/gui/GUIManager.cpp +++ b/source/gui/GUIManager.cpp @@ -294,7 +294,7 @@ std::string CGUIManager::GetSavedGameData() JS::RootedValue data(cx); JS::RootedValue global(cx, top()->GetGlobalObject()); scriptInterface->CallFunction(global, "getSavedGameData", &data); - return scriptInterface->StringifyJSON(data, false); + return scriptInterface->StringifyJSON(&data, false); } void CGUIManager::RestoreSavedGameData(std::string jsonData) @@ -304,7 +304,9 @@ void CGUIManager::RestoreSavedGameData(std::string jsonData) JSAutoRequest rq(cx); JS::RootedValue global(cx, top()->GetGlobalObject()); - scriptInterface->CallFunctionVoid(global, "restoreSavedGameData", scriptInterface->ParseJSON(jsonData)); + JS::RootedValue dataVal(cx); + scriptInterface->ParseJSON(jsonData, &dataVal); + scriptInterface->CallFunctionVoid(global, "restoreSavedGameData", dataVal); } InReaction CGUIManager::HandleEvent(const SDL_Event_* ev) diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 1bf25ef7e1..0a54384b0e 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -298,11 +298,15 @@ void SaveGamePrefix(ScriptInterface::CxPrivate* pCxPrivate, std::wstring prefix, LOGERROR(L"Failed to save game"); } -void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal attribs) +void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal attribs1) { ENSURE(g_NetServer); + // TODO: Get Handle parameter directly with SpiderMonkey 31 + JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); + JSAutoRequest rq(cx); + JS::RootedValue attribs(cx, attribs1.get()); - g_NetServer->UpdateGameAttributes(attribs, *(pCxPrivate->pScriptInterface)); + g_NetServer->UpdateGameAttributes(&attribs, *(pCxPrivate->pScriptInterface)); } void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, std::wstring playerName) diff --git a/source/main.cpp b/source/main.cpp index d2d9dec193..62ba5865bb 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -190,7 +190,7 @@ static void PumpEvents() { JS::RootedValue tmpVal(cx); ScriptInterface::ToJSVal(cx, &tmpVal, ev); - std::string data = g_GUI->GetScriptInterface()->StringifyJSON(tmpVal); + 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 e4809e87b1..fb50bbec95 100644 --- a/source/network/NetServer.cpp +++ b/source/network/NetServer.cpp @@ -375,6 +375,9 @@ bool CNetServerWorker::RunStep() // Check for messages from the game thread. // (Do as little work as possible while the mutex is held open, // to avoid performance problems and deadlocks.) + + JSContext* cx = m_ScriptInterface->GetContext(); + JSAutoRequest rq(cx); std::vector > newAssignPlayer; std::vector newStartGame; @@ -407,7 +410,11 @@ bool CNetServerWorker::RunStep() ClearAllPlayerReady(); if (!newGameAttributes.empty()) - UpdateGameAttributes(GetScriptInterface().ParseJSON(newGameAttributes.back())); + { + JS::RootedValue gameAttributesVal(cx); + GetScriptInterface().ParseJSON(newGameAttributes.back(), &gameAttributesVal); + UpdateGameAttributes(&gameAttributesVal); + } if (!newTurnLength.empty()) SetTurnLength(newTurnLength.back()); @@ -997,17 +1004,21 @@ void CNetServerWorker::StartGame() m_State = SERVER_STATE_LOADING; + JSContext* cx = GetScriptInterface().GetContext(); + JSAutoRequest rq(cx); + // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade + JS::RootedValue tmpGameAttributes(cx, m_GameAttributes.get()); // Send the final setup state to all clients - UpdateGameAttributes(m_GameAttributes); + UpdateGameAttributes(&tmpGameAttributes); SendPlayerAssignments(); CGameStartMessage gameStart; Broadcast(&gameStart); } -void CNetServerWorker::UpdateGameAttributes(const CScriptValRooted& attrs) +void CNetServerWorker::UpdateGameAttributes(JS::MutableHandleValue attrs) { - m_GameAttributes = attrs; + m_GameAttributes = CScriptValRooted(GetScriptInterface().GetContext(), attrs); if (!m_Host) return; @@ -1106,11 +1117,11 @@ void CNetServer::StartGame() m_Worker->m_StartGameQueue.push_back(true); } -void CNetServer::UpdateGameAttributes(const CScriptVal& attrs, ScriptInterface& scriptInterface) +void CNetServer::UpdateGameAttributes(JS::MutableHandleValue attrs, ScriptInterface& scriptInterface) { // Pass the attributes as JSON, since that's the easiest safe // cross-thread way of passing script data - std::string attrsJSON = scriptInterface.StringifyJSON(attrs.get(), false); + std::string attrsJSON = scriptInterface.StringifyJSON(attrs, false); CScopeLock lock(m_Worker->m_WorkerMutex); m_Worker->m_GameAttributesQueue.push_back(attrsJSON); diff --git a/source/network/NetServer.h b/source/network/NetServer.h index 60215602a3..1d5160819d 100644 --- a/source/network/NetServer.h +++ b/source/network/NetServer.h @@ -146,7 +146,7 @@ public: * The changes will be asynchronously propagated to all clients. * @param attrs game attributes, in the script context of scriptInterface */ - void UpdateGameAttributes(const CScriptVal& attrs, ScriptInterface& scriptInterface); + void UpdateGameAttributes(JS::MutableHandleValue attrs, ScriptInterface& scriptInterface); /** * Set the turn length to a fixed value. @@ -220,7 +220,7 @@ private: * The changes will be propagated to all clients. * @param attrs game attributes, in the script context of GetScriptInterface() */ - void UpdateGameAttributes(const CScriptValRooted& attrs); + void UpdateGameAttributes(JS::MutableHandleValue attrs); /** * Make a player name 'nicer' by limiting the length and removing forbidden characters etc. diff --git a/source/network/tests/test_Net.h b/source/network/tests/test_Net.h index 94440a729c..8c258cfa66 100644 --- a/source/network/tests/test_Net.h +++ b/source/network/tests/test_Net.h @@ -135,6 +135,9 @@ public: // and prints a load of debug output so you can see if anything funny's going on ScriptInterface scriptInterface("Engine", "Test", g_ScriptRuntime); + JSContext* cx = scriptInterface.GetContext(); + JSAutoRequest rq(cx); + TestStdoutLogger logger; std::vector clients; @@ -145,9 +148,9 @@ public: CNetServer server; - CScriptValRooted attrs; - scriptInterface.Eval("({mapType:'scenario',map:'_default',thing:'example'})", attrs); - server.UpdateGameAttributes(attrs.get(), scriptInterface); + JS::RootedValue attrs(cx); + scriptInterface.Eval("({mapType:'scenario',map:'_default',thing:'example'})", &attrs); + server.UpdateGameAttributes(&attrs, scriptInterface); CNetClient client1(&client1Game); CNetClient client2(&client2Game); @@ -197,6 +200,9 @@ public: void test_rejoin_DISABLED() { ScriptInterface scriptInterface("Engine", "Test", g_ScriptRuntime); + JSContext* cx = scriptInterface.GetContext(); + JSAutoRequest rq(cx); + TestStdoutLogger logger; std::vector clients; @@ -207,9 +213,9 @@ public: CNetServer server; - CScriptValRooted attrs; - scriptInterface.Eval("({mapType:'scenario',map:'_default',thing:'example'})", attrs); - server.UpdateGameAttributes(attrs.get(), scriptInterface); + JS::RootedValue attrs(cx); + scriptInterface.Eval("({mapType:'scenario',map:'_default',thing:'example'})", &attrs); + server.UpdateGameAttributes(&attrs, scriptInterface); CNetClient client1(&client1Game); CNetClient client2(&client2Game); diff --git a/source/ps/Game.cpp b/source/ps/Game.cpp index 34a0d48094..9f0aedd516 100644 --- a/source/ps/Game.cpp +++ b/source/ps/Game.cpp @@ -282,7 +282,7 @@ void CGame::StartGame(const CScriptValRooted& attribs1, const std::string& saved JSAutoRequest rq(cx); JS::RootedValue attribs(cx, attribs1.get()); // TODO: Get Handle parameter directly with SpiderMonkey 31 - m_ReplayLogger->StartGame(attribs1); + m_ReplayLogger->StartGame(&attribs); RegisterInit(attribs, savedState); } diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index c1e6960018..dcfbc1113d 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -1184,7 +1184,8 @@ bool Autostart(const CmdLineArgs& args) // Random map definition will be loaded from JSON file, so we need to parse it std::wstring scriptPath = L"maps/" + autoStartName.FromUTF8() + L".json"; - JS::RootedValue scriptData(cx, scriptInterface.ReadJSONFile(scriptPath).get()); + JS::RootedValue scriptData(cx); + scriptInterface.ReadJSONFile(scriptPath, &scriptData); if (!scriptData.isUndefined() && scriptInterface.GetProperty(scriptData, "settings", &settings)) { // JSON loaded ok - copy script name over to game attributes @@ -1239,9 +1240,7 @@ bool Autostart(const CmdLineArgs& args) // (Omitting this may cause the loading screen to display "Loading (undefined)", // for example...) CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml"); - CScriptValRooted mapSettings = scriptInterface.ParseJSON(mapSettingsJSON); - - settings = mapSettings.get(); + scriptInterface.ParseJSON(mapSettingsJSON, &settings); mapType = "scenario"; } else if (mapDirectory == L"skirmishes") @@ -1253,9 +1252,8 @@ bool Autostart(const CmdLineArgs& args) // To prevent this, we mimic the behavior of the game setup screen by // retrieving the map settings from the actual map xml... CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml"); - CScriptValRooted mapSettings = scriptInterface.ParseJSON(mapSettingsJSON); - - settings = mapSettings.get(); + scriptInterface.ParseJSON(mapSettingsJSON, &settings); + // ...and initialize the playerData array being edited by // autostart-civ et.al. with the real map data, so sensible values // are always present: @@ -1364,7 +1362,7 @@ bool Autostart(const CmdLineArgs& args) g_NetServer = new CNetServer(maxPlayers); - g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface); + g_NetServer->UpdateGameAttributes(&attrs, scriptInterface); bool ok = g_NetServer->SetupConnection(); ENSURE(ok); diff --git a/source/ps/GameSetup/HWDetect.cpp b/source/ps/GameSetup/HWDetect.cpp index c2f5a3b35e..0655afc4a8 100644 --- a/source/ps/GameSetup/HWDetect.cpp +++ b/source/ps/GameSetup/HWDetect.cpp @@ -320,7 +320,7 @@ void RunHardwareDetection() scriptInterface.SetProperty(settings, "timer_resolution", timer_Resolution()); // Send the same data to the reporting system - g_UserReporter.SubmitReport("hwdetect", 11, scriptInterface.StringifyJSON(settings, false)); + g_UserReporter.SubmitReport("hwdetect", 11, scriptInterface.StringifyJSON(&settings, false)); // Run the detection script: JS::RootedValue global(cx, scriptInterface.GetGlobalObject()); diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp index 89294a73de..48f5499232 100644 --- a/source/ps/Replay.cpp +++ b/source/ps/Replay.cpp @@ -75,17 +75,22 @@ CReplayLogger::~CReplayLogger() delete m_Stream; } -void CReplayLogger::StartGame(const CScriptValRooted& attribs) +void CReplayLogger::StartGame(JS::MutableHandleValue attribs) { - *m_Stream << "start " << m_ScriptInterface.StringifyJSON(attribs.get(), false) << "\n"; + *m_Stream << "start " << m_ScriptInterface.StringifyJSON(attribs, false) << "\n"; } void CReplayLogger::Turn(u32 n, u32 turnLength, const std::vector& commands) { + JSContext* cx = m_ScriptInterface.GetContext(); + JSAutoRequest rq(cx); + *m_Stream << "turn " << n << " " << turnLength << "\n"; for (size_t i = 0; i < commands.size(); ++i) { - *m_Stream << "cmd " << commands[i].player << " " << m_ScriptInterface.StringifyJSON(commands[i].data.get(), false) << "\n"; + // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade + JS::RootedValue tmpCommand(cx, commands[i].data.get()); + *m_Stream << "cmd " << commands[i].player << " " << m_ScriptInterface.StringifyJSON(&tmpCommand, false) << "\n"; } *m_Stream << "end\n"; m_Stream->flush(); @@ -133,6 +138,9 @@ void CReplayPlayer::Replay(bool serializationtest) g_Game = &game; if (serializationtest) game.GetSimulation2()->EnableSerializationTest(); + + JSContext* cx = game.GetSimulation2()->GetScriptInterface().GetContext(); + JSAutoRequest rq(cx); // Need some stuff for terrain movement costs: // (TODO: this ought to be independent of any graphics code) @@ -155,9 +163,10 @@ void CReplayPlayer::Replay(bool serializationtest) { std::string line; std::getline(*m_Stream, line); - CScriptValRooted attribs = game.GetSimulation2()->GetScriptInterface().ParseJSON(line); + JS::RootedValue attribs(cx); + game.GetSimulation2()->GetScriptInterface().ParseJSON(line, &attribs); - game.StartGame(attribs, ""); + game.StartGame(CScriptValRooted(cx, attribs), ""); // TODO: Non progressive load can fail - need a decent way to handle this LDR_NonprogressiveLoad(); @@ -177,9 +186,10 @@ void CReplayPlayer::Replay(bool serializationtest) std::string line; std::getline(*m_Stream, line); - CScriptValRooted data = game.GetSimulation2()->GetScriptInterface().ParseJSON(line); + JS::RootedValue data(cx); + game.GetSimulation2()->GetScriptInterface().ParseJSON(line, &data); - SimulationCommand cmd = { player, data }; + SimulationCommand cmd = { player, CScriptValRooted(cx, data) }; commands.push_back(cmd); } else if (type == "hash" || type == "hash-quick") diff --git a/source/ps/Replay.h b/source/ps/Replay.h index 9538e68f9b..c1a6aac61f 100644 --- a/source/ps/Replay.h +++ b/source/ps/Replay.h @@ -18,6 +18,8 @@ #ifndef INCLUDED_REPLAY #define INCLUDED_REPLAY +#include "scriptinterface/ScriptTypes.h" + class CScriptValRooted; struct SimulationCommand; class ScriptInterface; @@ -35,7 +37,7 @@ public: /** * Started the game with the given game attributes. */ - virtual void StartGame(const CScriptValRooted& attribs) = 0; + virtual void StartGame(JS::MutableHandleValue attribs) = 0; /** * Run the given turn with the given collection of player commands. @@ -69,7 +71,7 @@ public: CReplayLogger(ScriptInterface& scriptInterface); ~CReplayLogger(); - virtual void StartGame(const CScriptValRooted& attribs); + virtual void StartGame(JS::MutableHandleValue attribs); virtual void Turn(u32 n, u32 turnLength, const std::vector& commands); virtual void Hash(const std::string& hash, bool quick); diff --git a/source/ps/SavedGame.cpp b/source/ps/SavedGame.cpp index f863081432..c9a4cb4701 100644 --- a/source/ps/SavedGame.cpp +++ b/source/ps/SavedGame.cpp @@ -106,7 +106,7 @@ Status SavedGames::Save(const std::wstring& name, const std::wstring& descriptio simulation.GetScriptInterface().SetProperty(metadata, "description", description); - std::string metadataString = simulation.GetScriptInterface().StringifyJSON(metadata, true); + std::string metadataString = simulation.GetScriptInterface().StringifyJSON(&metadata, true); // Write the saved game as zip file containing the various components PIArchiveWriter archiveWriter = CreateArchiveWriter_Zip(tempSaveFileRealPath, false); @@ -161,12 +161,17 @@ public: void ReadEntry(const VfsPath& pathname, const CFileInfo& fileInfo, PIArchiveFile archiveFile) { + JSContext* cx = m_ScriptInterface.GetContext(); + JSAutoRequest rq(cx); + if (pathname == L"metadata.json") { std::string buffer; buffer.resize(fileInfo.Size()); WARN_IF_ERR(archiveFile->Load("", DummySharedPtr((u8*)buffer.data()), buffer.size())); - m_Metadata = m_ScriptInterface.ParseJSON(buffer); + JS::RootedValue tmpMetadata(cx); // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade + m_ScriptInterface.ParseJSON(buffer, &tmpMetadata); + m_Metadata = CScriptValRooted(cx, tmpMetadata); } else if (pathname == L"simulation.dat" && m_SavedState) { diff --git a/source/ps/TemplateLoader.cpp b/source/ps/TemplateLoader.cpp index 95d7f4454e..4ef7374912 100644 --- a/source/ps/TemplateLoader.cpp +++ b/source/ps/TemplateLoader.cpp @@ -203,7 +203,8 @@ std::vector CTemplateLoader::FindPlaceableTemplates(const std::stri if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES) { - JS::RootedValue placeablesFilter(cx, scriptInterface.ReadJSONFile("simulation/data/placeablesFilter.json").get()); + JS::RootedValue placeablesFilter(cx); + scriptInterface.ReadJSONFile("simulation/data/placeablesFilter.json", &placeablesFilter); std::vector folders; if (scriptInterface.GetProperty(placeablesFilter, "templates", folders)) diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp index 8b35fbabd9..f9464c6cb2 100644 --- a/source/scriptinterface/ScriptInterface.cpp +++ b/source/scriptinterface/ScriptInterface.cpp @@ -1238,23 +1238,24 @@ bool ScriptInterface::Eval_(const wchar_t* code, JS::MutableHandleValue rval) return ok; } -CScriptValRooted ScriptInterface::ParseJSON(const std::string& string_utf8) +void ScriptInterface::ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out) { JSAutoRequest rq(m->m_cx); std::wstring attrsW = wstring_from_utf8(string_utf8); 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)) + if (!JS_ParseJSON(m->m_cx, reinterpret_cast(string.c_str()), (u32)string.size(), out)) + { LOGERROR(L"JS_ParseJSON failed!"); - return CScriptValRooted(m->m_cx, vp); + return; + } } -CScriptValRooted ScriptInterface::ReadJSONFile(const VfsPath& path) +void ScriptInterface::ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out) { if (!VfsFileExists(path)) { LOGERROR(L"File '%ls' does not exist", path.string().c_str()); - return CScriptValRooted(); + return; } CVFSFile file; @@ -1264,12 +1265,12 @@ CScriptValRooted ScriptInterface::ReadJSONFile(const VfsPath& path) if (ret != PSRETURN_OK) { LOGERROR(L"Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret)); - return CScriptValRooted(); + return; } std::string content(file.DecodeUTF8()); // assume it's UTF-8 - return ParseJSON(content); + ParseJSON(content, out); } struct Stringifier @@ -1299,11 +1300,14 @@ struct StringifierW std::wstringstream stream; }; -std::string ScriptInterface::StringifyJSON(jsval obj, bool indent) +// TODO: It's not quite clear why JS_Stringify needs JS::MutableHandleValue. |obj| should not get modified. +// It probably has historical reasons and could be changed by SpiderMonkey in the future. +std::string ScriptInterface::StringifyJSON(JS::MutableHandleValue 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)) + JS::RootedValue indentVal(m->m_cx, indent ? JS::Int32Value(2) : JS::UndefinedValue()); + if (!JS_Stringify(m->m_cx, obj.address(), NULL, indentVal, &Stringifier::callback, &str)) { JS_ClearPendingException(m->m_cx); LOGERROR(L"StringifyJSON failed"); diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h index a587e6b190..26f5059004 100644 --- a/source/scriptinterface/ScriptInterface.h +++ b/source/scriptinterface/ScriptInterface.h @@ -268,19 +268,19 @@ public: std::wstring ToString(jsval obj, bool pretty = false); /** - * Parse a UTF-8-encoded JSON string. Returns the undefined value on error. + * Parse a UTF-8-encoded JSON string. Returns the unmodified value on error and prints an error message. */ - CScriptValRooted ParseJSON(const std::string& string_utf8); + void ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out); /** - * Read a JSON file. Returns the undefined value on error. + * Read a JSON file. Returns the unmodified value on error and prints an error message. */ - CScriptValRooted ReadJSONFile(const VfsPath& path); + void ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out); /** * Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error. */ - std::string StringifyJSON(jsval obj, bool indent = true); + std::string StringifyJSON(JS::MutableHandleValue obj, bool indent = true); /** * Report the given error message through the JS error reporting mechanism, diff --git a/source/scriptinterface/tests/test_ScriptInterface.h b/source/scriptinterface/tests/test_ScriptInterface.h index 9ff367a5bd..2ea90b4d52 100644 --- a/source/scriptinterface/tests/test_ScriptInterface.h +++ b/source/scriptinterface/tests/test_ScriptInterface.h @@ -241,15 +241,17 @@ public: void test_json() { ScriptInterface script("Test", "Test", g_ScriptRuntime); + JSContext* cx = script.GetContext(); + JSAutoRequest rq(cx); std::string input = "({'x':1,'z':[2,'3\\u263A\\ud800'],\"y\":true})"; - CScriptValRooted val; - TS_ASSERT(script.Eval(input.c_str(), val)); + JS::RootedValue val(cx); + TS_ASSERT(script.Eval(input.c_str(), &val)); - std::string stringified = script.StringifyJSON(val.get()); + std::string stringified = script.StringifyJSON(&val); TS_ASSERT_STR_EQUALS(stringified, "{\n \"x\": 1,\n \"z\": [\n 2,\n \"3\xE2\x98\xBA\xEF\xBF\xBD\"\n ],\n \"y\": true\n}"); - val = script.ParseJSON(stringified); + script.ParseJSON(stringified, &val); TS_ASSERT_WSTR_EQUALS(script.ToString(val.get()), L"({x:1, z:[2, \"3\\u263A\\uFFFD\"], y:true})"); } }; diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp index 9ffcb2b38b..2e934f1c10 100644 --- a/source/simulation2/Simulation2.cpp +++ b/source/simulation2/Simulation2.cpp @@ -724,7 +724,12 @@ CScriptValRooted CSimulation2::GetInitAttributes() void CSimulation2::SetMapSettings(const std::string& settings) { - m->m_MapSettings = m->m_ComponentManager.GetScriptInterface().ParseJSON(settings); + JSContext* cx = m->m_ComponentManager.GetScriptInterface().GetContext(); + JSAutoRequest rq(cx); + // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade + JS::RootedValue tmpMapSettings(cx); + m->m_ComponentManager.GetScriptInterface().ParseJSON(settings, &tmpMapSettings); + m->m_MapSettings = CScriptValRooted(cx, tmpMapSettings); } void CSimulation2::SetMapSettings(const CScriptValRooted& settings) @@ -734,7 +739,11 @@ void CSimulation2::SetMapSettings(const CScriptValRooted& settings) std::string CSimulation2::GetMapSettingsString() { - return m->m_ComponentManager.GetScriptInterface().StringifyJSON(m->m_MapSettings.get()); + JSContext* cx = m->m_ComponentManager.GetScriptInterface().GetContext(); + JSAutoRequest rq(cx); + // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade + JS::RootedValue tmpMapSettings(cx, m->m_MapSettings.get()); + return m->m_ComponentManager.GetScriptInterface().StringifyJSON(&tmpMapSettings); } CScriptVal CSimulation2::GetMapSettings() @@ -929,5 +938,5 @@ std::string CSimulation2::GetAIData() if (!scriptInterface.Eval("({})", &ais) || !scriptInterface.SetProperty(ais, "AIData", aiData)) return std::string(); - return scriptInterface.StringifyJSON(ais); + return scriptInterface.StringifyJSON(&ais); } diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index e1cf742738..178dad1d18 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -100,7 +100,8 @@ private: JSAutoRequest rq(cx); OsPath path = L"simulation/ai/" + m_AIName + L"/data.json"; - JS::RootedValue metadata(cx, m_Worker.LoadMetadata(path).get()); + JS::RootedValue metadata(cx); + m_Worker.LoadMetadata(path, &metadata); if (metadata.isUndefined()) { LOGERROR(L"Failed to create AI player: can't find %ls", path.string().c_str()); @@ -741,15 +742,15 @@ public: } private: - CScriptValRooted LoadMetadata(const VfsPath& path) + void LoadMetadata(const VfsPath& path, JS::MutableHandleValue out) { if (m_PlayerMetadata.find(path) == m_PlayerMetadata.end()) { // Load and cache the AI player metadata - m_PlayerMetadata[path] = m_ScriptInterface->ReadJSONFile(path); + m_ScriptInterface->ReadJSONFile(path, out); + m_PlayerMetadata[path] = CScriptValRooted(m_ScriptInterface->GetContext(), out); } - - return m_PlayerMetadata[path]; + out.set(m_PlayerMetadata[path].get()); } void PerformComputation() diff --git a/source/simulation2/components/CCmpCommandQueue.cpp b/source/simulation2/components/CCmpCommandQueue.cpp index e0206e18a5..8f8ecee2fb 100644 --- a/source/simulation2/components/CCmpCommandQueue.cpp +++ b/source/simulation2/components/CCmpCommandQueue.cpp @@ -89,12 +89,16 @@ public: m_LocalQueue.push_back(c); } - virtual void PostNetworkCommand(CScriptVal cmd) + virtual void PostNetworkCommand(CScriptVal cmd1) { JSContext* cx = GetSimContext().GetScriptInterface().GetContext(); + JSAutoRequest rq(cx); + + // TODO: With ESR31 we should be able to take JS::HandleValue directly + JS::RootedValue cmd(cx, cmd1.get()); PROFILE2_EVENT("post net command"); - PROFILE2_ATTR("command: %s", GetSimContext().GetScriptInterface().StringifyJSON(cmd.get(), false).c_str()); + PROFILE2_ATTR("command: %s", GetSimContext().GetScriptInterface().StringifyJSON(&cmd, false).c_str()); // TODO: would be nicer to not use globals if (g_Game && g_Game->GetTurnManager()) diff --git a/source/simulation2/components/ICmpAIManager.cpp b/source/simulation2/components/ICmpAIManager.cpp index 6e8dc0edf5..5e88257c8c 100644 --- a/source/simulation2/components/ICmpAIManager.cpp +++ b/source/simulation2/components/ICmpAIManager.cpp @@ -60,9 +60,11 @@ public: std::wstring dirname = GetWstringFromWpath(*it); JS::RootedValue ai(cx); + JS::RootedValue data(cx); + self->m_ScriptInterface.ReadJSONFile(pathname, &data); self->m_ScriptInterface.Eval("({})", &ai); self->m_ScriptInterface.SetProperty(ai, "id", dirname, true); - self->m_ScriptInterface.SetProperty(ai, "data", self->m_ScriptInterface.ReadJSONFile(pathname), true); + self->m_ScriptInterface.SetProperty(ai, "data", data, true); self->m_AIs.push_back(CScriptValRooted(cx, ai)); return INFO::OK; diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp index 7c7f267b4d..4129de5e7a 100644 --- a/source/simulation2/system/ComponentManager.cpp +++ b/source/simulation2/system/ComponentManager.cpp @@ -1147,13 +1147,16 @@ CScriptVal CComponentManager::Script_ReadCivJSONFile(ScriptInterface::CxPrivate* return ReadJSONFile(pCxPrivate, L"civs", fileName); } -CScriptVal CComponentManager::ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filePath, std::wstring fileName) +JS::Value CComponentManager::ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filePath, std::wstring fileName) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); + JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); + JSAutoRequest rq(cx); VfsPath path = VfsPath(filePath) / fileName; - - return componentManager->GetScriptInterface().ReadJSONFile(path).get(); + JS::RootedValue out(cx); + componentManager->GetScriptInterface().ReadJSONFile(path, &out); + return out.get(); } Status CComponentManager::FindJSONFilesCallback(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) diff --git a/source/simulation2/system/ComponentManager.h b/source/simulation2/system/ComponentManager.h index 0d1b37737b..69a0c7b3bc 100644 --- a/source/simulation2/system/ComponentManager.h +++ b/source/simulation2/system/ComponentManager.h @@ -286,7 +286,7 @@ private: static CScriptVal Script_ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring fileName); static CScriptVal Script_ReadCivJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring fileName); static std::vector Script_FindJSONFiles(ScriptInterface::CxPrivate* pCxPrivate, std::wstring subPath, bool recursive); - static CScriptVal ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filePath, std::wstring fileName); + static JS::Value ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filePath, std::wstring fileName); // callback function to handle recursively finding files in a directory static Status FindJSONFilesCallback(const VfsPath&, const CFileInfo&, const uintptr_t); diff --git a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp index 89c0b1e176..eaead7a857 100644 --- a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp @@ -91,7 +91,8 @@ QUERYHANDLER(GenerateMap) JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); - JS::RootedValue settings(cx, scriptInterface.ParseJSON(*msg->settings).get()); + JS::RootedValue settings(cx); + scriptInterface.ParseJSON(*msg->settings, &settings); JS::RootedValue attrs(cx); scriptInterface.Eval("({})", &attrs);