Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ScriptInterface.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 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 "ScriptInterface.h"
21 #include "DebuggingServer.h"
22 #include "ScriptStats.h"
23 #include "AutoRooters.h"
24 
25 #include "lib/debug.h"
26 #include "lib/utf8.h"
27 #include "ps/CLogger.h"
28 #include "ps/Filesystem.h"
29 #include "ps/Profile.h"
30 #include "ps/utf16string.h"
31 
32 #include <cassert>
33 #include <map>
34 
35 #define BOOST_MULTI_INDEX_DISABLE_SERIALIZATION
36 #include <boost/preprocessor/punctuation/comma_if.hpp>
37 #include <boost/preprocessor/repetition/repeat.hpp>
38 #include <boost/random/linear_congruential.hpp>
39 #include <boost/flyweight.hpp>
40 #include <boost/flyweight/key_value.hpp>
41 #include <boost/flyweight/no_locking.hpp>
42 #include <boost/flyweight/no_tracking.hpp>
43 
44 #include "valgrind.h"
45 
46 #define STACK_CHUNK_SIZE 8192
47 
49 
50 /**
51  * @file
52  * Abstractions of various SpiderMonkey features.
53  * Engine code should be using functions of these interfaces rather than
54  * directly accessing the underlying JS api.
55  */
56 
57 ////////////////////////////////////////////////////////////////
58 
59 /**
60  * Abstraction around a SpiderMonkey JSRuntime.
61  * Each ScriptRuntime can be used to initialize several ScriptInterface
62  * contexts which can then share data, but a single ScriptRuntime should
63  * only be used on a single thread.
64  *
65  * (One means to share data between threads and runtimes is to create
66  * a ScriptInterface::StructuredClone.)
67  */
69 {
70 public:
71  ScriptRuntime(int runtimeSize) :
72  m_rooter(NULL), m_compartmentGlobal(NULL)
73  {
74  m_rt = JS_NewRuntime(runtimeSize);
75  ENSURE(m_rt); // TODO: error handling
76 
78  {
79  // Profiler isn't thread-safe, so only enable this on the main thread
81  {
83  {
84  JS_SetExecuteHook(m_rt, jshook_script, this);
85  JS_SetCallHook(m_rt, jshook_function, this);
86  }
87  }
88  }
89 
90  JS_SetExtraGCRoots(m_rt, jshook_trace, this);
91  }
92 
94  {
95  JS_DestroyRuntime(m_rt);
96  }
97 
98  JSRuntime* m_rt;
100 
102 
103 private:
104 
105 
106  static void* jshook_script(JSContext* UNUSED(cx), JSStackFrame* UNUSED(fp), JSBool before, JSBool* UNUSED(ok), void* closure)
107  {
108  if (before)
109  g_Profiler.StartScript("script invocation");
110  else
111  g_Profiler.Stop();
112 
113  return closure;
114  }
115 
116  // To profile scripts usefully, we use a call hook that's called on every enter/exit,
117  // and need to find the function name. But most functions are anonymous so we make do
118  // with filename plus line number instead.
119  // Computing the names is fairly expensive, and we need to return an interned char*
120  // for the profiler to hold a copy of, so we use boost::flyweight to construct interned
121  // strings per call location.
122 
123  // Identifies a location in a script
125  {
126  JSContext* cx;
127  JSScript* script;
128  jsbytecode* pc;
129 
130  bool operator==(const ScriptLocation& b) const
131  {
132  return cx == b.cx && script == b.script && pc == b.pc;
133  }
134 
135  friend std::size_t hash_value(const ScriptLocation& loc)
136  {
137  std::size_t seed = 0;
138  boost::hash_combine(seed, loc.cx);
139  boost::hash_combine(seed, loc.script);
140  boost::hash_combine(seed, loc.pc);
141  return seed;
142  }
143  };
144 
145  // Computes and stores the name of a location in a script
147  {
149  {
150  JSContext* cx = loc.cx;
151  JSScript* script = loc.script;
152  jsbytecode* pc = loc.pc;
153 
154  std::string filename = JS_GetScriptFilename(cx, script);
155  size_t slash = filename.rfind('/');
156  if (slash != filename.npos)
157  filename = filename.substr(slash+1);
158 
159  uintN line = JS_PCToLineNumber(cx, script, pc);
160 
161  std::stringstream ss;
162  ss << "(" << filename << ":" << line << ")";
163  name = ss.str();
164  }
165 
166  std::string name;
167  };
168 
169  // Flyweight types (with no_locking because the call hooks are only used in the
170  // main thread, and no_tracking because we mustn't delete values the profiler is
171  // using and it's not going to waste much memory)
172  typedef boost::flyweight<
173  std::string,
174  boost::flyweights::no_tracking,
175  boost::flyweights::no_locking
177  typedef boost::flyweight<
178  boost::flyweights::key_value<ScriptLocation, ScriptLocationName>,
179  boost::flyweights::no_tracking,
180  boost::flyweights::no_locking
182 
183  static void* jshook_function(JSContext* cx, JSStackFrame* fp, JSBool before, JSBool* UNUSED(ok), void* closure)
184  {
185  if (!before)
186  {
187  g_Profiler.Stop();
188  return closure;
189  }
190 
191  JSFunction* fn = JS_GetFrameFunction(cx, fp);
192  if (!fn)
193  {
194  g_Profiler.StartScript("(function)");
195  return closure;
196  }
197 
198  // Try to get the name of non-anonymous functions
199  JSString* name = JS_GetFunctionId(fn);
200  if (name)
201  {
202  char* chars = JS_EncodeString(cx, name);
203  if (chars)
204  {
205  g_Profiler.StartScript(StringFlyweight(chars).get().c_str());
206  JS_free(cx, chars);
207  return closure;
208  }
209  }
210 
211  // No name - compute from the location instead
212  ScriptLocation loc = { cx, JS_GetFrameScript(cx, fp), JS_GetFramePC(cx, fp) };
213  g_Profiler.StartScript(LocFlyweight(loc).get().name.c_str());
214 
215  return closure;
216  }
217 
218  static void jshook_trace(JSTracer* trc, void* data)
219  {
220  ScriptRuntime* m = static_cast<ScriptRuntime*>(data);
221 
222  if (m->m_rooter)
223  m->m_rooter->Trace(trc);
224  }
225 };
226 
227 shared_ptr<ScriptRuntime> ScriptInterface::CreateRuntime(int runtimeSize)
228 {
229  return shared_ptr<ScriptRuntime>(new ScriptRuntime(runtimeSize));
230 }
231 
232 ////////////////////////////////////////////////////////////////
233 
235 {
236  ScriptInterface_impl(const char* nativeScopeName, const shared_ptr<ScriptRuntime>& runtime);
238  void Register(const char* name, JSNative fptr, uintN nargs);
239 
240  shared_ptr<ScriptRuntime> m_runtime;
241  JSContext* m_cx;
242  JSObject* m_glob; // global scope object
243  JSObject* m_nativeScope; // native function scope object
244  JSCrossCompartmentCall* m_call;
245 };
246 
247 namespace
248 {
249 
250 JSClass global_class = {
251  "global", JSCLASS_GLOBAL_FLAGS,
252  JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
253  JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
254  NULL, NULL, NULL, NULL,
255  NULL, NULL, NULL, NULL
256 };
257 
258 void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
259 {
260  // XXX Ugly hack: we want to compile code with 'use strict' and with JSOPTION_STRICT,
261  // but the latter causes the former to be reported as a useless expression, so
262  // ignore that specific warning here
263  if (report->flags == 5 && report->lineno == 0 && report->errorNumber == 163)
264  return;
265 
266  std::stringstream msg;
267  bool isWarning = JSREPORT_IS_WARNING(report->flags);
268  msg << (isWarning ? "JavaScript warning: " : "JavaScript error: ");
269  if (report->filename)
270  {
271  msg << report->filename;
272  msg << " line " << report->lineno << "\n";
273  }
274 
275  msg << message;
276 
277  // If there is an exception, then print its stack trace
278  jsval excn;
279  if (JS_GetPendingException(cx, &excn) && JSVAL_IS_OBJECT(excn))
280  {
281  // TODO: this violates the docs ("The error reporter callback must not reenter the JSAPI.")
282 
283  // Hide the exception from EvaluateScript
284  JSExceptionState* excnState = JS_SaveExceptionState(cx);
285  JS_ClearPendingException(cx);
286 
287  jsval rval;
288  const char dumpStack[] = "this.stack.trimRight().replace(/^/mg, ' ')"; // indent each line
289  if (JS_EvaluateScript(cx, JSVAL_TO_OBJECT(excn), dumpStack, ARRAY_SIZE(dumpStack)-1, "(eval)", 1, &rval))
290  {
291  std::string stackTrace;
292  if (ScriptInterface::FromJSVal(cx, rval, stackTrace))
293  msg << "\n" << stackTrace;
294 
295  JS_RestoreExceptionState(cx, excnState);
296  }
297  else
298  {
299  // Error got replaced by new exception from EvaluateScript
300  JS_DropExceptionState(cx, excnState);
301  }
302  }
303 
304  if (isWarning)
305  LOGWARNING(L"%hs", msg.str().c_str());
306  else
307  LOGERROR(L"%hs", msg.str().c_str());
308 
309  // When running under Valgrind, print more information in the error message
310 // VALGRIND_PRINTF_BACKTRACE("->");
311 }
312 
313 // Functions in the global namespace:
314 
315 JSBool print(JSContext* cx, uintN argc, jsval* vp)
316 {
317  for (uintN i = 0; i < argc; ++i)
318  {
319  std::string str;
320  if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[i], str))
321  return JS_FALSE;
322  debug_printf(L"%hs", str.c_str());
323  }
324  fflush(stdout);
325  JS_SET_RVAL(cx, vp, JSVAL_VOID);
326  return JS_TRUE;
327 }
328 
329 JSBool logmsg(JSContext* cx, uintN argc, jsval* vp)
330 {
331  if (argc < 1)
332  {
333  JS_SET_RVAL(cx, vp, JSVAL_VOID);
334  return JS_TRUE;
335  }
336 
337  std::wstring str;
338  if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], str))
339  return JS_FALSE;
340  LOGMESSAGE(L"%ls", str.c_str());
341  JS_SET_RVAL(cx, vp, JSVAL_VOID);
342  return JS_TRUE;
343 }
344 
345 JSBool warn(JSContext* cx, uintN argc, jsval* vp)
346 {
347  if (argc < 1)
348  {
349  JS_SET_RVAL(cx, vp, JSVAL_VOID);
350  return JS_TRUE;
351  }
352 
353  std::wstring str;
354  if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], str))
355  return JS_FALSE;
356  LOGWARNING(L"%ls", str.c_str());
357  JS_SET_RVAL(cx, vp, JSVAL_VOID);
358  return JS_TRUE;
359 }
360 
361 JSBool error(JSContext* cx, uintN argc, jsval* vp)
362 {
363  if (argc < 1)
364  {
365  JS_SET_RVAL(cx, vp, JSVAL_VOID);
366  return JS_TRUE;
367  }
368 
369  std::wstring str;
370  if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], str))
371  return JS_FALSE;
372  LOGERROR(L"%ls", str.c_str());
373  JS_SET_RVAL(cx, vp, JSVAL_VOID);
374  return JS_TRUE;
375 }
376 
377 JSBool deepcopy(JSContext* cx, uintN argc, jsval* vp)
378 {
379  if (argc < 1)
380  {
381  JS_SET_RVAL(cx, vp, JSVAL_VOID);
382  return JS_TRUE;
383  }
384 
385  jsval ret;
386 
387  // We'd usually do:
388  // if (!JS_StructuredClone(cx, JS_ARGV(cx, vp)[0], &ret, NULL, NULL))
389  // return JS_FALSE;
390  // but that function is broken in the 1.8.5 release
391  // (https://bugzilla.mozilla.org/show_bug.cgi?id=651510)
392  // so do an equivalent operation with a different API:
393  JSAutoStructuredCloneBuffer buf;
394  if (!buf.write(cx, JS_ARGV(cx, vp)[0]) || !buf.read(&ret, cx))
395  return JS_FALSE;
396 
397  JS_SET_RVAL(cx, vp, ret);
398  return JS_TRUE;
399 }
400 
401 JSBool ProfileStart(JSContext* cx, uintN argc, jsval* vp)
402 {
403  const char* name = "(ProfileStart)";
404 
405  if (argc >= 1)
406  {
407  std::string str;
408  if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], str))
409  return JS_FALSE;
410 
411  typedef boost::flyweight<
412  std::string,
413  boost::flyweights::no_tracking,
414  boost::flyweights::no_locking
415  > StringFlyweight;
416 
417  name = StringFlyweight(str).get().c_str();
418  }
419 
421  g_Profiler.StartScript(name);
422 
424 
425  JS_SET_RVAL(cx, vp, JSVAL_VOID);
426  return JS_TRUE;
427 }
428 
429 JSBool ProfileStop(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* vp)
430 {
432  g_Profiler.Stop();
433 
434  g_Profiler2.RecordRegionLeave("(ProfileStop)");
435 
436  JS_SET_RVAL(cx, vp, JSVAL_VOID);
437  return JS_TRUE;
438 }
439 
440 // Math override functions:
441 
442 // boost::uniform_real is apparently buggy in Boost pre-1.47 - for integer generators
443 // it returns [min,max], not [min,max). The bug was fixed in 1.47.
444 // We need consistent behaviour, so manually implement the correct version:
445 static double generate_uniform_real(boost::rand48& rng, double min, double max)
446 {
447  while (true)
448  {
449  double n = (double)(rng() - rng.min());
450  double d = (double)(rng.max() - rng.min()) + 1.0;
451  ENSURE(d > 0 && n >= 0 && n <= d);
452  double r = n / d * (max - min) + min;
453  if (r < max)
454  return r;
455  }
456 }
457 
458 JSBool Math_random(JSContext* cx, uintN UNUSED(argc), jsval* vp)
459 {
460  // Grab the RNG that was hidden in our slot
461  jsval rngp;
462  if (!JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), 0, &rngp))
463  return JS_FALSE;
464  boost::rand48* rng = static_cast<boost::rand48*>(JSVAL_TO_PRIVATE(rngp));
465 
466  double r = generate_uniform_real(*rng, 0.0, 1.0);
467 
468  jsval rv;
469  if (!JS_NewNumberValue(cx, r, &rv))
470  return JS_FALSE;
471  JS_SET_RVAL(cx, vp, rv);
472  return JS_TRUE;
473 }
474 
475 } // anonymous namespace
476 
477 ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr<ScriptRuntime>& runtime) :
478  m_runtime(runtime)
479 {
480  JSBool ok;
481 
482  m_cx = JS_NewContext(m_runtime->m_rt, STACK_CHUNK_SIZE);
483  ENSURE(m_cx);
484 
485  // For GC debugging:
486  // JS_SetGCZeal(m_cx, 2);
487 
488  JS_SetContextPrivate(m_cx, NULL);
489 
490  JS_SetErrorReporter(m_cx, ErrorReporter);
491 
492  uint32 options = 0;
493  options |= JSOPTION_STRICT; // "warn on dubious practice"
494  options |= JSOPTION_XML; // "ECMAScript for XML support: parse <!-- --> as a token"
495  options |= JSOPTION_VAROBJFIX; // "recommended" (fixes variable scoping)
496 
497  // Enable method JIT, unless script profiling/debugging is enabled (since profiling/debugging
498  // hooks are incompatible with the JIT)
499  // TODO: Verify what exactly is incompatible
501  {
502  options |= JSOPTION_METHODJIT;
503 
504  // Some other JIT flags to experiment with:
505  options |= JSOPTION_JIT;
506  options |= JSOPTION_PROFILING;
507  }
508 
509  JS_SetOptions(m_cx, options);
510 
511  JS_SetVersion(m_cx, JSVERSION_LATEST);
512 
513  // Threadsafe SpiderMonkey requires that we have a request before doing anything much
514  JS_BeginRequest(m_cx);
515 
516  // We only want a single compartment per runtime
517  if (m_runtime->m_compartmentGlobal)
518  {
519  m_call = JS_EnterCrossCompartmentCall(m_cx, m_runtime->m_compartmentGlobal);
520  m_glob = JS_NewGlobalObject(m_cx, &global_class);
521  }
522  else
523  {
524  m_call = NULL;
525  m_glob = JS_NewCompartmentAndGlobalObject(m_cx, &global_class, NULL);
526  m_runtime->m_compartmentGlobal = m_glob;
527  }
528 
529  ok = JS_InitStandardClasses(m_cx, m_glob);
530  ENSURE(ok);
531 
532  JS_DefineProperty(m_cx, m_glob, "global", OBJECT_TO_JSVAL(m_glob), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY
533  | JSPROP_PERMANENT);
534 
535  m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY
536  | JSPROP_PERMANENT);
537 
538  JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
539  JS_DefineFunction(m_cx, m_glob, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
540  JS_DefineFunction(m_cx, m_glob, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
541  JS_DefineFunction(m_cx, m_glob, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
542  JS_DefineFunction(m_cx, m_glob, "deepcopy", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
543 
544  Register("ProfileStart", ::ProfileStart, 1);
545  Register("ProfileStop", ::ProfileStop, 0);
546 }
547 
549 {
550  if (m_call)
551  JS_LeaveCrossCompartmentCall(m_call);
552  JS_EndRequest(m_cx);
553  JS_DestroyContext(m_cx);
554 }
555 
556 void ScriptInterface_impl::Register(const char* name, JSNative fptr, uintN nargs)
557 {
558  JSFunction* func = JS_DefineFunction(m_cx, m_nativeScope, name, fptr, nargs, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
559 
560  if (!func)
561  return;
562 
564  {
565  // Store the function name in a slot, so we can pass it to the profiler.
566 
567  // Use a flyweight std::string because we can't assume the caller has
568  // a correctly-aligned string and that its lifetime is long enough
569  typedef boost::flyweight<
570  std::string,
571  boost::flyweights::no_tracking
572  // can't use no_locking; Register might be called in threads
573  > LockedStringFlyweight;
574 
575  LockedStringFlyweight fw(name);
576  JS_SetReservedSlot(m_cx, JS_GetFunctionObject(func), 0, PRIVATE_TO_JSVAL((void*)fw.get().c_str()));
577  }
578 }
579 
580 ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr<ScriptRuntime>& runtime) :
581  m(new ScriptInterface_impl(nativeScopeName, runtime))
582 {
583  // Profiler stats table isn't thread-safe, so only enable this on the main thread
585  {
586  if (g_ScriptStatsTable)
587  g_ScriptStatsTable->Add(this, debugName);
588  }
589 
590  if (g_JSDebuggerEnabled && g_DebuggingServer != NULL)
591  {
592  if(!JS_SetDebugMode(GetContext(), true))
593  LOGERROR(L"Failed to set Spidermonkey to debug mode!");
594  else
595  g_DebuggingServer->RegisterScriptinterface(debugName, this);
596  }
597 }
598 
600 {
602  {
603  if (g_ScriptStatsTable)
604  g_ScriptStatsTable->Remove(this);
605  }
606 
607  // Unregister from the Debugger class
608  if (g_JSDebuggerEnabled && g_DebuggingServer != NULL)
610 }
611 
613 {
614  JS_ShutDown();
615 }
616 
618 {
619  JS_SetContextPrivate(m->m_cx, cbdata);
620 }
621 
623 {
624  return JS_GetContextPrivate(cx);
625 }
626 
628 {
629  // Ignore this failure in tests
630  if (!g_VFS)
631  return false;
632 
633  // Load and execute *.js in the global scripts directory
634  VfsPaths pathnames;
635  vfs::GetPathnames(g_VFS, L"globalscripts/", L"*.js", pathnames);
636  for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
637  {
638  if (!LoadGlobalScriptFile(*it))
639  {
640  LOGERROR(L"LoadGlobalScripts: Failed to load script %ls", it->string().c_str());
641  return false;
642  }
643  }
644 
645  return true;
646 }
647 
649 {
650  jsval math;
651  if (JS_GetProperty(m->m_cx, m->m_glob, "Math", &math) && JSVAL_IS_OBJECT(math))
652  {
653  JSFunction* random = JS_DefineFunction(m->m_cx, JSVAL_TO_OBJECT(math), "random", Math_random, 0,
654  JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
655  if (random)
656  {
657  // Store the RNG in a slot which is sort-of-guaranteed to be unused by the JS engine
658  if (JS_SetReservedSlot(m->m_cx, JS_GetFunctionObject(random), 0, PRIVATE_TO_JSVAL(&rng)))
659  return true;
660  }
661  }
662 
663  LOGERROR(L"ReplaceNondeterministicRNG: failed to replace Math.random");
664  return false;
665 }
666 
667 void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs)
668 {
669  m->Register(name, fptr, (uintN)nargs);
670 }
671 
672 JSContext* ScriptInterface::GetContext() const
673 {
674  return m->m_cx;
675 }
676 
677 JSRuntime* ScriptInterface::GetRuntime() const
678 {
679  return m->m_runtime->m_rt;
680 }
681 
683 {
684  AutoGCRooter* ret = m->m_runtime->m_rooter;
685  m->m_runtime->m_rooter = rooter;
686  return ret;
687 }
688 
689 
690 jsval ScriptInterface::CallConstructor(jsval ctor, jsval arg)
691 {
692  if (!JSVAL_IS_OBJECT(ctor))
693  {
694  LOGERROR(L"CallConstructor: ctor is not an object");
695  return JSVAL_VOID;
696  }
697 
698  return OBJECT_TO_JSVAL(JS_New(m->m_cx, JSVAL_TO_OBJECT(ctor), 1, &arg));
699 }
700 
702 {
703  // Get the constructor's prototype
704  // (Can't use JS_GetPrototype, since we want .prototype not .__proto__)
705  jsval protoVal;
706  if (!JS_GetProperty(m->m_cx, JSVAL_TO_OBJECT(ctor), "prototype", &protoVal))
707  {
708  LOGERROR(L"NewObjectFromConstructor: can't get prototype");
709  return JSVAL_VOID;
710  }
711 
712  if (!JSVAL_IS_OBJECT(protoVal))
713  {
714  LOGERROR(L"NewObjectFromConstructor: prototype is not an object");
715  return JSVAL_VOID;
716  }
717 
718  JSObject* proto = JSVAL_TO_OBJECT(protoVal);
719  JSObject* parent = JS_GetParent(m->m_cx, JSVAL_TO_OBJECT(ctor));
720  // TODO: rooting?
721  if (!proto || !parent)
722  {
723  LOGERROR(L"NewObjectFromConstructor: null proto/parent");
724  return JSVAL_VOID;
725  }
726 
727  JSObject* obj = JS_NewObject(m->m_cx, NULL, proto, parent);
728  if (!obj)
729  {
730  LOGERROR(L"NewObjectFromConstructor: object creation failed");
731  return JSVAL_VOID;
732  }
733 
734  return OBJECT_TO_JSVAL(obj);
735 }
736 
737 bool ScriptInterface::CallFunctionVoid(jsval val, const char* name)
738 {
739  jsval jsRet;
740  return CallFunction_(val, name, 0, NULL, jsRet);
741 }
742 
743 bool ScriptInterface::CallFunction_(jsval val, const char* name, size_t argc, jsval* argv, jsval& ret)
744 {
745  JSObject* obj;
746  if (!JS_ValueToObject(m->m_cx, val, &obj) || obj == NULL)
747  return false;
748 
749  // Check that the named function actually exists, to avoid ugly JS error reports
750  // when calling an undefined value
751  JSBool found;
752  if (!JS_HasProperty(m->m_cx, obj, name, &found) || !found)
753  return false;
754 
755  JSBool ok = JS_CallFunctionName(m->m_cx, obj, name, (uintN)argc, argv, &ret);
756 
757  return ok ? true : false;
758 }
759 
761 {
762  return OBJECT_TO_JSVAL(JS_GetGlobalObject(m->m_cx));
763 }
764 
766 {
767  return &global_class;
768 }
769 
770 bool ScriptInterface::SetGlobal_(const char* name, jsval value, bool replace)
771 {
772  if (!replace)
773  {
774  JSBool found;
775  if (!JS_HasProperty(m->m_cx, m->m_glob, name, &found))
776  return false;
777  if (found)
778  {
779  JS_ReportError(m->m_cx, "SetGlobal \"%s\" called multiple times", name);
780  return false;
781  }
782  }
783 
784  JSBool ok = JS_DefineProperty(m->m_cx, m->m_glob, name, value, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY
785  | JSPROP_PERMANENT);
786  return ok ? true : false;
787 }
788 
789 bool ScriptInterface::SetProperty_(jsval obj, const char* name, jsval value, bool constant, bool enumerate)
790 {
791  uintN attrs = 0;
792  if (constant)
793  attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
794  if (enumerate)
795  attrs |= JSPROP_ENUMERATE;
796 
797  if (! JSVAL_IS_OBJECT(obj))
798  return false;
799  JSObject* object = JSVAL_TO_OBJECT(obj);
800 
801  if (! JS_DefineProperty(m->m_cx, object, name, value, NULL, NULL, attrs))
802  return false;
803  return true;
804 }
805 
806 bool ScriptInterface::SetPropertyInt_(jsval obj, int name, jsval value, bool constant, bool enumerate)
807 {
808  uintN attrs = 0;
809  if (constant)
810  attrs |= JSPROP_READONLY | JSPROP_PERMANENT;
811  if (enumerate)
812  attrs |= JSPROP_ENUMERATE;
813 
814  if (! JSVAL_IS_OBJECT(obj))
815  return false;
816  JSObject* object = JSVAL_TO_OBJECT(obj);
817 
818  if (! JS_DefinePropertyById(m->m_cx, object, INT_TO_JSID(name), value, NULL, NULL, attrs))
819  return false;
820  return true;
821 }
822 
823 bool ScriptInterface::GetProperty_(jsval obj, const char* name, jsval& out)
824 {
825  if (! JSVAL_IS_OBJECT(obj))
826  return false;
827  JSObject* object = JSVAL_TO_OBJECT(obj);
828 
829  if (!JS_GetProperty(m->m_cx, object, name, &out))
830  return false;
831  return true;
832 }
833 
834 bool ScriptInterface::GetPropertyInt_(jsval obj, int name, jsval& out)
835 {
836  if (! JSVAL_IS_OBJECT(obj))
837  return false;
838  JSObject* object = JSVAL_TO_OBJECT(obj);
839 
840  if (!JS_GetPropertyById(m->m_cx, object, INT_TO_JSID(name), &out))
841  return false;
842  return true;
843 }
844 
845 bool ScriptInterface::HasProperty(jsval obj, const char* name)
846 {
847  if (! JSVAL_IS_OBJECT(obj))
848  return false;
849  JSObject* object = JSVAL_TO_OBJECT(obj);
850 
851  JSBool found;
852  if (!JS_HasProperty(m->m_cx, object, name, &found))
853  return false;
854  return (found != JS_FALSE);
855 }
856 
857 bool ScriptInterface::EnumeratePropertyNamesWithPrefix(jsval obj, const char* prefix, std::vector<std::string>& out)
858 {
859  utf16string prefix16 (prefix, prefix+strlen(prefix));
860 
861  if (! JSVAL_IS_OBJECT(obj))
862  return false; // TODO: log error messages
863 
864  JSObject* it = JS_NewPropertyIterator(m->m_cx, JSVAL_TO_OBJECT(obj));
865  if (!it)
866  return false;
867 
868  while (true)
869  {
870  jsid idp;
871  jsval val;
872  if (! JS_NextProperty(m->m_cx, it, &idp) || ! JS_IdToValue(m->m_cx, idp, &val))
873  return false;
874  if (val == JSVAL_VOID)
875  break; // end of iteration
876  if (! JSVAL_IS_STRING(val))
877  continue; // ignore integer properties
878 
879  JSString* name = JSVAL_TO_STRING(val);
880  size_t len;
881  const jschar* chars = JS_GetStringCharsAndLength(m->m_cx, name, &len);
882  if (chars && len >= prefix16.size() && memcmp(chars, prefix16.c_str(), prefix16.size()*2) == 0)
883  out.push_back(std::string(chars, chars+len)); // handles Unicode poorly
884  }
885 
886  // Recurse up the prototype chain
887  JSObject* prototype = JS_GetPrototype(m->m_cx, JSVAL_TO_OBJECT(obj));
888  if (prototype)
889  {
890  if (! EnumeratePropertyNamesWithPrefix(OBJECT_TO_JSVAL(prototype), prefix, out))
891  return false;
892  }
893 
894  return true;
895 }
896 
897 bool ScriptInterface::SetPrototype(jsval obj, jsval proto)
898 {
899  if (!JSVAL_IS_OBJECT(obj) || !JSVAL_IS_OBJECT(proto))
900  return false;
901  return JS_SetPrototype(m->m_cx, JSVAL_TO_OBJECT(obj), JSVAL_TO_OBJECT(proto)) ? true : false;
902 }
903 
904 bool ScriptInterface::FreezeObject(jsval obj, bool deep)
905 {
906  if (!JSVAL_IS_OBJECT(obj))
907  return false;
908 
909  if (deep)
910  return JS_DeepFreezeObject(m->m_cx, JSVAL_TO_OBJECT(obj)) ? true : false;
911  else
912  return JS_FreezeObject(m->m_cx, JSVAL_TO_OBJECT(obj)) ? true : false;
913 }
914 
915 bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code)
916 {
917  // Compile the code in strict mode, to encourage better coding practices and
918  // to possibly help SpiderMonkey with optimisations
919  std::wstring codeStrict = L"\"use strict\";\n" + wstring_from_utf8(code);
920  utf16string codeUtf16(codeStrict.begin(), codeStrict.end());
921  uintN lineNo = 0; // put the automatic 'use strict' on line 0, so the real code starts at line 1
922 
923  JSFunction* func = JS_CompileUCFunction(m->m_cx, NULL, NULL, 0, NULL,
924  reinterpret_cast<const jschar*> (codeUtf16.c_str()), (uintN)(codeUtf16.length()),
925  utf8_from_wstring(filename.string()).c_str(), lineNo);
926 
927  if (!func)
928  return false;
929 
930  jsval scriptRval;
931  JSBool ok = JS_CallFunction(m->m_cx, NULL, func, 0, NULL, &scriptRval);
932 
933  return ok ? true : false;
934 }
935 
936 bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::string& code)
937 {
938  // Compile the code in strict mode, to encourage better coding practices and
939  // to possibly help SpiderMonkey with optimisations
940  std::wstring codeStrict = L"\"use strict\";\n" + wstring_from_utf8(code);
941  utf16string codeUtf16(codeStrict.begin(), codeStrict.end());
942  uintN lineNo = 0; // put the automatic 'use strict' on line 0, so the real code starts at line 1
943 
944  jsval rval;
945  JSBool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob,
946  reinterpret_cast<const jschar*> (codeUtf16.c_str()), (uintN)(codeUtf16.length()),
947  utf8_from_wstring(filename.string()).c_str(), lineNo, &rval);
948 
949  return ok ? true : false;
950 }
951 
953 {
954  if (!VfsFileExists(path))
955  {
956  LOGERROR(L"File '%ls' does not exist", path.string().c_str());
957  return false;
958  }
959 
960  CVFSFile file;
961 
962  PSRETURN ret = file.Load(g_VFS, path);
963 
964  if (ret != PSRETURN_OK)
965  {
966  LOGERROR(L"Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret));
967  return false;
968  }
969 
970  std::wstring code = wstring_from_utf8(file.DecodeUTF8()); // assume it's UTF-8
971 
972  // Compile the code in strict mode, to encourage better coding practices and
973  // to possibly help SpiderMonkey with optimisations
974  std::wstring codeStrict = L"\"use strict\";\n" + code;
975  utf16string codeUtf16(codeStrict.begin(), codeStrict.end());
976  uintN lineNo = 0; // put the automatic 'use strict' on line 0, so the real code starts at line 1
977 
978  jsval rval;
979  JSBool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob,
980  reinterpret_cast<const jschar*> (codeUtf16.c_str()), (uintN)(codeUtf16.length()),
981  utf8_from_wstring(path.string()).c_str(), lineNo, &rval);
982 
983  return ok ? true : false;
984 }
985 
986 
987 bool ScriptInterface::Eval(const char* code)
988 {
989  jsval rval;
990  return Eval_(code, rval);
991 }
992 
993 bool ScriptInterface::Eval_(const char* code, jsval& rval)
994 {
995  utf16string codeUtf16(code, code+strlen(code));
996 
997  JSBool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, (const jschar*)codeUtf16.c_str(), (uintN)codeUtf16.length(), "(eval)", 1, &rval);
998  return ok ? true : false;
999 }
1000 
1001 bool ScriptInterface::Eval_(const wchar_t* code, jsval& rval)
1002 {
1003  utf16string codeUtf16(code, code+wcslen(code));
1004 
1005  JSBool ok = JS_EvaluateUCScript(m->m_cx, m->m_glob, (const jschar*)codeUtf16.c_str(), (uintN)codeUtf16.length(), "(eval)", 1, &rval);
1006  return ok ? true : false;
1007 }
1008 
1009 CScriptValRooted ScriptInterface::ParseJSON(const std::string& string_utf8)
1010 {
1011  std::wstring attrsW = wstring_from_utf8(string_utf8);
1012  utf16string string(attrsW.begin(), attrsW.end());
1013 
1014  jsval vp;
1015  JSONParser* parser = JS_BeginJSONParse(m->m_cx, &vp);
1016  if (!parser)
1017  {
1018  LOGERROR(L"ParseJSON failed to begin");
1019  return CScriptValRooted();
1020  }
1021 
1022  if (!JS_ConsumeJSONText(m->m_cx, parser, reinterpret_cast<const jschar*>(string.c_str()), (uint32)string.size()))
1023  {
1024  LOGERROR(L"ParseJSON failed to consume");
1025  return CScriptValRooted();
1026  }
1027 
1028  if (!JS_FinishJSONParse(m->m_cx, parser, JSVAL_NULL))
1029  {
1030  LOGERROR(L"ParseJSON failed to finish");
1031  return CScriptValRooted();
1032  }
1033 
1034  return CScriptValRooted(m->m_cx, vp);
1035 }
1036 
1038 {
1039  if (!VfsFileExists(path))
1040  {
1041  LOGERROR(L"File '%ls' does not exist", path.string().c_str());
1042  return CScriptValRooted();
1043  }
1044 
1045  CVFSFile file;
1046 
1047  PSRETURN ret = file.Load(g_VFS, path);
1048 
1049  if (ret != PSRETURN_OK)
1050  {
1051  LOGERROR(L"Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret));
1052  return CScriptValRooted();
1053  }
1054 
1055  std::string content(file.DecodeUTF8()); // assume it's UTF-8
1056 
1057  return ParseJSON(content);
1058 }
1059 
1061 {
1062  static JSBool callback(const jschar* buf, uint32 len, void* data)
1063  {
1064  utf16string str(buf, buf+len);
1065  std::wstring strw(str.begin(), str.end());
1066 
1067  Status err; // ignore Unicode errors
1068  static_cast<Stringifier*>(data)->stream << utf8_from_wstring(strw, &err);
1069  return JS_TRUE;
1070  }
1071 
1072  std::stringstream stream;
1073 };
1074 
1076 {
1077  static JSBool callback(const jschar* buf, uint32 len, void* data)
1078  {
1079  utf16string str(buf, buf+len);
1080  static_cast<StringifierW*>(data)->stream << std::wstring(str.begin(), str.end());
1081  return JS_TRUE;
1082  }
1083 
1084  std::wstringstream stream;
1085 };
1086 
1087 std::string ScriptInterface::StringifyJSON(jsval obj, bool indent)
1088 {
1089  Stringifier str;
1090  if (!JS_Stringify(m->m_cx, &obj, NULL, indent ? INT_TO_JSVAL(2) : JSVAL_VOID, &Stringifier::callback, &str))
1091  {
1092  JS_ClearPendingException(m->m_cx);
1093  LOGERROR(L"StringifyJSON failed");
1094  JS_ClearPendingException(m->m_cx);
1095  return std::string();
1096  }
1097 
1098  return str.stream.str();
1099 }
1100 
1101 
1102 std::wstring ScriptInterface::ToString(jsval obj, bool pretty)
1103 {
1104  if (JSVAL_IS_VOID(obj))
1105  return L"(void 0)";
1106 
1107  // Try to stringify as JSON if possible
1108  // (TODO: this is maybe a bad idea since it'll drop 'undefined' values silently)
1109  if (pretty)
1110  {
1111  StringifierW str;
1112 
1113  // Temporary disable the error reporter, so we don't print complaints about cyclic values
1114  JSErrorReporter er = JS_SetErrorReporter(m->m_cx, NULL);
1115 
1116  bool ok = JS_Stringify(m->m_cx, &obj, NULL, INT_TO_JSVAL(2), &StringifierW::callback, &str) == JS_TRUE;
1117 
1118  // Restore error reporter
1119  JS_SetErrorReporter(m->m_cx, er);
1120 
1121  if (ok)
1122  return str.stream.str();
1123 
1124  // Clear the exception set when Stringify failed
1125  JS_ClearPendingException(m->m_cx);
1126  }
1127 
1128  // Caller didn't want pretty output, or JSON conversion failed (e.g. due to cycles),
1129  // so fall back to obj.toSource()
1130 
1131  std::wstring source = L"(error)";
1132  CallFunction(obj, "toSource", source);
1133  return source;
1134 }
1135 
1136 void ScriptInterface::ReportError(const char* msg)
1137 {
1138  // JS_ReportError by itself doesn't seem to set a JS-style exception, and so
1139  // script callers will be unable to catch anything. So use JS_SetPendingException
1140  // to make sure there really is a script-level exception. But just set it to undefined
1141  // because there's not much value yet in throwing a real exception object.
1142  JS_SetPendingException(m->m_cx, JSVAL_VOID);
1143  // And report the actual error
1144  JS_ReportError(m->m_cx, "%s", msg);
1145 
1146  // TODO: Why doesn't JS_ReportPendingException(m->m_cx); work?
1147 }
1148 
1150 {
1151  return JS_IsExceptionPending(cx) ? true : false;
1152 }
1153 
1154 JSClass* ScriptInterface::GetClass(JSContext* cx, JSObject* obj)
1155 {
1156  UNUSED2(cx); // unused if not JS_THREADSAFE
1157 
1158  return JS_GET_CLASS(cx, obj);
1159 }
1160 
1161 void* ScriptInterface::GetPrivate(JSContext* cx, JSObject* obj)
1162 {
1163  // TODO: use JS_GetInstancePrivate
1164  return JS_GetPrivate(cx, obj);
1165 }
1166 
1168 {
1169 #if MOZJS_DEBUG_ABI
1170  JS_DumpHeap(m->m_cx, stderr, NULL, 0, NULL, (size_t)-1, NULL);
1171 #endif
1172  fprintf(stderr, "# Bytes allocated: %u\n", JS_GetGCParameter(GetRuntime(), JSGC_BYTES));
1173  JS_GC(m->m_cx);
1174  fprintf(stderr, "# Bytes allocated after GC: %u\n", JS_GetGCParameter(GetRuntime(), JSGC_BYTES));
1175 }
1176 
1178 {
1179  JS_MaybeGC(m->m_cx);
1180 }
1181 
1183 {
1184 public:
1186  scriptInterfaceFrom(from), cxFrom(from.GetContext()), cxTo(to.GetContext()), m_RooterFrom(from), m_RooterTo(to)
1187  {
1188  }
1189 
1190  // Return the cloned object (or an already-computed object if we've cloned val before)
1191  jsval GetOrClone(jsval val)
1192  {
1193  if (!JSVAL_IS_GCTHING(val) || JSVAL_IS_NULL(val))
1194  return val;
1195 
1196  std::map<void*, jsval>::iterator it = m_Mapping.find(JSVAL_TO_GCTHING(val));
1197  if (it != m_Mapping.end())
1198  return it->second;
1199 
1200  m_RooterFrom.Push(val); // root it so our mapping doesn't get invalidated
1201 
1202  return Clone(val);
1203  }
1204 
1205 private:
1206 
1207 #define CLONE_REQUIRE(expr, msg) if (!(expr)) { debug_warn(L"Internal error in CloneValueFromOtherContext: " msg); return JSVAL_VOID; }
1208 
1209  // Clone a new value (and root it and add it to the mapping)
1210  jsval Clone(jsval val)
1211  {
1212  if (JSVAL_IS_DOUBLE(val))
1213  {
1214  jsval rval;
1215  CLONE_REQUIRE(JS_NewNumberValue(cxTo, JSVAL_TO_DOUBLE(val), &rval), L"JS_NewNumberValue");
1216  m_RooterTo.Push(rval);
1217  return rval;
1218  }
1219 
1220  if (JSVAL_IS_STRING(val))
1221  {
1222  size_t len;
1223  const jschar* chars = JS_GetStringCharsAndLength(cxFrom, JSVAL_TO_STRING(val), &len);
1224  CLONE_REQUIRE(chars, L"JS_GetStringCharsAndLength");
1225  JSString* str = JS_NewUCStringCopyN(cxTo, chars, len);
1226  CLONE_REQUIRE(str, L"JS_NewUCStringCopyN");
1227  jsval rval = STRING_TO_JSVAL(str);
1228  m_Mapping[JSVAL_TO_GCTHING(val)] = rval;
1229  m_RooterTo.Push(rval);
1230  return rval;
1231  }
1232 
1233  ENSURE(JSVAL_IS_OBJECT(val));
1234 
1235  JSObject* newObj;
1236  if (JS_IsArrayObject(cxFrom, JSVAL_TO_OBJECT(val)))
1237  {
1238  jsuint length;
1239  CLONE_REQUIRE(JS_GetArrayLength(cxFrom, JSVAL_TO_OBJECT(val), &length), L"JS_GetArrayLength");
1240  newObj = JS_NewArrayObject(cxTo, length, NULL);
1241  CLONE_REQUIRE(newObj, L"JS_NewArrayObject");
1242  }
1243  else
1244  {
1245  newObj = JS_NewObject(cxTo, NULL, NULL, NULL);
1246  CLONE_REQUIRE(newObj, L"JS_NewObject");
1247  }
1248 
1249  m_Mapping[JSVAL_TO_GCTHING(val)] = OBJECT_TO_JSVAL(newObj);
1250  m_RooterTo.Push(newObj);
1251 
1252  AutoJSIdArray ida (cxFrom, JS_Enumerate(cxFrom, JSVAL_TO_OBJECT(val)));
1253  CLONE_REQUIRE(ida.get(), L"JS_Enumerate");
1254 
1255  AutoGCRooter idaRooter(scriptInterfaceFrom);
1256  idaRooter.Push(ida.get());
1257 
1258  for (size_t i = 0; i < ida.length(); ++i)
1259  {
1260  jsid id = ida[i];
1261  jsval idval, propval;
1262  CLONE_REQUIRE(JS_IdToValue(cxFrom, id, &idval), L"JS_IdToValue");
1263  CLONE_REQUIRE(JS_GetPropertyById(cxFrom, JSVAL_TO_OBJECT(val), id, &propval), L"JS_GetPropertyById");
1264  jsval newPropval = GetOrClone(propval);
1265 
1266  if (JSVAL_IS_INT(idval))
1267  {
1268  // int jsids are portable across runtimes
1269  CLONE_REQUIRE(JS_SetPropertyById(cxTo, newObj, id, &newPropval), L"JS_SetPropertyById");
1270  }
1271  else if (JSVAL_IS_STRING(idval))
1272  {
1273  // string jsids are runtime-specific, so we need to copy the string content
1274  JSString* idstr = JS_ValueToString(cxFrom, idval);
1275  CLONE_REQUIRE(idstr, L"JS_ValueToString (id)");
1276  size_t len;
1277  const jschar* chars = JS_GetStringCharsAndLength(cxFrom, idstr, &len);
1278  CLONE_REQUIRE(idstr, L"JS_GetStringCharsAndLength (id)");
1279  CLONE_REQUIRE(JS_SetUCProperty(cxTo, newObj, chars, len, &newPropval), L"JS_SetUCProperty");
1280  }
1281  else
1282  {
1283  // this apparently could be an XML object; ignore it
1284  }
1285  }
1286 
1287  return OBJECT_TO_JSVAL(newObj);
1288  }
1289 
1291  JSContext* cxFrom;
1292  JSContext* cxTo;
1293  std::map<void*, jsval> m_Mapping;
1296 };
1297 
1299 {
1300  PROFILE("CloneValueFromOtherContext");
1301 
1302  ValueCloner cloner(otherContext, *this);
1303  return cloner.GetOrClone(val);
1304 }
1305 
1307  m_Context(NULL), m_Data(NULL), m_Size(0)
1308 {
1309 }
1310 
1312 {
1313  if (m_Data)
1314  JS_free(m_Context, m_Data);
1315 }
1316 
1317 shared_ptr<ScriptInterface::StructuredClone> ScriptInterface::WriteStructuredClone(jsval v)
1318 {
1319  uint64* data = NULL;
1320  size_t nbytes = 0;
1321  if (!JS_WriteStructuredClone(m->m_cx, v, &data, &nbytes, NULL, NULL))
1322  return shared_ptr<StructuredClone>();
1323  // TODO: should we have better error handling?
1324  // Currently we'll probably continue and then crash in ReadStructuredClone
1325 
1326  shared_ptr<StructuredClone> ret (new StructuredClone);
1327  ret->m_Context = m->m_cx;
1328  ret->m_Data = data;
1329  ret->m_Size = nbytes;
1330  return ret;
1331 }
1332 
1333 jsval ScriptInterface::ReadStructuredClone(const shared_ptr<ScriptInterface::StructuredClone>& ptr)
1334 {
1335  jsval ret = JSVAL_VOID;
1336  JS_ReadStructuredClone(m->m_cx, ptr->m_Data, ptr->m_Size, JS_STRUCTURED_CLONE_VERSION, &ret, NULL, NULL);
1337  return ret;
1338 }
CDebuggingServer * g_DebuggingServer
bool SetPrototype(jsval obj, jsval proto)
jsval GetOrClone(jsval val)
CStr DecodeUTF8() const
Returns contents of a UTF-8 encoded file as a string with optional BOM removed.
Definition: Filesystem.cpp:153
bool ReplaceNondeterministicRNG(boost::rand48 &rng)
Replace the default JS random number geenrator with a seeded, network-sync&#39;d one. ...
bool CallFunction_(jsval val, const char *name, size_t argc, jsval *argv, jsval &ret)
AutoGCRooter m_RooterTo
CScriptStatsTable * g_ScriptStatsTable
Definition: ScriptStats.cpp:26
static JSBool callback(const jschar *buf, uint32 len, void *data)
void Add(const ScriptInterface *scriptInterface, const std::string &title)
Definition: ScriptStats.cpp:41
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
static void jshook_trace(JSTracer *trc, void *data)
bool LoadGlobalScript(const VfsPath &filename, const std::string &code)
Load and execute the given script in the global scope.
boost::flyweight< boost::flyweights::key_value< ScriptLocation, ScriptLocationName >, boost::flyweights::no_tracking, boost::flyweights::no_locking > LocFlyweight
std::stringstream stream
bool SetGlobal_(const char *name, jsval value, bool replace)
#define LOGERROR
Definition: CLogger.h:35
bool HasProperty(jsval obj, const char *name)
Check the named property has been defined on the given object.
void SetCallbackData(void *cbdata)
const PSRETURN PSRETURN_OK
Definition: Errors.h:103
AutoGCRooter * m_rooter
Reads a file, then gives read-only access to the contents.
Definition: Filesystem.h:69
std::string utf8_from_wstring(const std::wstring &src, Status *err)
opposite of wstring_from_utf8
Definition: utf8.cpp:208
void Register(const char *name, JSNative fptr, uintN nargs)
AutoGCRooter m_RooterFrom
void RegisterScriptinterface(std::string name, ScriptInterface *pScriptInterface)
Register a new ScriptInerface for debugging the scripts it executes.
JSBool logmsg(JSContext *cx, uintN argc, jsval *vp)
#define LOGMESSAGE
Definition: CLogger.h:32
static CStr prefix
Definition: DllLoader.cpp:47
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:419
bool CallFunctionVoid(jsval val, const char *name)
Call the named property on the given object, with void return type and 0 arguments.
bool g_JSDebuggerEnabled
Definition: Config.cpp:64
void Register(const char *name, JSNative fptr, size_t nargs)
JSRuntime * GetRuntime() const
static void ShutDown()
Shut down the JS system to clean up memory.
bool GetProperty_(jsval obj, const char *name, jsval &value)
bool FreezeObject(jsval obj, bool deep)
bool LoadScript(const VfsPath &filename, const std::string &code)
Load and execute the given script in a new function scope.
bool LoadGlobalScripts()
Load global scripts that most script contexts need, located in the /globalscripts directory...
std::string StringifyJSON(jsval obj, bool indent=true)
Stringify to a JSON string, UTF-8 encoded.
CScriptValRooted ParseJSON(const std::string &string_utf8)
Parse a UTF-8-encoded JSON string.
bool SetPropertyInt_(jsval obj, int name, jsval value, bool readonly, bool enumerate)
std::map< void *, jsval > m_Mapping
#define ARRAY_SIZE(name)
static double generate_uniform_real(boost::rand48 &rng, double min, double max)
bool g_ScriptProfilingEnabled
Definition: Config.cpp:65
Structured clones are a way to serialize &#39;simple&#39; JS values into a buffer that can safely be passed b...
AutoGCRooter * ReplaceAutoGCRooter(AutoGCRooter *rooter)
void DumpHeap()
Dump some memory heap debugging information to stderr.
static bool IsExceptionPending(JSContext *cx)
jsval NewObjectFromConstructor(jsval ctor)
Create an object as with CallConstructor except don&#39;t actually execute the constructor function...
#define LOGWARNING
Definition: CLogger.h:34
ScriptInterface(const char *nativeScopeName, const char *debugName, const shared_ptr< ScriptRuntime > &runtime)
Constructor.
shared_ptr< ScriptRuntime > m_runtime
friend std::size_t hash_value(const ScriptLocation &loc)
static bool FromJSVal(JSContext *cx, jsval val, T &ret)
Convert a jsval to a C++ type.
JSContext * cxTo
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
#define UNUSED2(param)
mark a function local variable or parameter as unused and avoid the corresponding compiler warning...
JSContext * cxFrom
#define g_Profiler
Definition: Profile.h:147
JSBool ProfileStart(JSContext *cx, uintN argc, jsval *vp)
ScriptLocationName(const ScriptLocation &loc)
CProfiler2 g_Profiler2
Definition: Profiler2.cpp:35
JSObject * m_compartmentGlobal
u32 PSRETURN
Definition: Errors.h:75
JSBool Math_random(JSContext *cx, uintN argc, jsval *vp)
JSCrossCompartmentCall * m_call
static void * GetCallbackData(JSContext *cx)
ValueCloner(ScriptInterface &from, ScriptInterface &to)
Definition: path.h:75
boost::mt19937 rng
Random number generator (Boost Mersenne Twister)
Definition: Noise.cpp:34
CScriptValRooted ReadJSONFile(const VfsPath &path)
Read a JSON file.
const String & string() const
Definition: path.h:123
static JSBool callback(const jschar *buf, uint32 len, void *data)
bool LoadGlobalScriptFile(const VfsPath &path)
Load and execute the given script in the global scope.
static JSClass * GetClass(JSContext *cx, JSObject *obj)
void Remove(const ScriptInterface *scriptInterface)
Definition: ScriptStats.cpp:46
bool Eval(const char *code)
JSBool print(JSContext *cx, uintN argc, jsval *vp)
#define PROFILE(name)
Definition: Profile.h:195
#define STACK_CHUNK_SIZE
i64 Status
Error handling system.
Definition: status.h:171
static bool IsInitialised()
Definition: Singleton.h:63
boost::flyweight< std::string, boost::flyweights::no_tracking, boost::flyweights::no_locking > StringFlyweight
static void * jshook_function(JSContext *cx, JSStackFrame *fp, JSBool before, JSBool *ok, void *closure)
size_t length() const
Definition: ScriptVal.cpp:84
jsval CloneValueFromOtherContext(ScriptInterface &otherContext, jsval val)
Construct a new value (usable in this ScriptInterface&#39;s context) by cloning a value from a different ...
const char * GetErrorString(PSRETURN code)
Definition: Errors.cpp:458
void Push(JSObject *obj)
Definition: AutoRooters.h:38
std::wstring wstring_from_utf8(const std::string &src, Status *err)
convert UTF-8 to a wide string (UTF-16 or UCS-4, depending on the platform&#39;s wchar_t).
Definition: utf8.cpp:225
bool CallFunction(jsval val, const char *name, R &ret)
Call the named property on the given object, with return type R and 0 arguments.
JSIdArray * get() const
Definition: ScriptVal.cpp:79
void RecordRegionEnter(const char *id)
Definition: Profiler2.h:315
std::basic_string< utf16_t, utf16_traits > utf16string
Definition: utf16string.h:109
bool SetProperty_(jsval obj, const char *name, jsval value, bool readonly, bool enumerate)
ScriptInterface_impl(const char *nativeScopeName, const shared_ptr< ScriptRuntime > &runtime)
PSRETURN Load(const PIVFS &vfs, const VfsPath &filename)
Returns either PSRETURN_OK or PSRETURN_CVFSFile_LoadFailed.
Definition: Filesystem.cpp:117
jsval CallConstructor(jsval ctor, jsval arg)
Call a constructor function, equivalent to JS &quot;new ctor(arg)&quot;.
JSClass * GetGlobalClass()
bool VfsFileExists(const VfsPath &pathname)
Definition: Filesystem.cpp:34
static void * jshook_script(JSContext *cx, JSStackFrame *fp, JSBool before, JSBool *ok, void *closure)
bool IsMainThread()
Returns whether the current thread is the &#39;main&#39; thread (i.e.
Definition: ThreadUtil.cpp:25
std::vector< VfsPath > VfsPaths
Definition: vfs_path.h:42
JSBool error(JSContext *cx, uintN argc, jsval *vp)
static void * GetPrivate(JSContext *cx, JSObject *obj)
void ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
#define CLONE_REQUIRE(expr, msg)
Abstraction around a SpiderMonkey JSContext.
void RecordRegionLeave(const char *id)
Definition: Profiler2.h:320
std::wstring ToString(jsval obj, bool pretty=false)
bool GetPropertyInt_(jsval obj, int name, jsval &value)
void Trace(JSTracer *trc)
Definition: AutoRooters.cpp:36
bool Eval_(const char *code, jsval &ret)
std::wstringstream stream
Abstraction around a SpiderMonkey JSRuntime.
jsval ReadStructuredClone(const shared_ptr< StructuredClone > &ptr)
PIVFS g_VFS
Definition: Filesystem.cpp:30
jsval Clone(jsval val)
JSBool ProfileStop(JSContext *cx, uintN argc, jsval *vp)
bool operator==(const ScriptLocation &b) const
bool EnumeratePropertyNamesWithPrefix(jsval obj, const char *prefix, std::vector< std::string > &out)
ScriptRuntime(int runtimeSize)
static shared_ptr< ScriptRuntime > CreateRuntime(int runtimeSize=DEFAULT_RUNTIME_SIZE)
Returns a runtime, which can used to initialise any number of ScriptInterfaces contexts.
Status GetPathnames(const PIVFS &fs, const VfsPath &path, const wchar_t *filter, VfsPaths &pathnames)
Definition: vfs_util.cpp:40
JSBool warn(JSContext *cx, uintN argc, jsval *vp)
JSBool deepcopy(JSContext *cx, uintN argc, jsval *vp)
JSContext * GetContext() const
void MaybeGC()
MaybeGC tries to determine whether garbage collection in cx&#39;s runtime would free up enough memory to ...
Helper for rooting large groups of script values.
Definition: AutoRooters.h:31
void UnRegisterScriptinterface(ScriptInterface *pScriptInterface)
Unregister a ScriptInerface that was previously registered using RegisterScriptinterface.
void ReportError(const char *msg)
Report the given error message through the JS error reporting mechanism, and throw a JS exception...
RAII wrapper for JSIdArray*.
Definition: ScriptVal.h:84
std::auto_ptr< ScriptInterface_impl > m
ScriptInterface & scriptInterfaceFrom
void debug_printf(const wchar_t *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:142
JSRuntime * m_rt
shared_ptr< StructuredClone > WriteStructuredClone(jsval v)