LCOV - code coverage report
Current view: top level - source/scriptinterface - ScriptInterface.h (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 22 31 71.0 %
Date: 2023-01-19 00:18:29 Functions: 14 53 26.4 %

          Line data    Source code
       1             : /* Copyright (C) 2022 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             : #ifndef INCLUDED_SCRIPTINTERFACE
      19             : #define INCLUDED_SCRIPTINTERFACE
      20             : 
      21             : #include "ps/Errors.h"
      22             : #include "scriptinterface/ScriptConversions.h"
      23             : #include "scriptinterface/ScriptExceptions.h"
      24             : #include "scriptinterface/ScriptRequest.h"
      25             : #include "scriptinterface/ScriptTypes.h"
      26             : 
      27             : #include <map>
      28             : 
      29           0 : ERROR_GROUP(Scripting);
      30             : ERROR_TYPE(Scripting, SetupFailed);
      31             : 
      32             : ERROR_SUBGROUP(Scripting, LoadFile);
      33             : ERROR_TYPE(Scripting_LoadFile, OpenFailed);
      34             : ERROR_TYPE(Scripting_LoadFile, EvalErrors);
      35             : 
      36             : ERROR_TYPE(Scripting, CallFunctionFailed);
      37             : ERROR_TYPE(Scripting, DefineConstantFailed);
      38             : ERROR_TYPE(Scripting, CreateObjectFailed);
      39           0 : ERROR_TYPE(Scripting, TypeDoesNotExist);
      40             : 
      41           0 : ERROR_SUBGROUP(Scripting, DefineType);
      42           0 : ERROR_TYPE(Scripting_DefineType, AlreadyExists);
      43           0 : ERROR_TYPE(Scripting_DefineType, CreationFailed);
      44             : 
      45             : // Set the maximum number of function arguments that can be handled
      46             : // (This should be as small as possible (for compiler efficiency),
      47             : // but as large as necessary for all wrapped functions)
      48             : #define SCRIPT_INTERFACE_MAX_ARGS 8
      49             : 
      50             : class ScriptInterface;
      51             : struct ScriptInterface_impl;
      52             : 
      53             : class ScriptContext;
      54             : // Using a global object for the context is a workaround until Simulation, AI, etc,
      55             : // use their own threads and also their own contexts.
      56             : extern thread_local std::shared_ptr<ScriptContext> g_ScriptContext;
      57             : 
      58             : namespace boost { namespace random { class rand48; } }
      59             : 
      60             : class Path;
      61             : using VfsPath = Path;
      62             : 
      63             : /**
      64             :  * Abstraction around a SpiderMonkey JS::Realm.
      65             :  *
      66             :  * Thread-safety:
      67             :  * - May be used in non-main threads.
      68             :  * - Each ScriptInterface must be created, used, and destroyed, all in a single thread
      69             :  *   (it must never be shared between threads).
      70             :  */
      71             : class ScriptInterface
      72             : {
      73             :     NONCOPYABLE(ScriptInterface);
      74             : 
      75             :     friend class ScriptRequest;
      76             : 
      77             : public:
      78             : 
      79             :     /**
      80             :      * Constructor.
      81             :      * @param nativeScopeName Name of global object that functions (via ScriptFunction::Register) will
      82             :      *   be placed into, as a scoping mechanism; typically "Engine"
      83             :      * @param debugName Name of this interface for CScriptStats purposes.
      84             :      * @param context ScriptContext to use when initializing this interface.
      85             :      */
      86             :     ScriptInterface(const char* nativeScopeName, const char* debugName, const std::shared_ptr<ScriptContext>& context);
      87             : 
      88             :     /**
      89             :      * Alternate constructor. This creates the new Realm in the same Compartment as the neighbor scriptInterface.
      90             :      * This means that data can be freely exchanged between these two script interfaces without cloning.
      91             :      * @param nativeScopeName Name of global object that functions (via ScriptFunction::Register) will
      92             :      *   be placed into, as a scoping mechanism; typically "Engine"
      93             :      * @param debugName Name of this interface for CScriptStats purposes.
      94             :      * @param scriptInterface 'Neighbor' scriptInterface to share a compartment with.
      95             :      */
      96             :     ScriptInterface(const char* nativeScopeName, const char* debugName, const ScriptInterface& neighbor);
      97             : 
      98             :     ~ScriptInterface();
      99             : 
     100             :     struct CmptPrivate
     101             :     {
     102             :         friend class ScriptInterface;
     103             :     public:
     104             :         static const ScriptInterface& GetScriptInterface(JSContext* cx);
     105             :         static void* GetCBData(JSContext* cx);
     106             :     protected:
     107             :         ScriptInterface* pScriptInterface; // the ScriptInterface object the compartment belongs to
     108             :         void* pCBData; // meant to be used as the "this" object for callback functions
     109             :     };
     110             : 
     111             :     void SetCallbackData(void* pCBData);
     112             : 
     113             :     /**
     114             :      * Convert the CmptPrivate callback data to T*
     115             :      */
     116             :     template <typename T>
     117         587 :     static T* ObjectFromCBData(const ScriptRequest& rq)
     118             :     {
     119             :         static_assert(!std::is_same_v<void, T>);
     120         587 :         ScriptInterface::CmptPrivate::GetCBData(rq.cx);
     121         587 :         return static_cast<T*>(ObjectFromCBData<void>(rq));
     122             :     }
     123             : 
     124             :     /**
     125             :      * Variant for the function wrapper.
     126             :      */
     127             :     template <typename T>
     128         124 :     static T* ObjectFromCBData(const ScriptRequest& rq, JS::CallArgs&)
     129             :     {
     130         124 :         return ObjectFromCBData<T>(rq);
     131             :     }
     132             : 
     133             :     /**
     134             :      * GetGeneralJSContext returns the context without starting a GC request and without
     135             :      * entering the ScriptInterface compartment. It should only be used in specific situations,
     136             :      * for instance when initializing a persistent rooted.
     137             :      * If you need the compartmented context of the ScriptInterface, you should create a
     138             :      * ScriptInterface::Request and use the context from that.
     139             :      */
     140             :     JSContext* GetGeneralJSContext() const;
     141             :     std::shared_ptr<ScriptContext> GetContext() const;
     142             : 
     143             :     /**
     144             :      * Load global scripts that most script interfaces need,
     145             :      * located in the /globalscripts directory. VFS must be initialized.
     146             :      */
     147             :     bool LoadGlobalScripts();
     148             : 
     149             :     /**
     150             :      * Replace the default JS random number generator with a seeded, network-synced one.
     151             :      */
     152             :     bool ReplaceNondeterministicRNG(boost::random::rand48& rng);
     153             : 
     154             :     /**
     155             :      * Call a constructor function, equivalent to JS "new ctor(arg)".
     156             :      * @param ctor An object that can be used as constructor
     157             :      * @param argv Constructor arguments
     158             :      * @param out The new object; On error an error message gets logged and out is Null (out.isNull() == true).
     159             :      */
     160             :     void CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const;
     161             : 
     162             :     JSObject* CreateCustomObject(const std::string & typeName) const;
     163             :     void DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs);
     164             : 
     165             :     /**
     166             :      * Set the named property on the global object.
     167             :      * Optionally makes it {ReadOnly, DontEnum}. We do not allow to make it DontDelete, so that it can be hotloaded
     168             :      * by deleting it and re-creating it, which is done by setting @p replace to true.
     169             :      */
     170             :     template<typename T>
     171             :     bool SetGlobal(const char* name, const T& value, bool replace = false, bool constant = true, bool enumerate = true);
     172             : 
     173             :     /**
     174             :      * Get an object from the global scope or any lexical scope.
     175             :      * This can return globally accessible objects even if they are not properties
     176             :      * of the global object (e.g. ES6 class definitions).
     177             :      * @param name - Name of the property.
     178             :      * @param out The object or null.
     179             :      */
     180             :     static bool GetGlobalProperty(const ScriptRequest& rq, const std::string& name, JS::MutableHandleValue out);
     181             : 
     182             :     bool SetPrototype(JS::HandleValue obj, JS::HandleValue proto);
     183             : 
     184             :     /**
     185             :      * Load and execute the given script in a new function scope.
     186             :      * @param filename Name for debugging purposes (not used to load the file)
     187             :      * @param code JS code to execute
     188             :      * @return true on successful compilation and execution; false otherwise
     189             :      */
     190             :     bool LoadScript(const VfsPath& filename, const std::string& code) const;
     191             : 
     192             :     /**
     193             :      * Load and execute the given script in the global scope.
     194             :      * @param filename Name for debugging purposes (not used to load the file)
     195             :      * @param code JS code to execute
     196             :      * @return true on successful compilation and execution; false otherwise
     197             :      */
     198             :     bool LoadGlobalScript(const VfsPath& filename, const std::string& code) const;
     199             : 
     200             :     /**
     201             :      * Load and execute the given script in the global scope.
     202             :      * @return true on successful compilation and execution; false otherwise
     203             :      */
     204             :     bool LoadGlobalScriptFile(const VfsPath& path) const;
     205             : 
     206             :     /**
     207             :      * Evaluate some JS code in the global scope.
     208             :      * @return true on successful compilation and execution; false otherwise
     209             :      */
     210             :     bool Eval(const char* code) const;
     211             :     bool Eval(const char* code, JS::MutableHandleValue out) const;
     212             :     template<typename T> bool Eval(const char* code, T& out) const;
     213             : 
     214             :     /**
     215             :      * Calls the random number generator assigned to this ScriptInterface instance and returns the generated number.
     216             :      */
     217             :     bool MathRandom(double& nbr) const;
     218             : 
     219             :     /**
     220             :      * JSNative wrapper of the above.
     221             :      */
     222             :     static bool Math_random(JSContext* cx, uint argc, JS::Value* vp);
     223             : 
     224             :     /**
     225             :      * Retrieve the private data field of a JSObject that is an instance of the given JSClass.
     226             :      */
     227             :     template <typename T>
     228             :     static T* GetPrivate(const ScriptRequest& rq, JS::HandleObject thisobj, JSClass* jsClass)
     229             :     {
     230             :         T* value = static_cast<T*>(JS_GetInstancePrivate(rq.cx, thisobj, jsClass, nullptr));
     231             : 
     232             :         if (value == nullptr)
     233             :             ScriptException::Raise(rq, "Private data of the given object is null!");
     234             : 
     235             :         return value;
     236             :     }
     237             : 
     238             :     /**
     239             :      * Retrieve the private data field of a JS Object that is an instance of the given JSClass.
     240             :      * If an error occurs, GetPrivate will report it with the according stack.
     241             :      */
     242             :     template <typename T>
     243           1 :     static T* GetPrivate(const ScriptRequest& rq, JS::CallArgs& callArgs, JSClass* jsClass)
     244             :     {
     245           1 :         if (!callArgs.thisv().isObject())
     246             :         {
     247           0 :             ScriptException::Raise(rq, "Cannot retrieve private JS class data because from a non-object value!");
     248           0 :             return nullptr;
     249             :         }
     250             : 
     251           2 :         JS::RootedObject thisObj(rq.cx, &callArgs.thisv().toObject());
     252           1 :         T* value = static_cast<T*>(JS_GetInstancePrivate(rq.cx, thisObj, jsClass, &callArgs));
     253             : 
     254           1 :         if (value == nullptr)
     255           0 :             ScriptException::Raise(rq, "Private data of the given object is null!");
     256             : 
     257           1 :         return value;
     258             :     }
     259             : 
     260             : private:
     261             :     bool SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate);
     262             : 
     263       11392 :     struct CustomType
     264             :     {
     265             :         JS::PersistentRootedObject m_Prototype;
     266             :         JSClass* m_Class;
     267             :         JSNative m_Constructor;
     268             :     };
     269             : 
     270             :     CmptPrivate m_CmptPrivate;
     271             : 
     272             :     // Take care to keep this declaration before heap rooted members. Destructors of heap rooted
     273             :     // members have to be called before the custom destructor of ScriptInterface_impl.
     274             :     std::unique_ptr<ScriptInterface_impl> m;
     275             : 
     276             :     std::map<std::string, CustomType> m_CustomObjectTypes;
     277             : };
     278             : 
     279             : // Explicitly instantiate void* as that is used for the generic template,
     280             : // and we want to define it in the .cpp file.
     281             : template <> void* ScriptInterface::ObjectFromCBData(const ScriptRequest& rq);
     282             : 
     283             : template<typename T>
     284       10927 : bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace, bool constant, bool enumerate)
     285             : {
     286       21854 :     ScriptRequest rq(this);
     287       21854 :     JS::RootedValue val(rq.cx);
     288       10927 :     Script::ToJSVal(rq, &val, value);
     289       21854 :     return SetGlobal_(name, val, replace, constant, enumerate);
     290             : }
     291             : 
     292             : template<typename T>
     293           8 : bool ScriptInterface::Eval(const char* code, T& ret) const
     294             : {
     295          16 :     ScriptRequest rq(this);
     296          16 :     JS::RootedValue rval(rq.cx);
     297           8 :     if (!Eval(code, &rval))
     298           0 :         return false;
     299           8 :     return Script::FromJSVal(rq, rval, ret);
     300             : }
     301             : 
     302             : #endif // INCLUDED_SCRIPTINTERFACE

Generated by: LCOV version 1.13