LCOV - code coverage report
Current view: top level - source/scriptinterface - ScriptInterface.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 246 353 69.7 %
Date: 2023-01-19 00:18:29 Functions: 38 43 88.4 %

          Line data    Source code
       1             : /* Copyright (C) 2023 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "FunctionWrapper.h"
      21             : #include "ScriptContext.h"
      22             : #include "ScriptExtraHeaders.h"
      23             : #include "ScriptInterface.h"
      24             : #include "ScriptStats.h"
      25             : #include "StructuredClone.h"
      26             : 
      27             : #include "lib/debug.h"
      28             : #include "lib/utf8.h"
      29             : #include "ps/CLogger.h"
      30             : #include "ps/Filesystem.h"
      31             : #include "ps/Profile.h"
      32             : 
      33             : #include <map>
      34             : #include <string>
      35             : 
      36             : #define BOOST_MULTI_INDEX_DISABLE_SERIALIZATION
      37             : #include <boost/preprocessor/punctuation/comma_if.hpp>
      38             : #include <boost/preprocessor/repetition/repeat.hpp>
      39             : #include <boost/random/linear_congruential.hpp>
      40             : #include <boost/flyweight.hpp>
      41             : #include <boost/flyweight/key_value.hpp>
      42             : #include <boost/flyweight/no_locking.hpp>
      43             : #include <boost/flyweight/no_tracking.hpp>
      44             : 
      45             : /**
      46             :  * @file
      47             :  * Abstractions of various SpiderMonkey features.
      48             :  * Engine code should be using functions of these interfaces rather than
      49             :  * directly accessing the underlying JS api.
      50             :  */
      51             : 
      52             : struct ScriptInterface_impl
      53             : {
      54             :     ScriptInterface_impl(const char* nativeScopeName, const std::shared_ptr<ScriptContext>& context, JS::Compartment* compartment);
      55             :     ~ScriptInterface_impl();
      56             : 
      57             :     // Take care to keep this declaration before heap rooted members. Destructors of heap rooted
      58             :     // members have to be called before the context destructor.
      59             :     std::shared_ptr<ScriptContext> m_context;
      60             : 
      61             :     friend ScriptRequest;
      62             :     private:
      63             :         JSContext* m_cx;
      64             :         JS::PersistentRootedObject m_glob; // global scope object
      65             : 
      66             :     public:
      67             :         boost::rand48* m_rng;
      68             :         JS::PersistentRootedObject m_nativeScope; // native function scope object
      69             : };
      70             : 
      71             : /**
      72             :  * Constructor for ScriptRequest - here because it needs access into ScriptInterface_impl.
      73             :  */
      74       35230 : ScriptRequest::ScriptRequest(const ScriptInterface& scriptInterface) :
      75       35230 :     cx(scriptInterface.m->m_cx),
      76       35230 :     glob(scriptInterface.m->m_glob),
      77       35230 :     nativeScope(scriptInterface.m->m_nativeScope),
      78      105690 :     m_ScriptInterface(scriptInterface)
      79             : {
      80       35230 :     m_FormerRealm = JS::EnterRealm(cx, scriptInterface.m->m_glob);
      81       35230 : }
      82             : 
      83       70460 : ScriptRequest::~ScriptRequest()
      84             : {
      85       35230 :     JS::LeaveRealm(cx, m_FormerRealm);
      86       35230 : }
      87             : 
      88        1302 : ScriptRequest::ScriptRequest(JSContext* cx) : ScriptRequest(ScriptInterface::CmptPrivate::GetScriptInterface(cx))
      89             : {
      90        1302 : }
      91             : 
      92          73 : JS::Value ScriptRequest::globalValue() const
      93             : {
      94          73 :     return JS::ObjectValue(*glob);
      95             : }
      96             : 
      97         818 : const ScriptInterface& ScriptRequest::GetScriptInterface() const
      98             : {
      99         818 :     return m_ScriptInterface;
     100             : }
     101             : 
     102             : namespace
     103             : {
     104             : 
     105             : JSClassOps global_classops = {
     106             :     nullptr, nullptr,
     107             :     nullptr, nullptr,
     108             :     nullptr, nullptr, nullptr,
     109             :     nullptr, nullptr, nullptr,
     110             :     JS_GlobalObjectTraceHook
     111             : };
     112             : 
     113             : JSClass global_class = {
     114             :     "global", JSCLASS_GLOBAL_FLAGS, &global_classops
     115             : };
     116             : 
     117             : // Functions in the global namespace:
     118             : 
     119           0 : bool print(JSContext* cx, uint argc, JS::Value* vp)
     120             : {
     121           0 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     122           0 :     ScriptRequest rq(cx);
     123             : 
     124           0 :     for (uint i = 0; i < args.length(); ++i)
     125             :     {
     126           0 :         std::wstring str;
     127           0 :         if (!Script::FromJSVal(rq, args[i], str))
     128           0 :             return false;
     129           0 :         debug_printf("%s", utf8_from_wstring(str).c_str());
     130             :     }
     131           0 :     fflush(stdout);
     132           0 :     args.rval().setUndefined();
     133           0 :     return true;
     134             : }
     135             : 
     136           0 : bool logmsg(JSContext* cx, uint argc, JS::Value* vp)
     137             : {
     138           0 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     139           0 :     if (args.length() < 1)
     140             :     {
     141           0 :         args.rval().setUndefined();
     142           0 :         return true;
     143             :     }
     144             : 
     145           0 :     ScriptRequest rq(cx);
     146           0 :     std::wstring str;
     147           0 :     if (!Script::FromJSVal(rq, args[0], str))
     148           0 :         return false;
     149           0 :     LOGMESSAGE("%s", utf8_from_wstring(str));
     150           0 :     args.rval().setUndefined();
     151           0 :     return true;
     152             : }
     153             : 
     154           1 : bool warn(JSContext* cx, uint argc, JS::Value* vp)
     155             : {
     156           1 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     157           1 :     if (args.length() < 1)
     158             :     {
     159           0 :         args.rval().setUndefined();
     160           0 :         return true;
     161             :     }
     162             : 
     163           2 :     ScriptRequest rq(cx);
     164           2 :     std::wstring str;
     165           1 :     if (!Script::FromJSVal(rq, args[0], str))
     166           0 :         return false;
     167           1 :     LOGWARNING("%s", utf8_from_wstring(str));
     168           1 :     args.rval().setUndefined();
     169           1 :     return true;
     170             : }
     171             : 
     172           0 : bool error(JSContext* cx, uint argc, JS::Value* vp)
     173             : {
     174           0 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     175           0 :     if (args.length() < 1)
     176             :     {
     177           0 :         args.rval().setUndefined();
     178           0 :         return true;
     179             :     }
     180             : 
     181           0 :     ScriptRequest rq(cx);
     182           0 :     std::wstring str;
     183           0 :     if (!Script::FromJSVal(rq, args[0], str))
     184           0 :         return false;
     185           0 :     LOGERROR("%s", utf8_from_wstring(str));
     186           0 :     args.rval().setUndefined();
     187           0 :     return true;
     188             : }
     189             : 
     190         330 : JS::Value deepcopy(const ScriptRequest& rq, JS::HandleValue val)
     191             : {
     192         330 :     if (val.isNullOrUndefined())
     193             :     {
     194           0 :         ScriptException::Raise(rq, "deepcopy requires one argument.");
     195           0 :         return JS::UndefinedValue();
     196             :     }
     197             : 
     198         660 :     JS::RootedValue ret(rq.cx, Script::DeepCopy(rq, val));
     199         330 :     if (ret.isNullOrUndefined())
     200             :     {
     201           0 :         ScriptException::Raise(rq, "deepcopy StructureClone copy failed.");
     202           0 :         return JS::UndefinedValue();
     203             :     }
     204         330 :     return ret;
     205             : }
     206             : 
     207         262 : JS::Value deepfreeze(const ScriptInterface& scriptInterface, JS::HandleValue val)
     208             : {
     209         524 :     ScriptRequest rq(scriptInterface);
     210         262 :     if (!val.isObject())
     211             :     {
     212           0 :         ScriptException::Raise(rq, "deepfreeze requires exactly one object as an argument.");
     213           0 :         return JS::UndefinedValue();
     214             :     }
     215             : 
     216         262 :     Script::FreezeObject(rq, val, true);
     217         262 :     return val;
     218             : }
     219             : 
     220           5 : void ProfileStart(const std::string& regionName)
     221             : {
     222           5 :     const char* name = "(ProfileStart)";
     223             : 
     224             :     typedef boost::flyweight<
     225             :         std::string,
     226             :         boost::flyweights::no_tracking,
     227             :         boost::flyweights::no_locking
     228             :     > StringFlyweight;
     229             : 
     230           5 :     if (!regionName.empty())
     231           5 :         name = StringFlyweight(regionName).get().c_str();
     232             : 
     233           5 :     if (CProfileManager::IsInitialised() && Threading::IsMainThread())
     234           0 :         g_Profiler.StartScript(name);
     235             : 
     236           5 :     g_Profiler2.RecordRegionEnter(name);
     237           5 : }
     238             : 
     239           5 : void ProfileStop()
     240             : {
     241           5 :     if (CProfileManager::IsInitialised() && Threading::IsMainThread())
     242           0 :         g_Profiler.Stop();
     243             : 
     244           5 :     g_Profiler2.RecordRegionLeave();
     245           5 : }
     246             : 
     247           0 : void ProfileAttribute(const std::string& attr)
     248             : {
     249           0 :     const char* name = "(ProfileAttribute)";
     250             : 
     251             :     typedef boost::flyweight<
     252             :         std::string,
     253             :         boost::flyweights::no_tracking,
     254             :         boost::flyweights::no_locking
     255             :     > StringFlyweight;
     256             : 
     257           0 :     if (!attr.empty())
     258           0 :         name = StringFlyweight(attr).get().c_str();
     259             : 
     260           0 :     g_Profiler2.RecordAttribute("%s", name);
     261           0 : }
     262             : 
     263             : // Math override functions:
     264             : 
     265             : // boost::uniform_real is apparently buggy in Boost pre-1.47 - for integer generators
     266             : // it returns [min,max], not [min,max). The bug was fixed in 1.47.
     267             : // We need consistent behaviour, so manually implement the correct version:
     268          89 : static double generate_uniform_real(boost::rand48& rng, double min, double max)
     269             : {
     270             :     while (true)
     271             :     {
     272          89 :         double n = (double)(rng() - rng.min());
     273          89 :         double d = (double)(rng.max() - rng.min()) + 1.0;
     274          89 :         ENSURE(d > 0 && n >= 0 && n <= d);
     275          89 :         double r = n / d * (max - min) + min;
     276          89 :         if (r < max)
     277         178 :             return r;
     278           0 :     }
     279             : }
     280             : 
     281             : } // anonymous namespace
     282             : 
     283          89 : bool ScriptInterface::MathRandom(double& nbr) const
     284             : {
     285          89 :     if (m->m_rng == nullptr)
     286           0 :         return false;
     287          89 :     nbr = generate_uniform_real(*(m->m_rng), 0.0, 1.0);
     288          89 :     return true;
     289             : }
     290             : 
     291          87 : bool ScriptInterface::Math_random(JSContext* cx, uint argc, JS::Value* vp)
     292             : {
     293          87 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     294             :     double r;
     295          87 :     if (!ScriptInterface::CmptPrivate::GetScriptInterface(cx).MathRandom(r))
     296           0 :         return false;
     297             : 
     298          87 :     args.rval().setNumber(r);
     299          87 :     return true;
     300             : }
     301             : 
     302         282 : ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const std::shared_ptr<ScriptContext>& context, JS::Compartment* compartment) :
     303         282 :     m_context(context), m_cx(context->GetGeneralJSContext()), m_glob(context->GetGeneralJSContext()), m_nativeScope(context->GetGeneralJSContext())
     304             : {
     305         282 :     JS::RealmCreationOptions creationOpt;
     306             :     // Keep JIT code during non-shrinking GCs. This brings a quite big performance improvement.
     307         282 :     creationOpt.setPreserveJitCode(true);
     308             :     // Enable uneval
     309         282 :     creationOpt.setToSourceEnabled(true);
     310             : 
     311         282 :     if (compartment)
     312           0 :         creationOpt.setExistingCompartment(compartment);
     313             :     else
     314             :         // This is the default behaviour.
     315         282 :         creationOpt.setNewCompartmentAndZone();
     316             : 
     317         282 :     JS::RealmOptions opt(creationOpt, JS::RealmBehaviors{});
     318             : 
     319         282 :     m_glob = JS_NewGlobalObject(m_cx, &global_class, nullptr, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt);
     320             : 
     321         564 :     JSAutoRealm autoRealm(m_cx, m_glob);
     322             : 
     323         282 :     ENSURE(JS::InitRealmStandardClasses(m_cx));
     324             : 
     325         282 :     JS_DefineProperty(m_cx, m_glob, "global", m_glob, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     326             : 
     327             :     // These first 4 actually use CallArgs & thus don't use ScriptFunction
     328         282 :     JS_DefineFunction(m_cx, m_glob, "print", ::print,        0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     329         282 :     JS_DefineFunction(m_cx, m_glob, "log",   ::logmsg,       1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     330         282 :     JS_DefineFunction(m_cx, m_glob, "warn",  ::warn,         1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     331         282 :     JS_DefineFunction(m_cx, m_glob, "error", ::error,        1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     332         282 :     ScriptFunction::Register<deepcopy>(m_cx, m_glob, "clone");
     333         282 :     ScriptFunction::Register<deepfreeze>(m_cx, m_glob, "deepfreeze");
     334             : 
     335         282 :     m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, nullptr, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     336             : 
     337         292 :     ScriptFunction::Register<&ProfileStart>(m_cx, m_nativeScope, "ProfileStart");
     338         292 :     ScriptFunction::Register<&ProfileStop>(m_cx, m_nativeScope, "ProfileStop");
     339         282 :     ScriptFunction::Register<&ProfileAttribute>(m_cx, m_nativeScope, "ProfileAttribute");
     340             : 
     341         282 :     m_context->RegisterRealm(JS::GetObjectRealmOrNull(m_glob));
     342         282 : }
     343             : 
     344         564 : ScriptInterface_impl::~ScriptInterface_impl()
     345             : {
     346         282 :     m_context->UnRegisterRealm(JS::GetObjectRealmOrNull(m_glob));
     347         282 : }
     348             : 
     349         282 : ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const std::shared_ptr<ScriptContext>& context) :
     350         282 :     m(std::make_unique<ScriptInterface_impl>(nativeScopeName, context, nullptr))
     351             : {
     352             :     // Profiler stats table isn't thread-safe, so only enable this on the main thread
     353         282 :     if (Threading::IsMainThread())
     354             :     {
     355         282 :         if (g_ScriptStatsTable)
     356           0 :             g_ScriptStatsTable->Add(this, debugName);
     357             :     }
     358             : 
     359         564 :     ScriptRequest rq(this);
     360         282 :     m_CmptPrivate.pScriptInterface = this;
     361         282 :     JS::SetRealmPrivate(JS::GetObjectRealmOrNull(rq.glob), (void*)&m_CmptPrivate);
     362         282 : }
     363             : 
     364           0 : ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const ScriptInterface& neighbor)
     365             : {
     366           0 :     ScriptRequest nrq(neighbor);
     367           0 :     JS::Compartment* comp = JS::GetCompartmentForRealm(JS::GetCurrentRealmOrNull(nrq.cx));
     368           0 :     m = std::make_unique<ScriptInterface_impl>(nativeScopeName, neighbor.GetContext(), comp);
     369             : 
     370             :     // Profiler stats table isn't thread-safe, so only enable this on the main thread
     371           0 :     if (Threading::IsMainThread())
     372             :     {
     373           0 :         if (g_ScriptStatsTable)
     374           0 :             g_ScriptStatsTable->Add(this, debugName);
     375             :     }
     376             : 
     377           0 :     ScriptRequest rq(this);
     378           0 :     m_CmptPrivate.pScriptInterface = this;
     379           0 :     JS::SetRealmPrivate(JS::GetObjectRealmOrNull(rq.glob), (void*)&m_CmptPrivate);
     380           0 : }
     381             : 
     382         564 : ScriptInterface::~ScriptInterface()
     383             : {
     384         282 :     if (Threading::IsMainThread())
     385             :     {
     386         282 :         if (g_ScriptStatsTable)
     387           0 :             g_ScriptStatsTable->Remove(this);
     388             :     }
     389         282 : }
     390             : 
     391        1389 : const ScriptInterface& ScriptInterface::CmptPrivate::GetScriptInterface(JSContext *cx)
     392             : {
     393        1389 :     CmptPrivate* pCmptPrivate = (CmptPrivate*)JS::GetRealmPrivate(JS::GetCurrentRealmOrNull(cx));
     394        1389 :     ENSURE(pCmptPrivate);
     395        1389 :     return *pCmptPrivate->pScriptInterface;
     396             : }
     397             : 
     398        1174 : void* ScriptInterface::CmptPrivate::GetCBData(JSContext *cx)
     399             : {
     400        1174 :     CmptPrivate* pCmptPrivate = (CmptPrivate*)JS::GetRealmPrivate(JS::GetCurrentRealmOrNull(cx));
     401        1174 :     return pCmptPrivate ? pCmptPrivate->pCBData : nullptr;
     402             : }
     403             : 
     404         150 : void ScriptInterface::SetCallbackData(void* pCBData)
     405             : {
     406         150 :     m_CmptPrivate.pCBData = pCBData;
     407         150 : }
     408             : 
     409             : template <>
     410         587 : void* ScriptInterface::ObjectFromCBData<void>(const ScriptRequest& rq)
     411             : {
     412         587 :     return ScriptInterface::CmptPrivate::GetCBData(rq.cx);
     413             : }
     414             : 
     415         198 : bool ScriptInterface::LoadGlobalScripts()
     416             : {
     417             :     // Ignore this failure in tests
     418         198 :     if (!g_VFS)
     419          23 :         return false;
     420             : 
     421             :     // Load and execute *.js in the global scripts directory
     422         350 :     VfsPaths pathnames;
     423         175 :     vfs::GetPathnames(g_VFS, L"globalscripts/", L"*.js", pathnames);
     424        1651 :     for (const VfsPath& path : pathnames)
     425        1476 :         if (!LoadGlobalScriptFile(path))
     426             :         {
     427           0 :             LOGERROR("LoadGlobalScripts: Failed to load script %s", path.string8());
     428           0 :             return false;
     429             :         }
     430             : 
     431         175 :     return true;
     432             : }
     433             : 
     434         136 : bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng)
     435             : {
     436         272 :     ScriptRequest rq(this);
     437         272 :     JS::RootedValue math(rq.cx);
     438         272 :     JS::RootedObject global(rq.cx, rq.glob);
     439         136 :     if (JS_GetProperty(rq.cx, global, "Math", &math) && math.isObject())
     440             :     {
     441         136 :         JS::RootedObject mathObj(rq.cx, &math.toObject());
     442         272 :         JS::RootedFunction random(rq.cx, JS_DefineFunction(rq.cx, mathObj, "random", Math_random, 0,
     443         136 :             JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT));
     444         136 :         if (random)
     445             :         {
     446         136 :             m->m_rng = &rng;
     447         136 :             return true;
     448             :         }
     449             :     }
     450             : 
     451           0 :     ScriptException::CatchPending(rq);
     452           0 :     LOGERROR("ReplaceNondeterministicRNG: failed to replace Math.random");
     453           0 :     return false;
     454             : }
     455             : 
     456           1 : JSContext* ScriptInterface::GetGeneralJSContext() const
     457             : {
     458           1 :     return m->m_context->GetGeneralJSContext();
     459             : }
     460             : 
     461           7 : std::shared_ptr<ScriptContext> ScriptInterface::GetContext() const
     462             : {
     463           7 :     return m->m_context;
     464             : }
     465             : 
     466          49 : void ScriptInterface::CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const
     467             : {
     468          98 :     ScriptRequest rq(this);
     469          49 :     if (!ctor.isObject())
     470             :     {
     471           0 :         LOGERROR("CallConstructor: ctor is not an object");
     472           0 :         out.setNull();
     473           0 :         return;
     474             :     }
     475             : 
     476          98 :     JS::RootedObject objOut(rq.cx);
     477          49 :     if (!JS::Construct(rq.cx, ctor, argv, &objOut))
     478           0 :         out.setNull();
     479             :     else
     480          49 :         out.setObjectOrNull(objOut);
     481             : }
     482             : 
     483        5696 : void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs)
     484             : {
     485       11392 :     ScriptRequest rq(this);
     486       11392 :     std::string typeName = clasp->name;
     487             : 
     488        5696 :     if (m_CustomObjectTypes.find(typeName) != m_CustomObjectTypes.end())
     489             :     {
     490             :         // This type already exists
     491           0 :         throw PSERROR_Scripting_DefineType_AlreadyExists();
     492             :     }
     493             : 
     494       11392 :     JS::RootedObject global(rq.cx, rq.glob);
     495       11392 :     JS::RootedObject obj(rq.cx, JS_InitClass(rq.cx, global, nullptr,
     496             :                                                clasp,
     497             :                                                constructor, minArgs,     // Constructor, min args
     498             :                                                ps, fs,                   // Properties, methods
     499       11392 :                                                static_ps, static_fs));   // Constructor properties, methods
     500             : 
     501        5696 :     if (obj == nullptr)
     502             :     {
     503           0 :         ScriptException::CatchPending(rq);
     504           0 :         throw PSERROR_Scripting_DefineType_CreationFailed();
     505             :     }
     506             : 
     507        5696 :     CustomType& type = m_CustomObjectTypes[typeName];
     508             : 
     509        5696 :     type.m_Prototype.init(rq.cx, obj);
     510        5696 :     type.m_Class = clasp;
     511        5696 :     type.m_Constructor = constructor;
     512        5696 : }
     513             : 
     514           1 : JSObject* ScriptInterface::CreateCustomObject(const std::string& typeName) const
     515             : {
     516           1 :     std::map<std::string, CustomType>::const_iterator it = m_CustomObjectTypes.find(typeName);
     517             : 
     518           1 :     if (it == m_CustomObjectTypes.end())
     519           0 :         throw PSERROR_Scripting_TypeDoesNotExist();
     520             : 
     521           2 :     ScriptRequest rq(this);
     522           2 :     JS::RootedObject prototype(rq.cx, it->second.m_Prototype.get());
     523           2 :     return JS_NewObjectWithGivenProto(rq.cx, it->second.m_Class, prototype);
     524             : }
     525             : 
     526       10927 : bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate)
     527             : {
     528       21854 :     ScriptRequest rq(this);
     529       21854 :     JS::RootedObject global(rq.cx, rq.glob);
     530             : 
     531             :     bool found;
     532       10927 :     if (!JS_HasProperty(rq.cx, global, name, &found))
     533           0 :         return false;
     534       10927 :     if (found)
     535             :     {
     536          18 :         JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(rq.cx);
     537           9 :         if (!JS_GetOwnPropertyDescriptor(rq.cx, global, name, &desc) || !desc.isSome())
     538           0 :             return false;
     539             : 
     540           9 :         if (!desc->writable())
     541             :         {
     542           9 :             if (!replace)
     543             :             {
     544           0 :                 ScriptException::Raise(rq, "SetGlobal \"%s\" called multiple times", name);
     545           0 :                 return false;
     546             :             }
     547             : 
     548             :             // This is not supposed to happen, unless the user has called SetProperty with constant = true on the global object
     549             :             // instead of using SetGlobal.
     550           9 :             if (!desc->configurable())
     551             :             {
     552           0 :                 ScriptException::Raise(rq, "The global \"%s\" is permanent and cannot be hotloaded", name);
     553           0 :                 return false;
     554             :             }
     555             : 
     556           9 :             LOGMESSAGE("Hotloading new value for global \"%s\".", name);
     557           9 :             ENSURE(JS_DeleteProperty(rq.cx, global, name));
     558             :         }
     559             :     }
     560             : 
     561       10927 :     uint attrs = 0;
     562       10927 :     if (constant)
     563       10927 :         attrs |= JSPROP_READONLY;
     564       10927 :     if (enumerate)
     565       10927 :         attrs |= JSPROP_ENUMERATE;
     566             : 
     567       10927 :     return JS_DefineProperty(rq.cx, global, name, value, attrs);
     568             : }
     569             : 
     570          19 : bool ScriptInterface::GetGlobalProperty(const ScriptRequest& rq, const std::string& name, JS::MutableHandleValue out)
     571             : {
     572             :     // Try to get the object as a property of the global object.
     573          38 :     JS::RootedObject global(rq.cx, rq.glob);
     574          19 :     if (!JS_GetProperty(rq.cx, global, name.c_str(), out))
     575             :     {
     576           0 :         out.set(JS::NullHandleValue);
     577           0 :         return false;
     578             :     }
     579             : 
     580          19 :     if (!out.isNullOrUndefined())
     581          11 :         return true;
     582             : 
     583             :     // Some objects, such as const definitions, or Class definitions, are hidden inside closures.
     584             :     // We must fetch those from the correct lexical scope.
     585             :     //JS::RootedValue glob(cx);
     586          16 :     JS::RootedObject lexical_environment(rq.cx, JS_GlobalLexicalEnvironment(rq.glob));
     587           8 :     if (!JS_GetProperty(rq.cx, lexical_environment, name.c_str(), out))
     588             :     {
     589           0 :         out.set(JS::NullHandleValue);
     590           0 :         return false;
     591             :     }
     592             : 
     593           8 :     if (!out.isNullOrUndefined())
     594           8 :         return true;
     595             : 
     596           0 :     out.set(JS::NullHandleValue);
     597           0 :     return false;
     598             : }
     599             : 
     600           5 : bool ScriptInterface::SetPrototype(JS::HandleValue objVal, JS::HandleValue protoVal)
     601             : {
     602          10 :     ScriptRequest rq(this);
     603           5 :     if (!objVal.isObject() || !protoVal.isObject())
     604           0 :         return false;
     605          10 :     JS::RootedObject obj(rq.cx, &objVal.toObject());
     606          10 :     JS::RootedObject proto(rq.cx, &protoVal.toObject());
     607           5 :     return JS_SetPrototype(rq.cx, obj, proto);
     608             : }
     609             : 
     610         698 : bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code) const
     611             : {
     612        1396 :     ScriptRequest rq(this);
     613        1396 :     JS::RootedObject global(rq.cx, rq.glob);
     614             : 
     615             :     // CompileOptions does not copy the contents of the filename string pointer.
     616             :     // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary.
     617        1396 :     std::string filenameStr = filename.string8();
     618             : 
     619         698 :     JS::CompileOptions options(rq.cx);
     620             :     // Set the line to 0 because CompileFunction silently adds a `(function() {` as the first line,
     621             :     // and errors get misreported.
     622             :     // TODO: it would probably be better to not implicitly introduce JS scopes.
     623         698 :     options.setFileAndLine(filenameStr.c_str(), 0);
     624         698 :     options.setIsRunOnce(false);
     625             : 
     626        1396 :     JS::SourceText<mozilla::Utf8Unit> src;
     627         698 :     ENSURE(src.init(rq.cx, code.c_str(), code.length(), JS::SourceOwnership::Borrowed));
     628        1396 :     JS::RootedObjectVector emptyScopeChain(rq.cx);
     629        1396 :     JS::RootedFunction func(rq.cx, JS::CompileFunction(rq.cx, emptyScopeChain, options, NULL, 0, NULL, src));
     630         698 :     if (func == nullptr)
     631             :     {
     632           2 :         ScriptException::CatchPending(rq);
     633           2 :         return false;
     634             :     }
     635             : 
     636        1392 :     JS::RootedValue rval(rq.cx);
     637         696 :     if (JS_CallFunction(rq.cx, nullptr, func, JS::HandleValueArray::empty(), &rval))
     638         696 :         return true;
     639             : 
     640           0 :     ScriptException::CatchPending(rq);
     641           0 :     return false;
     642             : }
     643             : 
     644        1701 : bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::string& code) const
     645             : {
     646        3402 :     ScriptRequest rq(this);
     647             :     // CompileOptions does not copy the contents of the filename string pointer.
     648             :     // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary.
     649        3402 :     std::string filenameStr = filename.string8();
     650             : 
     651        3402 :     JS::RootedValue rval(rq.cx);
     652        1701 :     JS::CompileOptions opts(rq.cx);
     653        1701 :     opts.setFileAndLine(filenameStr.c_str(), 1);
     654             : 
     655        3402 :     JS::SourceText<mozilla::Utf8Unit> src;
     656        1701 :     ENSURE(src.init(rq.cx, code.c_str(), code.length(), JS::SourceOwnership::Borrowed));
     657        1701 :     if (JS::Evaluate(rq.cx, opts, src, &rval))
     658        1701 :         return true;
     659             : 
     660           0 :     ScriptException::CatchPending(rq);
     661           0 :     return false;
     662             : }
     663             : 
     664        1701 : bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) const
     665             : {
     666        1701 :     if (!VfsFileExists(path))
     667             :     {
     668           0 :         LOGERROR("File '%s' does not exist", path.string8());
     669           0 :         return false;
     670             :     }
     671             : 
     672        3402 :     CVFSFile file;
     673             : 
     674        1701 :     PSRETURN ret = file.Load(g_VFS, path);
     675             : 
     676        1701 :     if (ret != PSRETURN_OK)
     677             :     {
     678           0 :         LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret));
     679           0 :         return false;
     680             :     }
     681             : 
     682        3402 :     CStr code = file.DecodeUTF8(); // assume it's UTF-8
     683             : 
     684        1701 :     return LoadGlobalScript(path, code);
     685             : }
     686             : 
     687           2 : bool ScriptInterface::Eval(const char* code) const
     688             : {
     689           4 :     ScriptRequest rq(this);
     690           4 :     JS::RootedValue rval(rq.cx);
     691             : 
     692           2 :     JS::CompileOptions opts(rq.cx);
     693           2 :     opts.setFileAndLine("(eval)", 1);
     694           4 :     JS::SourceText<mozilla::Utf8Unit> src;
     695           2 :     ENSURE(src.init(rq.cx, code, strlen(code), JS::SourceOwnership::Borrowed));
     696           2 :     if (JS::Evaluate(rq.cx, opts, src, &rval))
     697           2 :         return true;
     698             : 
     699           0 :     ScriptException::CatchPending(rq);
     700           0 :     return false;
     701             : }
     702             : 
     703          66 : bool ScriptInterface::Eval(const char* code, JS::MutableHandleValue rval) const
     704             : {
     705         132 :     ScriptRequest rq(this);
     706             : 
     707          66 :     JS::CompileOptions opts(rq.cx);
     708          66 :     opts.setFileAndLine("(eval)", 1);
     709         132 :     JS::SourceText<mozilla::Utf8Unit> src;
     710          66 :     ENSURE(src.init(rq.cx, code, strlen(code), JS::SourceOwnership::Borrowed));
     711          66 :     if (JS::Evaluate(rq.cx, opts, src, rval))
     712          66 :         return true;
     713             : 
     714           0 :     ScriptException::CatchPending(rq);
     715           0 :     return false;
     716           3 : }

Generated by: LCOV version 1.13