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 : }
|