Explicitly make ScriptInterface a Compartment wrapper.

ScriptInterface is now a wrapper around a JSCompartment, and thus always
has a well-defined global.

The error reporter is moved to ScriptRuntime in anticipation of that
handling JSContext in a later diff.

Part of the SM52 migration, stage: SM45 compatible.

Patch by: Itms
Tested By: Freagarach
Refs #4893

Differential Revision: https://code.wildfiregames.com/D3090
This was SVN commit r24180.
This commit is contained in:
wraitii 2020-11-14 08:46:32 +00:00
parent 0046783e73
commit aae417bd29
24 changed files with 210 additions and 188 deletions

View File

@ -92,7 +92,7 @@ void* CMapGeneratorWorker::RunThread(CMapGeneratorWorker* self)
shared_ptr<ScriptRuntime> mapgenRuntime = ScriptRuntime::CreateRuntime(RMS_RUNTIME_SIZE);
// Enable the script to be aborted
JS_SetInterruptCallback(mapgenRuntime->m_rt, MapGeneratorInterruptCallback);
JS_SetInterruptCallback(mapgenRuntime->GetJSRuntime(), MapGeneratorInterruptCallback);
self->m_ScriptInterface = new ScriptInterface("Engine", "MapGenerator", mapgenRuntime);
@ -143,7 +143,7 @@ bool CMapGeneratorWorker::Run()
RegisterScriptFunctions_MapGenerator();
// Copy settings to global variable
JS::RootedValue global(rq.cx, m_ScriptInterface->GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
if (!m_ScriptInterface->SetProperty(global, "g_MapSettings", settingsVal, true, true))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to define g_MapSettings");

View File

@ -97,8 +97,8 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev)
{
ret = IN_HANDLED;
ScriptInterface::Request rq(*m_ScriptInterface);
JS::RootedObject globalObj(rq.cx, &GetGlobalObject().toObject());
ScriptInterface::Request rq(m_ScriptInterface);
JS::RootedObject globalObj(rq.cx, rq.glob);
JS::RootedValue result(rq.cx);
JS_CallFunctionValue(rq.cx, globalObj, m_GlobalHotkeys[hotkey][eventName], JS::HandleValueArray::empty(), &result);
}

View File

@ -237,7 +237,6 @@ public:
const CGUIColor& GetPreDefinedColor(const CStr& name) const { return m_PreDefinedColors.at(name); }
shared_ptr<ScriptInterface> GetScriptInterface() { return m_ScriptInterface; };
JS::Value GetGlobalObject() { return m_ScriptInterface->GetGlobalObject(); };
private:
/**

View File

@ -133,9 +133,9 @@ void CGUIManager::SGUIPage::LoadPage(shared_ptr<ScriptRuntime> scriptRuntime)
if (gui)
{
shared_ptr<ScriptInterface> scriptInterface = gui->GetScriptInterface();
ScriptInterface::Request rq(*scriptInterface);
ScriptInterface::Request rq(scriptInterface);
JS::RootedValue global(rq.cx, scriptInterface->GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
JS::RootedValue hotloadDataVal(rq.cx);
scriptInterface->CallFunction(global, "getHotloadData", &hotloadDataVal);
hotloadData = scriptInterface->WriteStructuredClone(hotloadDataVal);
@ -199,11 +199,11 @@ void CGUIManager::SGUIPage::LoadPage(shared_ptr<ScriptRuntime> scriptRuntime)
gui->LoadedXmlFiles();
shared_ptr<ScriptInterface> scriptInterface = gui->GetScriptInterface();
ScriptInterface::Request rq(*scriptInterface);
ScriptInterface::Request rq(scriptInterface);
JS::RootedValue initDataVal(rq.cx);
JS::RootedValue hotloadDataVal(rq.cx);
JS::RootedValue global(rq.cx, scriptInterface->GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
if (initData)
scriptInterface->ReadStructuredClone(initData, &initDataVal);
@ -241,9 +241,9 @@ void CGUIManager::SGUIPage::PerformCallbackFunction(shared_ptr<ScriptInterface::
return;
shared_ptr<ScriptInterface> scriptInterface = gui->GetScriptInterface();
ScriptInterface::Request rq(*scriptInterface);
ScriptInterface::Request rq(scriptInterface);
JS::RootedObject globalObj(rq.cx, &scriptInterface->GetGlobalObject().toObject());
JS::RootedObject globalObj(rq.cx, rq.glob);
JS::RootedValue funcVal(rq.cx, *callbackFunction);
@ -299,7 +299,7 @@ InReaction CGUIManager::HandleEvent(const SDL_Event_* ev)
PROFILE("handleInputBeforeGui");
ScriptInterface::Request rq(*top()->GetScriptInterface());
JS::RootedValue global(rq.cx, top()->GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
if (top()->GetScriptInterface()->CallFunction(global, "handleInputBeforeGui", handled, *ev, top()->FindObjectUnderMouse()))
if (handled)
return IN_HANDLED;
@ -315,7 +315,7 @@ InReaction CGUIManager::HandleEvent(const SDL_Event_* ev)
{
// We can't take the following lines out of this scope because top() may be another gui page than it was when calling handleInputBeforeGui!
ScriptInterface::Request rq(*top()->GetScriptInterface());
JS::RootedValue global(rq.cx, top()->GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
PROFILE("handleInputAfterGui");
if (top()->GetScriptInterface()->CallFunction(global, "handleInputAfterGui", handled, *ev))

View File

@ -300,8 +300,6 @@ float IGUIObject::GetBufferedZ() const
void IGUIObject::RegisterScriptHandler(const CStr& eventName, const CStr& Code, CGUI& pGUI)
{
ScriptInterface::Request rq(pGUI.GetScriptInterface());
JS::RootedValue globalVal(rq.cx, pGUI.GetGlobalObject());
JS::RootedObject globalObj(rq.cx, &globalVal.toObject());
const int paramCount = 1;
const char* paramNames[paramCount] = { "mouse" };

View File

@ -84,7 +84,7 @@ public:
const ScriptInterface& pageScriptInterface = *(g_GUI->GetActiveGUI()->GetScriptInterface());
ScriptInterface::Request prq(pageScriptInterface);
JS::RootedValue global(prq.cx, pageScriptInterface.GetGlobalObject());
JS::RootedValue global(prq.cx, prq.globalValue());
// Ensure that our hotkey state was synchronised with the event itself.
bool hotkey_pressed_value = false;

View File

@ -736,7 +736,7 @@ JS::Value XmppClient::GuiPollNewMessages(const ScriptInterface& scriptInterface)
m_GuiMessageQueue.clear();
// Copy the messages over to the caller script interface.
return scriptInterface.CloneValueFromOtherContext(*m_ScriptInterface, messages);
return scriptInterface.CloneValueFromOtherCompartment(*m_ScriptInterface, messages);
}
JS::Value XmppClient::GuiPollHistoricMessages(const ScriptInterface& scriptInterface)
@ -754,7 +754,7 @@ JS::Value XmppClient::GuiPollHistoricMessages(const ScriptInterface& scriptInter
m_ScriptInterface->SetPropertyInt(messages, j++, message);
// Copy the messages over to the caller script interface.
return scriptInterface.CloneValueFromOtherContext(*m_ScriptInterface, messages);
return scriptInterface.CloneValueFromOtherCompartment(*m_ScriptInterface, messages);
}
/**

View File

@ -158,7 +158,7 @@ JS::Value JSI_Network::PollNetworkClient(ScriptInterface::CmptPrivate* pCmptPriv
ScriptInterface::Request rqNet(g_NetClient->GetScriptInterface());
JS::RootedValue pollNet(rqNet.cx);
g_NetClient->GuiPoll(&pollNet);
return pCmptPrivate->pScriptInterface->CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), pollNet);
return pCmptPrivate->pScriptInterface->CloneValueFromOtherCompartment(g_NetClient->GetScriptInterface(), pollNet);
}
void JSI_Network::SetNetworkGameAttributes(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue attribs1)

View File

@ -325,7 +325,7 @@ PSRETURN CGame::ReallyStartGame()
shared_ptr<ScriptInterface> scriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
ScriptInterface::Request rq(scriptInterface);
JS::RootedValue global(rq.cx, scriptInterface->GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
if (scriptInterface->HasProperty(global, "reallyStartGame"))
scriptInterface->CallFunctionVoid(global, "reallyStartGame");
}

View File

@ -1615,7 +1615,7 @@ void CancelLoad(const CStrW& message)
shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
ScriptInterface::Request rq(pScriptInterface);
JS::RootedValue global(rq.cx, pScriptInterface->GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
LDR_Cancel();

View File

@ -283,7 +283,7 @@ void RunHardwareDetection()
scriptInterface.StringifyJSON(&settings, true));
// Run the detection script:
JS::RootedValue global(rq.cx, scriptInterface.GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
scriptInterface.CallFunctionVoid(global, "RunHardwareDetection", settings);
}

View File

@ -51,7 +51,7 @@ void JSI_Game::StartGame(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleV
ScriptInterface::Request rqSim(sim->GetScriptInterface());
JS::RootedValue gameAttribs(rqSim.cx,
sim->GetScriptInterface().CloneValueFromOtherContext(*(pCmptPrivate->pScriptInterface), attribs));
sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), attribs));
g_Game->SetPlayerID(playerID);
g_Game->StartGame(&gameAttribs, "");

View File

@ -99,7 +99,7 @@ JS::Value JSI_SavedGame::StartSavedGame(ScriptInterface::CmptPrivate* pCmptPriva
ScriptInterface::Request rqGame(sim->GetScriptInterface());
JS::RootedValue gameContextMetadata(rqGame.cx,
sim->GetScriptInterface().CloneValueFromOtherContext(*(pCmptPrivate->pScriptInterface), guiContextMetadata));
sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), guiContextMetadata));
JS::RootedValue gameInitAttributes(rqGame.cx);
sim->GetScriptInterface().GetProperty(gameContextMetadata, "initAttributes", &gameInitAttributes);

View File

@ -62,24 +62,32 @@ struct ScriptInterface_impl
// members have to be called before the runtime destructor.
shared_ptr<ScriptRuntime> m_runtime;
JS::PersistentRootedObject m_glob; // global scope object
boost::rand48* m_rng;
JS::PersistentRootedObject m_nativeScope; // native function scope object
friend ScriptInterface::Request;
private:
JSContext* m_cx;
JSCompartment* m_formerCompartment;
JS::PersistentRootedObject m_glob; // global scope object
public:
boost::rand48* m_rng;
JS::PersistentRootedObject m_nativeScope; // native function scope object
};
ScriptInterface::Request::Request(const ScriptInterface& scriptInterface) :
cx(scriptInterface.m->m_cx)
{
JS_BeginRequest(cx);
m_formerCompartment = JS_EnterCompartment(cx, scriptInterface.m->m_glob);
glob = JS::CurrentGlobalOrNull(cx);
}
JS::Value ScriptInterface::Request::globalValue() const
{
return JS::ObjectValue(*glob);
}
ScriptInterface::Request::~Request()
{
JS_LeaveCompartment(cx, m_formerCompartment);
JS_EndRequest(cx);
}
@ -95,46 +103,6 @@ JSClass global_class = {
JS_GlobalObjectTraceHook
};
void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
{
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
std::stringstream msg;
bool isWarning = JSREPORT_IS_WARNING(report->flags);
msg << (isWarning ? "JavaScript warning: " : "JavaScript error: ");
if (report->filename)
{
msg << report->filename;
msg << " line " << report->lineno << "\n";
}
msg << message;
// If there is an exception, then print its stack trace
JS::RootedValue excn(cx);
if (JS_GetPendingException(cx, &excn) && excn.isObject())
{
JS::RootedValue stackVal(cx);
JS::RootedObject excnObj(cx, &excn.toObject());
JS_GetProperty(cx, excnObj, "stack", &stackVal);
std::string stackText;
ScriptInterface::FromJSVal(rq, stackVal, stackText);
std::istringstream stream(stackText);
for (std::string line; std::getline(stream, line);)
msg << "\n " << line;
}
if (isWarning)
LOGWARNING("%s", msg.str().c_str());
else
LOGERROR("%s", msg.str().c_str());
// When running under Valgrind, print more information in the error message
// VALGRIND_PRINTF_BACKTRACE("->");
}
// Functions in the global namespace:
bool print(JSContext* cx, uint argc, JS::Value* vp)
@ -351,70 +319,47 @@ bool ScriptInterface::MathRandom(double& nbr)
}
ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr<ScriptRuntime>& runtime) :
m_runtime(runtime), m_glob(runtime->m_rt), m_nativeScope(runtime->m_rt)
m_runtime(runtime), m_cx(runtime->GetGeneralJSContext()), m_glob(runtime->GetJSRuntime()), m_nativeScope(runtime->GetJSRuntime())
{
m_cx = JS_NewContext(m_runtime->m_rt, STACK_CHUNK_SIZE);
ENSURE(m_cx);
JS_SetOffthreadIonCompilationEnabled(m_runtime->m_rt, true);
// For GC debugging:
// JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ);
JS_SetContextPrivate(m_cx, NULL);
JS_SetErrorReporter(m_runtime->m_rt, ErrorReporter);
JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_ION_ENABLE, 1);
JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_BASELINE_ENABLE, 1);
JS::RuntimeOptionsRef(m_cx)
.setExtraWarnings(true)
.setWerror(false)
.setStrictMode(true);
JS::CompartmentOptions opt;
opt.setVersion(JSVERSION_LATEST);
// Keep JIT code during non-shrinking GCs. This brings a quite big performance improvement.
opt.setPreserveJitCode(true);
JSAutoRequest rq(m_cx);
JS::RootedObject globalRootedVal(m_cx, JS_NewGlobalObject(m_cx, &global_class, NULL, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt));
m_formerCompartment = JS_EnterCompartment(m_cx, globalRootedVal);
ENSURE(JS_InitStandardClasses(m_cx, globalRootedVal));
m_glob = globalRootedVal.get();
m_glob = JS_NewGlobalObject(m_cx, &global_class, nullptr, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt);
JS_DefineProperty(m_cx, m_glob, "global", globalRootedVal, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JSAutoCompartment autoCmpt(m_cx, m_glob);
ENSURE(JS_InitStandardClasses(m_cx, m_glob));
JS_DefineProperty(m_cx, m_glob, "global", m_glob, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, nullptr, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, globalRootedVal, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, globalRootedVal, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, globalRootedVal, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, globalRootedVal, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, globalRootedVal, "clone", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, globalRootedVal, "deepfreeze", ::deepfreeze, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "clone", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "deepfreeze", ::deepfreeze, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
Register("ProfileStart", ::ProfileStart, 1);
Register("ProfileStop", ::ProfileStop, 0);
Register("ProfileAttribute", ::ProfileAttribute, 1);
runtime->RegisterContext(m_cx);
m_runtime->RegisterCompartment(js::GetObjectCompartment(m_glob));
}
ScriptInterface_impl::~ScriptInterface_impl()
{
m_runtime->UnRegisterContext(m_cx);
{
JSAutoRequest rq(m_cx);
JS_LeaveCompartment(m_cx, m_formerCompartment);
}
JS_DestroyContext(m_cx);
m_runtime->UnRegisterCompartment(js::GetObjectCompartment(m_glob));
}
void ScriptInterface_impl::Register(const char* name, JSNative fptr, uint nargs) const
{
JSAutoRequest rq(m_cx);
JSAutoCompartment autoCmpt(m_cx, m_glob);
JS::RootedObject nativeScope(m_cx, m_nativeScope);
JS::RootedFunction func(m_cx, JS_DefineFunction(m_cx, nativeScope, name, fptr, nargs, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT));
}
@ -431,7 +376,7 @@ ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugN
Request rq(this);
m_CmptPrivate.pScriptInterface = this;
JS_SetContextPrivate(rq.cx, (void*)&m_CmptPrivate);
JS_SetCompartmentPrivate(js::GetObjectCompartment(rq.glob), (void*)&m_CmptPrivate);
}
ScriptInterface::~ScriptInterface()
@ -450,7 +395,7 @@ void ScriptInterface::SetCallbackData(void* pCBData)
ScriptInterface::CmptPrivate* ScriptInterface::GetScriptInterfaceAndCBData(JSContext* cx)
{
CmptPrivate* pCmptPrivate = (CmptPrivate*)JS_GetContextPrivate(cx);
CmptPrivate* pCmptPrivate = (CmptPrivate*)JS_GetCompartmentPrivate(js::GetContextCompartment(cx));
return pCmptPrivate;
}
@ -478,7 +423,7 @@ bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng)
{
Request rq(this);
JS::RootedValue math(rq.cx);
JS::RootedObject global(rq.cx, m->m_glob);
JS::RootedObject global(rq.cx, rq.glob);
if (JS_GetProperty(rq.cx, global, "Math", &math) && math.isObject())
{
JS::RootedObject mathObj(rq.cx, &math.toObject());
@ -502,7 +447,7 @@ void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs) co
JSRuntime* ScriptInterface::GetJSRuntime() const
{
return m->m_runtime->m_rt;
return m->m_runtime->GetJSRuntime();
}
shared_ptr<ScriptRuntime> ScriptInterface::GetRuntime() const
@ -535,7 +480,7 @@ void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructo
throw PSERROR_Scripting_DefineType_AlreadyExists();
}
JS::RootedObject global(rq.cx, m->m_glob);
JS::RootedObject global(rq.cx, rq.glob);
JS::RootedObject obj(rq.cx, JS_InitClass(rq.cx, global, nullptr,
clasp,
constructor, minArgs, // Constructor, min args
@ -597,16 +542,10 @@ void ScriptInterface::CreateArray(const Request& rq, JS::MutableHandleValue obje
throw PSERROR_Scripting_CreateObjectFailed();
}
JS::Value ScriptInterface::GetGlobalObject() const
{
Request rq(this);
return JS::ObjectValue(*JS::CurrentGlobalOrNull(rq.cx));
}
bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate)
{
Request rq(this);
JS::RootedObject global(rq.cx, m->m_glob);
JS::RootedObject global(rq.cx, rq.glob);
bool found;
if (!JS_HasProperty(rq.cx, global, name, &found))
@ -834,7 +773,7 @@ bool ScriptInterface::FreezeObject(JS::HandleValue objVal, bool deep) const
bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code) const
{
Request rq(this);
JS::RootedObject global(rq.cx, m->m_glob);
JS::RootedObject global(rq.cx, rq.glob);
utf16string codeUtf16(code.begin(), code.end());
uint lineNo = 1;
// CompileOptions does not copy the contents of the filename string pointer.
@ -1036,12 +975,12 @@ std::string ScriptInterface::ToString(JS::MutableHandleValue obj, bool pretty) c
JS::RootedValue indentVal(rq.cx, JS::Int32Value(2));
// Temporary disable the error reporter, so we don't print complaints about cyclic values
JSErrorReporter er = JS_SetErrorReporter(m->m_runtime->m_rt, NULL);
JSErrorReporter er = JS_SetErrorReporter(GetJSRuntime(), nullptr);
bool ok = JS_Stringify(rq.cx, obj, nullptr, indentVal, &Stringifier::callback, &str);
// Restore error reporter
JS_SetErrorReporter(m->m_runtime->m_rt, er);
JS_SetErrorReporter(GetJSRuntime(), er);
if (ok)
return str.stream.str();
@ -1077,12 +1016,12 @@ bool ScriptInterface::IsExceptionPending(const Request& rq)
return JS_IsExceptionPending(rq.cx) ? true : false;
}
JS::Value ScriptInterface::CloneValueFromOtherContext(const ScriptInterface& otherContext, JS::HandleValue val) const
JS::Value ScriptInterface::CloneValueFromOtherCompartment(const ScriptInterface& otherCompartment, JS::HandleValue val) const
{
PROFILE("CloneValueFromOtherContext");
PROFILE("CloneValueFromOtherCompartment");
Request rq(this);
JS::RootedValue out(rq.cx);
shared_ptr<StructuredClone> structuredClone = otherContext.WriteStructuredClone(val);
shared_ptr<StructuredClone> structuredClone = otherCompartment.WriteStructuredClone(val);
ReadStructuredClone(structuredClone, &out);
return out.get();
}

View File

@ -58,7 +58,7 @@ extern thread_local shared_ptr<ScriptRuntime> g_ScriptRuntime;
/**
* Abstraction around a SpiderMonkey JSContext.
* Abstraction around a SpiderMonkey JSCompartment.
*
* Thread-safety:
* - May be used in non-main threads.
@ -84,7 +84,7 @@ public:
struct CmptPrivate
{
ScriptInterface* pScriptInterface; // the ScriptInterface object the current context belongs to
ScriptInterface* pScriptInterface; // the ScriptInterface object the compartment belongs to
void* pCBData; // meant to be used as the "this" object for callback functions
} m_CmptPrivate;
@ -95,9 +95,13 @@ public:
shared_ptr<ScriptRuntime> GetRuntime() const;
/**
* RAII structure which encapsulates an access to the context of a ScriptInterface.
* This struct provides a pointer to the context, and it acts like JSAutoRequest. This
* way, getting the context is safe with respect to the GC.
* RAII structure which encapsulates an access to the context and compartment of a ScriptInterface.
* This struct provides:
* - a pointer to the context, while acting like JSAutoRequest
* - a pointer to the global object of the compartment, while acting like JSAutoCompartment
*
* This way, getting and using those pointers is safe with respect to the GC
* and to the separation of compartments.
*/
struct Request
{
@ -107,15 +111,19 @@ public:
Request(const ScriptInterface& scriptInterface);
Request(const ScriptInterface* scriptInterface) : Request(*scriptInterface) {}
Request(shared_ptr<ScriptInterface> scriptInterface) : Request(*scriptInterface) {}
Request(const CmptPrivate* CmptPrivate) : Request(CmptPrivate->pScriptInterface) {}
Request(const CmptPrivate* cmptPrivate) : Request(cmptPrivate->pScriptInterface) {}
~Request();
JS::Value globalValue() const;
JSContext* cx;
JSObject* glob;
private:
JSCompartment* m_formerCompartment;
};
friend struct Request;
/**
* Load global scripts that most script contexts need,
* Load global scripts that most script interfaces need,
* located in the /globalscripts directory. VFS must be initialized.
*/
bool LoadGlobalScripts();
@ -158,8 +166,6 @@ public:
*/
static void CreateArray(const Request& rq, JS::MutableHandleValue objectValue, size_t length = 0);
JS::Value GetGlobalObject() const;
/**
* Set the named property on the global object.
* Optionally makes it {ReadOnly, DontEnum}. We do not allow to make it DontDelete, so that it can be hotloaded
@ -291,12 +297,12 @@ public:
bool LoadGlobalScriptFile(const VfsPath& path) const;
/**
* Construct a new value (usable in this ScriptInterface's context) by cloning
* a value from a different context.
* Construct a new value (usable in this ScriptInterface's compartment) by cloning
* a value from a different compartment.
* Complex values (functions, XML, etc) won't be cloned correctly, but basic
* types and cyclic references should be fine.
*/
JS::Value CloneValueFromOtherContext(const ScriptInterface& otherContext, JS::HandleValue val) const;
JS::Value CloneValueFromOtherCompartment(const ScriptInterface& otherCompartment, JS::HandleValue val) const;
/**
* Convert a JS::Value to a C++ type. (This might trigger GC.)
@ -327,7 +333,7 @@ public:
/**
* Structured clones are a way to serialize 'simple' JS::Values into a buffer
* that can safely be passed between contexts and runtimes and threads.
* that can safely be passed between compartments and between threads.
* A StructuredClone can be stored and read multiple times if desired.
* We wrap them in shared_ptr so memory management is automatic and
* thread-safe.

View File

@ -22,6 +22,7 @@
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "scriptinterface/ScriptEngine.h"
#include "scriptinterface/ScriptInterface.h"
void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc))
@ -89,6 +90,52 @@ void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const J
#endif
}
namespace {
void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
{
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
std::stringstream msg;
bool isWarning = JSREPORT_IS_WARNING(report->flags);
msg << (isWarning ? "JavaScript warning: " : "JavaScript error: ");
if (report->filename)
{
msg << report->filename;
msg << " line " << report->lineno << "\n";
}
msg << message;
// If there is an exception, then print its stack trace
JS::RootedValue excn(rq.cx);
if (JS_GetPendingException(rq.cx, &excn) && excn.isObject())
{
JS::RootedValue stackVal(rq.cx);
JS::RootedObject excnObj(rq.cx, &excn.toObject());
JS_GetProperty(rq.cx, excnObj, "stack", &stackVal);
std::string stackText;
ScriptInterface::FromJSVal(rq, stackVal, stackText);
std::istringstream stream(stackText);
for (std::string line; std::getline(stream, line);)
msg << "\n " << line;
}
if (isWarning)
LOGWARNING("%s", msg.str().c_str());
else
LOGERROR("%s", msg.str().c_str());
// When running under Valgrind, print more information in the error message
// VALGRIND_PRINTF_BACKTRACE("->");
}
} // anonymous namespace
shared_ptr<ScriptRuntime> ScriptRuntime::CreateRuntime(int runtimeSize, int heapGrowthBytesGCTrigger)
{
return shared_ptr<ScriptRuntime>(new ScriptRuntime(runtimeSize, heapGrowthBytesGCTrigger));
@ -116,24 +163,47 @@ ScriptRuntime::ScriptRuntime(int runtimeSize, int heapGrowthBytesGCTrigger):
JS_SetGCParameter(m_rt, JSGC_DYNAMIC_HEAP_GROWTH, false);
ScriptEngine::GetSingleton().RegisterRuntime(m_rt);
m_cx = JS_NewContext(m_rt, STACK_CHUNK_SIZE);
ENSURE(m_cx); // TODO: error handling
JS_SetOffthreadIonCompilationEnabled(m_rt, true);
// For GC debugging:
// JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ);
JS_SetContextPrivate(m_cx, nullptr);
JS_SetErrorReporter(m_rt, ErrorReporter);
JS_SetGlobalJitCompilerOption(m_rt, JSJITCOMPILER_ION_ENABLE, 1);
JS_SetGlobalJitCompilerOption(m_rt, JSJITCOMPILER_BASELINE_ENABLE, 1);
JS::RuntimeOptionsRef(m_cx)
.setExtraWarnings(true)
.setWerror(false)
.setStrictMode(true);
}
ScriptRuntime::~ScriptRuntime()
{
JS_DestroyContext(m_cx);
JS_DestroyRuntime(m_rt);
ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be active (initialized and not yet shut down) when destroying a ScriptRuntime!");
ScriptEngine::GetSingleton().UnRegisterRuntime(m_rt);
}
void ScriptRuntime::RegisterContext(JSContext* cx)
void ScriptRuntime::RegisterCompartment(JSCompartment* cmpt)
{
m_Contexts.push_back(cx);
ENSURE(cmpt);
m_Compartments.push_back(cmpt);
}
void ScriptRuntime::UnRegisterContext(JSContext* cx)
void ScriptRuntime::UnRegisterCompartment(JSCompartment* cmpt)
{
m_Contexts.remove(cx);
m_Compartments.remove(cmpt);
}
#define GC_DEBUG_PRINT 0
@ -199,7 +269,7 @@ void ScriptRuntime::MaybeIncrementalGC(double delay)
#if GC_DEBUG_PRINT
printf("Finishing incremental GC because gcBytes > m_RuntimeSize / 2. \n");
#endif
PrepareContextsForIncrementalGC();
PrepareCompartmentsForIncrementalGC();
JS::FinishIncrementalGC(m_rt, JS::gcreason::REFRESH_FRAME);
}
else
@ -228,7 +298,7 @@ void ScriptRuntime::MaybeIncrementalGC(double delay)
else
printf("Running incremental GC slice \n");
#endif
PrepareContextsForIncrementalGC();
PrepareCompartmentsForIncrementalGC();
if (!JS::IsIncrementalGCInProgress(m_rt))
JS::StartIncrementalGC(m_rt, GC_NORMAL, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget);
else
@ -247,8 +317,8 @@ void ScriptRuntime::ShrinkingGC()
JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
}
void ScriptRuntime::PrepareContextsForIncrementalGC()
void ScriptRuntime::PrepareCompartmentsForIncrementalGC() const
{
for (JSContext* const& ctx : m_Contexts)
JS::PrepareZoneForGC(js::GetCompartmentZone(js::GetContextCompartment(ctx)));
for (JSCompartment* const& cmpt : m_Compartments)
JS::PrepareZoneForGC(js::GetCompartmentZone(cmpt));
}

View File

@ -30,10 +30,10 @@ constexpr int DEFAULT_RUNTIME_SIZE = 16 * 1024 * 1024;
constexpr int DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER = 2 * 1024 * 1024;
/**
* Abstraction around a SpiderMonkey JSRuntime.
* Each ScriptRuntime can be used to initialize several ScriptInterface
* contexts which can then share data, but a single ScriptRuntime should
* only be used on a single thread.
* Abstraction around a SpiderMonkey JSRuntime/JSContext.
*
* A single ScriptRuntime, with the associated runtime and context,
* should only be used on a single thread.
*
* (One means to share data between threads and runtimes is to create
* a ScriptInterface::StructuredClone.)
@ -46,9 +46,7 @@ public:
~ScriptRuntime();
/**
* Returns a runtime, which can used to initialise any number of
* ScriptInterfaces contexts. Values created in one context may be used
* in any other context from the same runtime (but not any other runtime).
* Returns a runtime/context, in which any number of ScriptInterfaces compartments can live.
* Each runtime should only ever be used on a single thread.
* @param runtimeSize Maximum size in bytes of the new runtime
* @param heapGrowthBytesGCTrigger Size in bytes of cumulated allocations after which a GC will be triggered
@ -70,16 +68,30 @@ public:
void MaybeIncrementalGC(double delay);
void ShrinkingGC();
void RegisterContext(JSContext* cx);
void UnRegisterContext(JSContext* cx);
/**
* This is used to keep track of compartments which should be prepared for a GC.
*/
void RegisterCompartment(JSCompartment* cmpt);
void UnRegisterCompartment(JSCompartment* cmpt);
JSRuntime* m_rt;
JSRuntime* GetJSRuntime() const { return m_rt; }
/**
* GetGeneralJSContext returns the context without starting a GC request and without
* entering any compartment. It should only be used in specific situations, such as
* creating a new compartment, or as an unsafe alternative to GetJSRuntime.
* If you need the compartmented context of a ScriptInterface, you should create a
* ScriptInterface::Request and use the context from that.
*/
JSContext* GetGeneralJSContext() const { return m_cx; }
private:
void PrepareContextsForIncrementalGC();
JSRuntime* m_rt;
JSContext* m_cx;
std::list<JSContext*> m_Contexts;
void PrepareCompartmentsForIncrementalGC() const;
std::list<JSCompartment*> m_Compartments;
int m_RuntimeSize;
int m_HeapGrowthBytesGCTrigger;

View File

@ -43,7 +43,7 @@ class TestScriptConversions : public CxxTest::TestSuite
// 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;
JS::RootedValue global(rq.cx, script.GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
TS_ASSERT(script.CallFunction(global, "uneval", source, v1));
TS_ASSERT_STR_EQUALS(source, expected);
@ -60,7 +60,7 @@ class TestScriptConversions : public CxxTest::TestSuite
ScriptInterface::ToJSVal(rq, &v1, value);
std::string source;
JS::RootedValue global(rq.cx, script.GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
TS_ASSERT(script.CallFunction(global, "uneval", source, v1));
if (expected)
@ -90,7 +90,7 @@ class TestScriptConversions : public CxxTest::TestSuite
ScriptInterface::ToJSVal(rq, &r1, r);
std::string source;
JS::RootedValue global(rq.cx, script.GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
TS_ASSERT(script.CallFunction(global, "uneval", source, r1));
TS_ASSERT_STR_EQUALS(source, expected);

View File

@ -72,7 +72,7 @@ public:
{
ScriptInterface::Request rq2(script2);
JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherContext(script1, obj1));
JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherCompartment(script1, obj1));
std::string source;
TS_ASSERT(script2.CallFunction(obj2, "toSource", source));
@ -94,7 +94,7 @@ public:
{
ScriptInterface::Request rq2(script2);
JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherContext(script1, obj1));
JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherCompartment(script1, obj1));
std::string source;
TS_ASSERT(script2.CallFunction(obj2, "toSource", source));
@ -114,7 +114,7 @@ public:
{
ScriptInterface::Request rq2(script2);
JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherContext(script1, obj1));
JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherCompartment(script1, obj1));
// Use JSAPI function to check if the values of the properties "a", "b" are equals a.x[0]
JS::RootedValue prop_a(rq2.cx);

View File

@ -53,7 +53,7 @@ public:
m_SimContext(), m_ComponentManager(m_SimContext, rt),
m_EnableOOSLog(false), m_EnableSerializationTest(false), m_RejoinTestTurn(-1), m_TestingRejoin(false),
m_SecondaryTerrain(nullptr), m_SecondaryContext(nullptr), m_SecondaryComponentManager(nullptr), m_SecondaryLoadedScripts(nullptr),
m_MapSettings(rt->m_rt), m_InitAttributes(rt->m_rt)
m_MapSettings(rt->GetJSRuntime()), m_InitAttributes(rt->GetJSRuntime())
{
m_SimContext.m_UnitManager = unitManager;
m_SimContext.m_Terrain = terrain;
@ -173,7 +173,7 @@ public:
ScriptInterface::Request rqNew(newScript);
for (const SimulationCommand& command : commands)
{
JS::RootedValue tmpCommand(rqNew.cx, newScript.CloneValueFromOtherContext(oldScript, command.data));
JS::RootedValue tmpCommand(rqNew.cx, newScript.CloneValueFromOtherCompartment(oldScript, command.data));
newScript.FreezeObject(tmpCommand, true);
SimulationCommand cmd(command.player, rqNew.cx, tmpCommand);
newCommands.emplace_back(std::move(cmd));
@ -423,7 +423,7 @@ void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
{
ScriptInterface::Request rq2(m_SecondaryComponentManager->GetScriptInterface());
JS::RootedValue mapSettingsCloned(rq2.cx,
m_SecondaryComponentManager->GetScriptInterface().CloneValueFromOtherContext(
m_SecondaryComponentManager->GetScriptInterface().CloneValueFromOtherCompartment(
scriptInterface, m_MapSettings));
ENSURE(LoadTriggerScripts(*m_SecondaryComponentManager, mapSettingsCloned, m_SecondaryLoadedScripts));
}
@ -734,14 +734,14 @@ ScriptInterface& CSimulation2::GetScriptInterface() const
void CSimulation2::PreInitGame()
{
ScriptInterface::Request rq(GetScriptInterface());
JS::RootedValue global(rq.cx, GetScriptInterface().GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
GetScriptInterface().CallFunctionVoid(global, "PreInitGame");
}
void CSimulation2::InitGame()
{
ScriptInterface::Request rq(GetScriptInterface());
JS::RootedValue global(rq.cx, GetScriptInterface().GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
JS::RootedValue settings(rq.cx);
JS::RootedValue tmpInitAttributes(rq.cx, GetInitAttributes());
@ -840,7 +840,7 @@ void CSimulation2::GetMapSettings(JS::MutableHandleValue ret)
void CSimulation2::LoadPlayerSettings(bool newPlayers)
{
ScriptInterface::Request rq(GetScriptInterface());
JS::RootedValue global(rq.cx, GetScriptInterface().GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
GetScriptInterface().CallFunctionVoid(global, "LoadPlayerSettings", m->m_MapSettings, newPlayers);
}
@ -848,7 +848,7 @@ void CSimulation2::LoadMapSettings()
{
ScriptInterface::Request rq(GetScriptInterface());
JS::RootedValue global(rq.cx, GetScriptInterface().GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
// Initialize here instead of in Update()
GetScriptInterface().CallFunctionVoid(global, "LoadMapSettings", m->m_MapSettings);

View File

@ -111,7 +111,7 @@ private:
std::string moduleName;
std::string constructor;
JS::RootedValue objectWithConstructor(rq.cx); // object that should contain the constructor function
JS::RootedValue global(rq.cx, m_ScriptInterface->GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
JS::RootedValue ctor(rq.cx);
if (!m_ScriptInterface->HasProperty(metadata, "moduleName"))
{
@ -216,10 +216,10 @@ public:
m_CommandsComputed(true),
m_HasLoadedEntityTemplates(false),
m_HasSharedComponent(false),
m_EntityTemplates(g_ScriptRuntime->m_rt),
m_SharedAIObj(g_ScriptRuntime->m_rt),
m_PassabilityMapVal(g_ScriptRuntime->m_rt),
m_TerritoryMapVal(g_ScriptRuntime->m_rt)
m_EntityTemplates(g_ScriptRuntime->GetJSRuntime()),
m_SharedAIObj(g_ScriptRuntime->GetJSRuntime()),
m_PassabilityMapVal(g_ScriptRuntime->GetJSRuntime()),
m_TerritoryMapVal(g_ScriptRuntime->GetJSRuntime())
{
m_ScriptInterface->ReplaceNondeterministicRNG(m_RNG);
@ -422,7 +422,7 @@ public:
// Constructor name is SharedScript, it's in the module API3
// TODO: Hardcoding this is bad, we need a smarter way.
JS::RootedValue AIModule(rq.cx);
JS::RootedValue global(rq.cx, m_ScriptInterface->GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
JS::RootedValue ctor(rq.cx);
if (!m_ScriptInterface->GetProperty(global, "API3", &AIModule) || AIModule.isUndefined())
{

View File

@ -103,7 +103,7 @@ public:
const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
ScriptInterface::Request rq(scriptInterface);
JS::RootedValue global(rq.cx, scriptInterface.GetGlobalObject());
JS::RootedValue global(rq.cx, rq.globalValue());
std::vector<SimulationCommand> localCommands;
m_LocalQueue.swap(localCommands);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -158,8 +158,7 @@ template<> bool ScriptInterface::FromJSVal<CFixedVector3D>(const Request& rq, J
template<> void ScriptInterface::ToJSVal<CFixedVector3D>(const Request& rq, JS::MutableHandleValue ret, const CFixedVector3D& val)
{
ScriptInterface::CmptPrivate* pCmptPrivate = ScriptInterface::GetScriptInterfaceAndCBData(rq.cx);
JS::RootedObject global(rq.cx, &pCmptPrivate->pScriptInterface->GetGlobalObject().toObject());
JS::RootedObject global(rq.cx, rq.glob);
JS::RootedValue valueVector3D(rq.cx);
if (!JS_GetProperty(rq.cx, global, "Vector3D", &valueVector3D))
FAIL_VOID("Failed to get Vector3D constructor");
@ -192,8 +191,7 @@ template<> bool ScriptInterface::FromJSVal<CFixedVector2D>(const Request& rq, J
template<> void ScriptInterface::ToJSVal<CFixedVector2D>(const Request& rq, JS::MutableHandleValue ret, const CFixedVector2D& val)
{
ScriptInterface::CmptPrivate* pCmptPrivate = ScriptInterface::GetScriptInterfaceAndCBData(rq.cx);
JS::RootedObject global(rq.cx, &pCmptPrivate->pScriptInterface->GetGlobalObject().toObject());
JS::RootedObject global(rq.cx, rq.glob);
JS::RootedValue valueVector2D(rq.cx);
if (!JS_GetProperty(rq.cx, global, "Vector2D", &valueVector2D))
FAIL_VOID("Failed to get Vector2D constructor");

View File

@ -51,11 +51,11 @@ JS::Value JSI_Simulation::GuiInterfaceCall(ScriptInterface::CmptPrivate* pCmptPr
return JS::UndefinedValue();
ScriptInterface::Request rqSim(sim->GetScriptInterface());
JS::RootedValue arg(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherContext(*(pCmptPrivate->pScriptInterface), data));
JS::RootedValue arg(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), data));
JS::RootedValue ret(rqSim.cx);
cmpGuiInterface->ScriptCall(g_Game->GetViewedPlayerID(), name, arg, &ret);
return pCmptPrivate->pScriptInterface->CloneValueFromOtherContext(sim->GetScriptInterface(), ret);
return pCmptPrivate->pScriptInterface->CloneValueFromOtherCompartment(sim->GetScriptInterface(), ret);
}
void JSI_Simulation::PostNetworkCommand(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue cmd)
@ -71,7 +71,7 @@ void JSI_Simulation::PostNetworkCommand(ScriptInterface::CmptPrivate* pCmptPriva
return;
ScriptInterface::Request rqSim(sim->GetScriptInterface());
JS::RootedValue cmd2(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherContext(*(pCmptPrivate->pScriptInterface), cmd));
JS::RootedValue cmd2(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), cmd));
cmpCommandQueue->PostNetworkCommand(cmd2);
}