1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsXULAppAPI.h"
8 #include "jsapi.h"
9 #include "jsfriendapi.h"
10 #include "js/Array.h"  // JS::NewArrayObject
11 #include "js/CharacterEncoding.h"
12 #include "js/CompilationAndEvaluation.h"  // JS::Evaluate
13 #include "js/ContextOptions.h"
14 #include "js/Printf.h"
15 #include "js/PropertySpec.h"
16 #include "js/SourceText.h"  // JS::SourceText
17 #include "mozilla/ChaosMode.h"
18 #include "mozilla/dom/AutoEntryScript.h"
19 #include "mozilla/dom/ScriptSettings.h"
20 #include "mozilla/IOInterposer.h"
21 #include "mozilla/Preferences.h"
22 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
23 #include "nsServiceManagerUtils.h"
24 #include "nsComponentManagerUtils.h"
25 #include "nsExceptionHandler.h"
26 #include "nsIServiceManager.h"
27 #include "nsIFile.h"
28 #include "nsString.h"
29 #include "nsIDirectoryService.h"
30 #include "nsDirectoryServiceDefs.h"
31 #include "nsAppDirectoryServiceDefs.h"
32 #include "nscore.h"
33 #include "nsArrayEnumerator.h"
34 #include "nsCOMArray.h"
35 #include "nsDirectoryServiceUtils.h"
36 #include "nsCOMPtr.h"
37 #include "nsJSPrincipals.h"
38 #include "xpcpublic.h"
39 #include "xpcprivate.h"
40 #include "BackstagePass.h"
41 #include "nsIScriptSecurityManager.h"
42 #include "nsIPrincipal.h"
43 #include "nsJSUtils.h"
44 
45 #include "nsIXULRuntime.h"
46 #include "nsIAppStartup.h"
47 #include "GeckoProfiler.h"
48 #include "Components.h"
49 
50 #ifdef ANDROID
51 #  include <android/log.h>
52 #  include "XREShellData.h"
53 #endif
54 
55 #ifdef XP_WIN
56 #  include "mozilla/mscom/ProcessRuntime.h"
57 #  include "mozilla/ScopeExit.h"
58 #  include "mozilla/widget/AudioSession.h"
59 #  include "mozilla/WinDllServices.h"
60 #  include <windows.h>
61 #  if defined(MOZ_SANDBOX)
62 #    include "XREShellData.h"
63 #    include "sandboxBroker.h"
64 #  endif
65 #endif
66 
67 #ifdef MOZ_CODE_COVERAGE
68 #  include "mozilla/CodeCoverageHandler.h"
69 #endif
70 
71 // all this crap is needed to do the interactive shell stuff
72 #include <stdlib.h>
73 #include <errno.h>
74 #ifdef HAVE_IO_H
75 #  include <io.h> /* for isatty() */
76 #endif
77 #ifdef HAVE_UNISTD_H
78 #  include <unistd.h> /* for isatty() */
79 #endif
80 
81 #ifdef ENABLE_TESTS
82 #  include "xpctest_private.h"
83 #endif
84 
85 // Fuzzing support for XPC runtime fuzzing
86 #ifdef FUZZING_INTERFACES
87 #  include "xpcrtfuzzing/xpcrtfuzzing.h"
88 #  include "XREShellData.h"
89 static bool fuzzDoDebug = !!getenv("MOZ_FUZZ_DEBUG");
90 static bool fuzzHaveModule = !!getenv("FUZZER");
91 #endif  // FUZZING_INTERFACES
92 
93 using namespace mozilla;
94 using namespace JS;
95 using mozilla::dom::AutoEntryScript;
96 using mozilla::dom::AutoJSAPI;
97 
98 class XPCShellDirProvider : public nsIDirectoryServiceProvider2 {
99  public:
100   NS_DECL_ISUPPORTS_INHERITED
101   NS_DECL_NSIDIRECTORYSERVICEPROVIDER
102   NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
103 
104   XPCShellDirProvider() = default;
105   ~XPCShellDirProvider() = default;
106 
107   // The platform resource folder
108   void SetGREDirs(nsIFile* greDir);
ClearGREDirs()109   void ClearGREDirs() {
110     mGREDir = nullptr;
111     mGREBinDir = nullptr;
112   }
113   // The application resource folder
114   void SetAppDir(nsIFile* appFile);
ClearAppDir()115   void ClearAppDir() { mAppDir = nullptr; }
116   // The app executable
117   void SetAppFile(nsIFile* appFile);
ClearAppFile()118   void ClearAppFile() { mAppFile = nullptr; }
119   // An additional custom plugin dir if specified
120   void SetPluginDir(nsIFile* pluginDir);
ClearPluginDir()121   void ClearPluginDir() { mPluginDir = nullptr; }
122 
123  private:
124   nsCOMPtr<nsIFile> mGREDir;
125   nsCOMPtr<nsIFile> mGREBinDir;
126   nsCOMPtr<nsIFile> mAppDir;
127   nsCOMPtr<nsIFile> mPluginDir;
128   nsCOMPtr<nsIFile> mAppFile;
129 };
130 
131 #ifdef XP_WIN
132 class MOZ_STACK_CLASS AutoAudioSession {
133  public:
AutoAudioSession()134   AutoAudioSession() { widget::StartAudioSession(); }
135 
~AutoAudioSession()136   ~AutoAudioSession() { widget::StopAudioSession(); }
137 };
138 #endif
139 
140 #define EXITCODE_RUNTIME_ERROR 3
141 #define EXITCODE_FILE_NOT_FOUND 4
142 
143 static FILE* gOutFile = nullptr;
144 static FILE* gErrFile = nullptr;
145 static FILE* gInFile = nullptr;
146 
147 static int gExitCode = 0;
148 static bool gQuitting = false;
149 static bool reportWarnings = true;
150 static bool compileOnly = false;
151 
152 static JSPrincipals* gJSPrincipals = nullptr;
153 static nsAutoString* gWorkingDirectory = nullptr;
154 
GetLocationProperty(JSContext * cx,unsigned argc,Value * vp)155 static bool GetLocationProperty(JSContext* cx, unsigned argc, Value* vp) {
156   CallArgs args = CallArgsFromVp(argc, vp);
157   if (!args.thisv().isObject()) {
158     JS_ReportErrorASCII(cx, "Unexpected this value for GetLocationProperty");
159     return false;
160   }
161 #if !defined(XP_WIN) && !defined(XP_UNIX)
162   // XXX: your platform should really implement this
163   return false;
164 #else
165   JS::AutoFilename filename;
166   if (JS::DescribeScriptedCaller(cx, &filename) && filename.get()) {
167 #  if defined(XP_WIN)
168     // convert from the system codepage to UTF-16
169     int bufferSize =
170         MultiByteToWideChar(CP_ACP, 0, filename.get(), -1, nullptr, 0);
171     nsAutoString filenameString;
172     filenameString.SetLength(bufferSize);
173     MultiByteToWideChar(CP_ACP, 0, filename.get(), -1,
174                         (LPWSTR)filenameString.BeginWriting(),
175                         filenameString.Length());
176     // remove the null terminator
177     filenameString.SetLength(bufferSize - 1);
178 
179     // replace forward slashes with backslashes,
180     // since nsLocalFileWin chokes on them
181     char16_t* start = filenameString.BeginWriting();
182     char16_t* end = filenameString.EndWriting();
183 
184     while (start != end) {
185       if (*start == L'/') {
186         *start = L'\\';
187       }
188       start++;
189     }
190 #  elif defined(XP_UNIX)
191     NS_ConvertUTF8toUTF16 filenameString(filename.get());
192 #  endif
193 
194     nsCOMPtr<nsIFile> location;
195     nsresult rv =
196         NS_NewLocalFile(filenameString, false, getter_AddRefs(location));
197 
198     if (!location && gWorkingDirectory) {
199       // could be a relative path, try appending it to the cwd
200       // and then normalize
201       nsAutoString absolutePath(*gWorkingDirectory);
202       absolutePath.Append(filenameString);
203 
204       rv = NS_NewLocalFile(absolutePath, false, getter_AddRefs(location));
205     }
206 
207     if (location) {
208       bool symlink;
209       // don't normalize symlinks, because that's kind of confusing
210       if (NS_SUCCEEDED(location->IsSymlink(&symlink)) && !symlink)
211         location->Normalize();
212       RootedObject locationObj(cx);
213       RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
214       rv = nsXPConnect::XPConnect()->WrapNative(
215           cx, scope, location, NS_GET_IID(nsIFile), locationObj.address());
216       if (NS_SUCCEEDED(rv) && locationObj) {
217         args.rval().setObject(*locationObj);
218       }
219     }
220   }
221 
222   return true;
223 #endif
224 }
225 
GetLine(JSContext * cx,char * bufp,FILE * file,const char * prompt)226 static bool GetLine(JSContext* cx, char* bufp, FILE* file, const char* prompt) {
227   fputs(prompt, gOutFile);
228   fflush(gOutFile);
229 
230   char line[4096] = {'\0'};
231   while (true) {
232     if (fgets(line, sizeof line, file)) {
233       strcpy(bufp, line);
234       return true;
235     }
236     if (errno != EINTR) {
237       return false;
238     }
239   }
240 }
241 
ReadLine(JSContext * cx,unsigned argc,Value * vp)242 static bool ReadLine(JSContext* cx, unsigned argc, Value* vp) {
243   CallArgs args = CallArgsFromVp(argc, vp);
244 
245   // While 4096 might be quite arbitrary, this is something to be fixed in
246   // bug 105707. It is also the same limit as in ProcessFile.
247   char buf[4096];
248   RootedString str(cx);
249 
250   /* If a prompt was specified, construct the string */
251   if (args.length() > 0) {
252     str = JS::ToString(cx, args[0]);
253     if (!str) {
254       return false;
255     }
256   } else {
257     str = JS_GetEmptyString(cx);
258   }
259 
260   /* Get a line from the infile */
261   JS::UniqueChars strBytes = JS_EncodeStringToLatin1(cx, str);
262   if (!strBytes || !GetLine(cx, buf, gInFile, strBytes.get())) {
263     return false;
264   }
265 
266   /* Strip newline character added by GetLine() */
267   unsigned int buflen = strlen(buf);
268   if (buflen == 0) {
269     if (feof(gInFile)) {
270       args.rval().setNull();
271       return true;
272     }
273   } else if (buf[buflen - 1] == '\n') {
274     --buflen;
275   }
276 
277   /* Turn buf into a JSString */
278   str = JS_NewStringCopyN(cx, buf, buflen);
279   if (!str) {
280     return false;
281   }
282 
283   args.rval().setString(str);
284   return true;
285 }
286 
Print(JSContext * cx,unsigned argc,Value * vp)287 static bool Print(JSContext* cx, unsigned argc, Value* vp) {
288   CallArgs args = CallArgsFromVp(argc, vp);
289   args.rval().setUndefined();
290 
291 #ifdef FUZZING_INTERFACES
292   if (fuzzHaveModule && !fuzzDoDebug) {
293     // When fuzzing and not debugging, suppress any print() output,
294     // as it slows down fuzzing and makes libFuzzer's output hard
295     // to read.
296     return true;
297   }
298 #endif  // FUZZING_INTERFACES
299 
300   RootedString str(cx);
301   nsAutoCString utf8output;
302 
303   for (unsigned i = 0; i < args.length(); i++) {
304     str = ToString(cx, args[i]);
305     if (!str) {
306       return false;
307     }
308 
309     JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
310     if (!utf8str) {
311       return false;
312     }
313 
314     if (i) {
315       utf8output.Append(' ');
316     }
317     utf8output.Append(utf8str.get(), strlen(utf8str.get()));
318   }
319   utf8output.Append('\n');
320   fputs(utf8output.get(), gOutFile);
321   fflush(gOutFile);
322   return true;
323 }
324 
Dump(JSContext * cx,unsigned argc,Value * vp)325 static bool Dump(JSContext* cx, unsigned argc, Value* vp) {
326   CallArgs args = CallArgsFromVp(argc, vp);
327   args.rval().setUndefined();
328 
329   if (!args.length()) {
330     return true;
331   }
332 
333   RootedString str(cx, ToString(cx, args[0]));
334   if (!str) {
335     return false;
336   }
337 
338   JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
339   if (!utf8str) {
340     return false;
341   }
342 
343 #ifdef ANDROID
344   __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get());
345 #endif
346 #ifdef XP_WIN
347   if (IsDebuggerPresent()) {
348     nsAutoJSString wstr;
349     if (!wstr.init(cx, str)) {
350       return false;
351     }
352     OutputDebugStringW(wstr.get());
353   }
354 #endif
355   fputs(utf8str.get(), gOutFile);
356   fflush(gOutFile);
357   return true;
358 }
359 
Load(JSContext * cx,unsigned argc,Value * vp)360 static bool Load(JSContext* cx, unsigned argc, Value* vp) {
361   CallArgs args = CallArgsFromVp(argc, vp);
362 
363   JS::RootedObject thisObject(cx);
364   if (!args.computeThis(cx, &thisObject)) {
365     return false;
366   }
367   if (!JS_IsGlobalObject(thisObject)) {
368     JS_ReportErrorASCII(cx, "Trying to load() into a non-global object");
369     return false;
370   }
371 
372   RootedString str(cx);
373   for (unsigned i = 0; i < args.length(); i++) {
374     str = ToString(cx, args[i]);
375     if (!str) {
376       return false;
377     }
378     JS::UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
379     if (!filename) {
380       return false;
381     }
382     FILE* file = fopen(filename.get(), "r");
383     if (!file) {
384       filename = JS_EncodeStringToUTF8(cx, str);
385       if (!filename) {
386         return false;
387       }
388       JS_ReportErrorUTF8(cx, "cannot open file '%s' for reading",
389                          filename.get());
390       return false;
391     }
392     JS::CompileOptions options(cx);
393     options.setFileAndLine(filename.get(), 1)
394         .setIsRunOnce(true)
395         .setSkipFilenameValidation(true);
396     JS::Rooted<JSScript*> script(cx);
397     JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
398     script = JS::CompileUtf8File(cx, options, file);
399     fclose(file);
400     if (!script) {
401       return false;
402     }
403 
404     if (!compileOnly) {
405       if (!JS_ExecuteScript(cx, script)) {
406         return false;
407       }
408     }
409   }
410   args.rval().setUndefined();
411   return true;
412 }
413 
Quit(JSContext * cx,unsigned argc,Value * vp)414 static bool Quit(JSContext* cx, unsigned argc, Value* vp) {
415   CallArgs args = CallArgsFromVp(argc, vp);
416 
417   gExitCode = 0;
418   if (!ToInt32(cx, args.get(0), &gExitCode)) {
419     return false;
420   }
421 
422   gQuitting = true;
423   //    exit(0);
424   return false;
425 }
426 
DumpXPC(JSContext * cx,unsigned argc,Value * vp)427 static bool DumpXPC(JSContext* cx, unsigned argc, Value* vp) {
428   JS::CallArgs args = CallArgsFromVp(argc, vp);
429 
430   uint16_t depth = 2;
431   if (args.length() > 0) {
432     if (!JS::ToUint16(cx, args[0], &depth)) {
433       return false;
434     }
435   }
436 
437   nsXPConnect::XPConnect()->DebugDump(int16_t(depth));
438   args.rval().setUndefined();
439   return true;
440 }
441 
GC(JSContext * cx,unsigned argc,Value * vp)442 static bool GC(JSContext* cx, unsigned argc, Value* vp) {
443   CallArgs args = CallArgsFromVp(argc, vp);
444 
445   JS_GC(cx);
446 
447   args.rval().setUndefined();
448   return true;
449 }
450 
451 #ifdef JS_GC_ZEAL
GCZeal(JSContext * cx,unsigned argc,Value * vp)452 static bool GCZeal(JSContext* cx, unsigned argc, Value* vp) {
453   CallArgs args = CallArgsFromVp(argc, vp);
454   uint32_t zeal;
455   if (!ToUint32(cx, args.get(0), &zeal)) {
456     return false;
457   }
458 
459   JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
460   args.rval().setUndefined();
461   return true;
462 }
463 #endif
464 
SendCommand(JSContext * cx,unsigned argc,Value * vp)465 static bool SendCommand(JSContext* cx, unsigned argc, Value* vp) {
466   CallArgs args = CallArgsFromVp(argc, vp);
467 
468   if (args.length() == 0) {
469     JS_ReportErrorASCII(cx, "Function takes at least one argument!");
470     return false;
471   }
472 
473   RootedString str(cx, ToString(cx, args[0]));
474   if (!str) {
475     JS_ReportErrorASCII(cx, "Could not convert argument 1 to string!");
476     return false;
477   }
478 
479   if (args.get(1).isObject() && !JS_ObjectIsFunction(&args[1].toObject())) {
480     JS_ReportErrorASCII(cx, "Could not convert argument 2 to function!");
481     return false;
482   }
483 
484   if (!XRE_SendTestShellCommand(
485           cx, str, args.length() > 1 ? args[1].address() : nullptr)) {
486     JS_ReportErrorASCII(cx, "Couldn't send command!");
487     return false;
488   }
489 
490   args.rval().setUndefined();
491   return true;
492 }
493 
Options(JSContext * cx,unsigned argc,Value * vp)494 static bool Options(JSContext* cx, unsigned argc, Value* vp) {
495   JS::CallArgs args = CallArgsFromVp(argc, vp);
496   ContextOptions oldContextOptions = ContextOptionsRef(cx);
497 
498   RootedString str(cx);
499   JS::UniqueChars opt;
500   for (unsigned i = 0; i < args.length(); ++i) {
501     str = ToString(cx, args[i]);
502     if (!str) {
503       return false;
504     }
505 
506     opt = JS_EncodeStringToUTF8(cx, str);
507     if (!opt) {
508       return false;
509     }
510 
511     if (strcmp(opt.get(), "strict_mode") == 0) {
512       ContextOptionsRef(cx).toggleStrictMode();
513     } else {
514       JS_ReportErrorUTF8(cx,
515                          "unknown option name '%s'. The valid name is "
516                          "strict_mode.",
517                          opt.get());
518       return false;
519     }
520   }
521 
522   UniqueChars names;
523   if (names && oldContextOptions.strictMode()) {
524     names = JS_sprintf_append(std::move(names), "%s%s", names ? "," : "",
525                               "strict_mode");
526     if (!names) {
527       JS_ReportOutOfMemory(cx);
528       return false;
529     }
530   }
531 
532   str = JS_NewStringCopyZ(cx, names.get());
533   if (!str) {
534     return false;
535   }
536 
537   args.rval().setString(str);
538   return true;
539 }
540 
541 static PersistentRootedValue* sScriptedInterruptCallback = nullptr;
542 
XPCShellInterruptCallback(JSContext * cx)543 static bool XPCShellInterruptCallback(JSContext* cx) {
544   MOZ_ASSERT(sScriptedInterruptCallback->initialized());
545   RootedValue callback(cx, *sScriptedInterruptCallback);
546 
547   // If no interrupt callback was set by script, no-op.
548   if (callback.isUndefined()) {
549     return true;
550   }
551 
552   MOZ_ASSERT(js::IsFunctionObject(&callback.toObject()));
553 
554   JSAutoRealm ar(cx, &callback.toObject());
555   RootedValue rv(cx);
556   if (!JS_CallFunctionValue(cx, nullptr, callback,
557                             JS::HandleValueArray::empty(), &rv) ||
558       !rv.isBoolean()) {
559     NS_WARNING("Scripted interrupt callback failed! Terminating script.");
560     JS_ClearPendingException(cx);
561     return false;
562   }
563 
564   return rv.toBoolean();
565 }
566 
SetInterruptCallback(JSContext * cx,unsigned argc,Value * vp)567 static bool SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) {
568   MOZ_ASSERT(sScriptedInterruptCallback->initialized());
569 
570   // Sanity-check args.
571   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
572   if (args.length() != 1) {
573     JS_ReportErrorASCII(cx, "Wrong number of arguments");
574     return false;
575   }
576 
577   // Allow callers to remove the interrupt callback by passing undefined.
578   if (args[0].isUndefined()) {
579     *sScriptedInterruptCallback = UndefinedValue();
580     return true;
581   }
582 
583   // Otherwise, we should have a function object.
584   if (!args[0].isObject() || !js::IsFunctionObject(&args[0].toObject())) {
585     JS_ReportErrorASCII(cx, "Argument must be a function");
586     return false;
587   }
588 
589   *sScriptedInterruptCallback = args[0];
590 
591   return true;
592 }
593 
SimulateNoScriptActivity(JSContext * cx,unsigned argc,Value * vp)594 static bool SimulateNoScriptActivity(JSContext* cx, unsigned argc, Value* vp) {
595   // Sanity-check args.
596   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
597   if (args.length() != 1 || !args[0].isInt32() || args[0].toInt32() < 0) {
598     JS_ReportErrorASCII(cx, "Expected a positive integer argument");
599     return false;
600   }
601 
602   // This mimics mozilla::SpinEventLoopUntil but instead of spinning the
603   // event loop we sleep, to make sure we don't run script.
604   xpc::AutoScriptActivity asa(false);
605   PR_Sleep(PR_SecondsToInterval(args[0].toInt32()));
606 
607   args.rval().setUndefined();
608   return true;
609 }
610 
RegisterAppManifest(JSContext * cx,unsigned argc,Value * vp)611 static bool RegisterAppManifest(JSContext* cx, unsigned argc, Value* vp) {
612   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
613   if (args.length() != 1) {
614     JS_ReportErrorASCII(cx, "Wrong number of arguments");
615     return false;
616   }
617   if (!args[0].isObject()) {
618     JS_ReportErrorASCII(cx,
619                         "Expected object as argument 1 to registerAppManifest");
620     return false;
621   }
622 
623   Rooted<JSObject*> arg1(cx, &args[0].toObject());
624   nsCOMPtr<nsIFile> file;
625   nsresult rv = nsXPConnect::XPConnect()->WrapJS(cx, arg1, NS_GET_IID(nsIFile),
626                                                  getter_AddRefs(file));
627   if (NS_FAILED(rv)) {
628     XPCThrower::Throw(rv, cx);
629     return false;
630   }
631   rv = XRE_AddManifestLocation(NS_APP_LOCATION, file);
632   if (NS_FAILED(rv)) {
633     XPCThrower::Throw(rv, cx);
634     return false;
635   }
636   return true;
637 }
638 
639 #ifdef ENABLE_TESTS
RegisterXPCTestComponents(JSContext * cx,unsigned argc,Value * vp)640 static bool RegisterXPCTestComponents(JSContext* cx, unsigned argc, Value* vp) {
641   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
642   if (args.length() != 0) {
643     JS_ReportErrorASCII(cx, "Wrong number of arguments");
644     return false;
645   }
646   nsresult rv = XRE_AddStaticComponent(&kXPCTestModule);
647   if (NS_FAILED(rv)) {
648     XPCThrower::Throw(rv, cx);
649     return false;
650   }
651   return true;
652 }
653 #endif
654 
655 static const JSFunctionSpec glob_functions[] = {
656     // clang-format off
657     JS_FN("print",           Print,          0,0),
658     JS_FN("readline",        ReadLine,       1,0),
659     JS_FN("load",            Load,           1,0),
660     JS_FN("quit",            Quit,           0,0),
661     JS_FN("dumpXPC",         DumpXPC,        1,0),
662     JS_FN("dump",            Dump,           1,0),
663     JS_FN("gc",              GC,             0,0),
664 #ifdef JS_GC_ZEAL
665     JS_FN("gczeal",          GCZeal,         1,0),
666 #endif
667     JS_FN("options",         Options,        0,0),
668     JS_FN("sendCommand",     SendCommand,    1,0),
669     JS_FN("atob",            xpc::Atob,      1,0),
670     JS_FN("btoa",            xpc::Btoa,      1,0),
671     JS_FN("setInterruptCallback", SetInterruptCallback, 1,0),
672     JS_FN("simulateNoScriptActivity", SimulateNoScriptActivity, 1,0),
673     JS_FN("registerAppManifest", RegisterAppManifest, 1, 0),
674 #ifdef ENABLE_TESTS
675     JS_FN("registerXPCTestComponents", RegisterXPCTestComponents, 0, 0),
676 #endif
677     JS_FS_END
678     // clang-format on
679 };
680 
681 /***************************************************************************/
682 
683 typedef enum JSShellErrNum {
684 #define MSG_DEF(name, number, count, exception, format) name = number,
685 #include "jsshell.msg"
686 #undef MSG_DEF
687   JSShellErr_Limit
688 } JSShellErrNum;
689 
690 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
691 #define MSG_DEF(name, number, count, exception, format) {#name, format, count},
692 #include "jsshell.msg"
693 #undef MSG_DEF
694 };
695 
my_GetErrorMessage(void * userRef,const unsigned errorNumber)696 static const JSErrorFormatString* my_GetErrorMessage(
697     void* userRef, const unsigned errorNumber) {
698   if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) {
699     return nullptr;
700   }
701 
702   return &jsShell_ErrorFormatString[errorNumber];
703 }
704 
ProcessUtf8Line(AutoJSAPI & jsapi,const char * buffer,int startline)705 static bool ProcessUtf8Line(AutoJSAPI& jsapi, const char* buffer,
706                             int startline) {
707   JSContext* cx = jsapi.cx();
708   JS::CompileOptions options(cx);
709   options.setFileAndLine("typein", startline)
710       .setIsRunOnce(true)
711       .setSkipFilenameValidation(true);
712 
713   JS::SourceText<mozilla::Utf8Unit> srcBuf;
714   if (!srcBuf.init(cx, buffer, strlen(buffer), JS::SourceOwnership::Borrowed)) {
715     return false;
716   }
717 
718   JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
719   if (!script) {
720     return false;
721   }
722   if (compileOnly) {
723     return true;
724   }
725 
726   JS::RootedValue result(cx);
727   if (!JS_ExecuteScript(cx, script, &result)) {
728     return false;
729   }
730 
731   if (result.isUndefined()) {
732     return true;
733   }
734 
735   RootedString str(cx, JS::ToString(cx, result));
736   if (!str) {
737     return false;
738   }
739 
740   JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str);
741   if (!bytes) {
742     return false;
743   }
744 
745   fprintf(gOutFile, "%s\n", bytes.get());
746   return true;
747 }
748 
ProcessFile(AutoJSAPI & jsapi,const char * filename,FILE * file,bool forceTTY)749 static bool ProcessFile(AutoJSAPI& jsapi, const char* filename, FILE* file,
750                         bool forceTTY) {
751   JSContext* cx = jsapi.cx();
752   JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
753   MOZ_ASSERT(global);
754 
755   if (forceTTY) {
756     file = stdin;
757   } else if (!isatty(fileno(file))) {
758     /*
759      * It's not interactive - just execute it.
760      *
761      * Support the UNIX #! shell hack; gobble the first line if it starts
762      * with '#'.  TODO - this isn't quite compatible with sharp variables,
763      * as a legal js program (using sharp variables) might start with '#'.
764      * But that would require multi-character lookahead.
765      */
766     int ch = fgetc(file);
767     if (ch == '#') {
768       while ((ch = fgetc(file)) != EOF) {
769         if (ch == '\n' || ch == '\r') {
770           break;
771         }
772       }
773     }
774     ungetc(ch, file);
775 
776     JS::RootedScript script(cx);
777     JS::RootedValue unused(cx);
778     JS::CompileOptions options(cx);
779     options.setFileAndLine(filename, 1)
780         .setIsRunOnce(true)
781         .setNoScriptRval(true)
782         .setSkipFilenameValidation(true);
783     script = JS::CompileUtf8File(cx, options, file);
784     if (!script) {
785       return false;
786     }
787     return compileOnly || JS_ExecuteScript(cx, script, &unused);
788   }
789 
790   /* It's an interactive filehandle; drop into read-eval-print loop. */
791   int lineno = 1;
792   bool hitEOF = false;
793   do {
794     char buffer[4096];
795     char* bufp = buffer;
796     *bufp = '\0';
797 
798     /*
799      * Accumulate lines until we get a 'compilable unit' - one that either
800      * generates an error (before running out of source) or that compiles
801      * cleanly.  This should be whenever we get a complete statement that
802      * coincides with the end of a line.
803      */
804     int startline = lineno;
805     do {
806       if (!GetLine(cx, bufp, file, startline == lineno ? "js> " : "")) {
807         hitEOF = true;
808         break;
809       }
810       bufp += strlen(bufp);
811       lineno++;
812     } while (
813         !JS_Utf8BufferIsCompilableUnit(cx, global, buffer, strlen(buffer)));
814 
815     if (!ProcessUtf8Line(jsapi, buffer, startline)) {
816       jsapi.ReportException();
817     }
818   } while (!hitEOF && !gQuitting);
819 
820   fprintf(gOutFile, "\n");
821   return true;
822 }
823 
Process(AutoJSAPI & jsapi,const char * filename,bool forceTTY)824 static bool Process(AutoJSAPI& jsapi, const char* filename, bool forceTTY) {
825   FILE* file;
826 
827   if (forceTTY || !filename || strcmp(filename, "-") == 0) {
828     file = stdin;
829   } else {
830     file = fopen(filename, "r");
831     if (!file) {
832       /*
833        * Use Latin1 variant here because the encoding of the return value
834        * of strerror function can be non-UTF-8.
835        */
836       JS_ReportErrorNumberLatin1(jsapi.cx(), my_GetErrorMessage, nullptr,
837                                  JSSMSG_CANT_OPEN, filename, strerror(errno));
838       gExitCode = EXITCODE_FILE_NOT_FOUND;
839       return false;
840     }
841   }
842 
843   bool ok = ProcessFile(jsapi, filename, file, forceTTY);
844   if (file != stdin) {
845     fclose(file);
846   }
847   return ok;
848 }
849 
usage()850 static int usage() {
851   fprintf(gErrFile, "%s\n", JS_GetImplementationVersion());
852   fprintf(
853       gErrFile,
854       "usage: xpcshell [-g gredir] [-a appdir] [-r manifest]... [-WwxiCmIp] "
855       "[-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n");
856   return 2;
857 }
858 
printUsageAndSetExitCode()859 static bool printUsageAndSetExitCode() {
860   gExitCode = usage();
861   return false;
862 }
863 
ProcessArgs(AutoJSAPI & jsapi,char ** argv,int argc,XPCShellDirProvider * aDirProvider)864 static bool ProcessArgs(AutoJSAPI& jsapi, char** argv, int argc,
865                         XPCShellDirProvider* aDirProvider) {
866   JSContext* cx = jsapi.cx();
867   const char rcfilename[] = "xpcshell.js";
868   FILE* rcfile;
869   int rootPosition;
870   JS::Rooted<JSObject*> argsObj(cx);
871   char* filename = nullptr;
872   bool isInteractive = true;
873   bool forceTTY = false;
874 
875   rcfile = fopen(rcfilename, "r");
876   if (rcfile) {
877     printf("[loading '%s'...]\n", rcfilename);
878     bool ok = ProcessFile(jsapi, rcfilename, rcfile, false);
879     fclose(rcfile);
880     if (!ok) {
881       return false;
882     }
883   }
884 
885   JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
886 
887   /*
888    * Scan past all optional arguments so we can create the arguments object
889    * before processing any -f options, which must interleave properly with
890    * -v and -w options.  This requires two passes, and without getopt, we'll
891    * have to keep the option logic here and in the second for loop in sync.
892    * First of all, find out the first argument position which will be passed
893    * as a script file to be executed.
894    */
895   for (rootPosition = 0; rootPosition < argc; rootPosition++) {
896     if (argv[rootPosition][0] != '-' || argv[rootPosition][1] == '\0') {
897       ++rootPosition;
898       break;
899     }
900 
901     bool isPairedFlag =
902         argv[rootPosition][0] != '\0' &&
903         (argv[rootPosition][1] == 'v' || argv[rootPosition][1] == 'f' ||
904          argv[rootPosition][1] == 'e');
905     if (isPairedFlag && rootPosition < argc - 1) {
906       ++rootPosition;  // Skip over the 'foo' portion of |-v foo|, |-f foo|, or
907                        // |-e foo|.
908     }
909   }
910 
911   /*
912    * Create arguments early and define it to root it, so it's safe from any
913    * GC calls nested below, and so it is available to -f <file> arguments.
914    */
915   argsObj = JS::NewArrayObject(cx, 0);
916   if (!argsObj) {
917     return 1;
918   }
919   if (!JS_DefineProperty(cx, global, "arguments", argsObj, 0)) {
920     return 1;
921   }
922 
923   for (int j = 0, length = argc - rootPosition; j < length; j++) {
924     RootedString str(cx, JS_NewStringCopyZ(cx, argv[rootPosition++]));
925     if (!str || !JS_DefineElement(cx, argsObj, j, str, JSPROP_ENUMERATE)) {
926       return 1;
927     }
928   }
929 
930   for (int i = 0; i < argc; i++) {
931     if (argv[i][0] != '-' || argv[i][1] == '\0') {
932       filename = argv[i++];
933       isInteractive = false;
934       break;
935     }
936     switch (argv[i][1]) {
937       case 'W':
938         reportWarnings = false;
939         break;
940       case 'w':
941         reportWarnings = true;
942         break;
943       case 'x':
944         break;
945       case 'd':
946         /* This used to try to turn on the debugger. */
947         break;
948       case 'm':
949         break;
950       case 'f':
951         if (++i == argc) {
952           return printUsageAndSetExitCode();
953         }
954         if (!Process(jsapi, argv[i], false)) {
955           return false;
956         }
957         /*
958          * XXX: js -f foo.js should interpret foo.js and then
959          * drop into interactive mode, but that breaks test
960          * harness. Just execute foo.js for now.
961          */
962         isInteractive = false;
963         break;
964       case 'i':
965         isInteractive = forceTTY = true;
966         break;
967       case 'e': {
968         RootedValue rval(cx);
969 
970         if (++i == argc) {
971           return printUsageAndSetExitCode();
972         }
973 
974         JS::CompileOptions opts(cx);
975         opts.setSkipFilenameValidation(true);
976         opts.setFileAndLine("-e", 1);
977 
978         JS::SourceText<mozilla::Utf8Unit> srcBuf;
979         if (srcBuf.init(cx, argv[i], strlen(argv[i]),
980                         JS::SourceOwnership::Borrowed)) {
981           JS::Evaluate(cx, opts, srcBuf, &rval);
982         }
983 
984         isInteractive = false;
985         break;
986       }
987       case 'C':
988         compileOnly = true;
989         isInteractive = false;
990         break;
991       case 'p': {
992         // plugins path
993         char* pluginPath = argv[++i];
994         nsCOMPtr<nsIFile> pluginsDir;
995         if (NS_FAILED(
996                 XRE_GetFileFromPath(pluginPath, getter_AddRefs(pluginsDir)))) {
997           fprintf(gErrFile, "Couldn't use given plugins dir.\n");
998           return printUsageAndSetExitCode();
999         }
1000         aDirProvider->SetPluginDir(pluginsDir);
1001         break;
1002       }
1003       default:
1004         return printUsageAndSetExitCode();
1005     }
1006   }
1007 
1008   if (filename || isInteractive) {
1009     return Process(jsapi, filename, forceTTY);
1010   }
1011   return true;
1012 }
1013 
1014 /***************************************************************************/
1015 
GetCurrentWorkingDirectory(nsAString & workingDirectory)1016 static bool GetCurrentWorkingDirectory(nsAString& workingDirectory) {
1017 #if !defined(XP_WIN) && !defined(XP_UNIX)
1018   // XXX: your platform should really implement this
1019   return false;
1020 #elif XP_WIN
1021   DWORD requiredLength = GetCurrentDirectoryW(0, nullptr);
1022   workingDirectory.SetLength(requiredLength);
1023   GetCurrentDirectoryW(workingDirectory.Length(),
1024                        (LPWSTR)workingDirectory.BeginWriting());
1025   // we got a trailing null there
1026   workingDirectory.SetLength(requiredLength);
1027   workingDirectory.Replace(workingDirectory.Length() - 1, 1, L'\\');
1028 #elif defined(XP_UNIX)
1029   nsAutoCString cwd;
1030   // 1024 is just a guess at a sane starting value
1031   size_t bufsize = 1024;
1032   char* result = nullptr;
1033   while (result == nullptr) {
1034     cwd.SetLength(bufsize);
1035     result = getcwd(cwd.BeginWriting(), cwd.Length());
1036     if (!result) {
1037       if (errno != ERANGE) {
1038         return false;
1039       }
1040       // need to make the buffer bigger
1041       bufsize *= 2;
1042     }
1043   }
1044   // size back down to the actual string length
1045   cwd.SetLength(strlen(result) + 1);
1046   cwd.Replace(cwd.Length() - 1, 1, '/');
1047   CopyUTF8toUTF16(cwd, workingDirectory);
1048 #endif
1049   return true;
1050 }
1051 
1052 static JSSecurityCallbacks shellSecurityCallbacks;
1053 
XRE_XPCShellMain(int argc,char ** argv,char ** envp,const XREShellData * aShellData)1054 int XRE_XPCShellMain(int argc, char** argv, char** envp,
1055                      const XREShellData* aShellData) {
1056   MOZ_ASSERT(aShellData);
1057 
1058   JSContext* cx;
1059   int result = 0;
1060   nsresult rv;
1061 
1062 #ifdef ANDROID
1063   gOutFile = aShellData->outFile;
1064   gErrFile = aShellData->errFile;
1065 #else
1066   gOutFile = stdout;
1067   gErrFile = stderr;
1068 #endif
1069   gInFile = stdin;
1070 
1071   NS_LogInit();
1072 
1073   mozilla::LogModule::Init(argc, argv);
1074 
1075   // This guard ensures that all threads that attempt to register themselves
1076   // with the IOInterposer will be properly tracked.
1077   mozilla::IOInterposerInit ioInterposerGuard;
1078 
1079 #ifdef MOZ_GECKO_PROFILER
1080   char aLocal;
1081   profiler_init(&aLocal);
1082 #endif
1083 
1084 #ifdef MOZ_ASAN_REPORTER
1085   PR_SetEnv("MOZ_DISABLE_ASAN_REPORTER=1");
1086 #endif
1087 
1088   if (PR_GetEnv("MOZ_CHAOSMODE")) {
1089     ChaosFeature feature = ChaosFeature::Any;
1090     long featureInt = strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16);
1091     if (featureInt) {
1092       // NOTE: MOZ_CHAOSMODE=0 or a non-hex value maps to Any feature.
1093       feature = static_cast<ChaosFeature>(featureInt);
1094     }
1095     ChaosMode::SetChaosFeature(feature);
1096   }
1097 
1098   if (ChaosMode::isActive(ChaosFeature::Any)) {
1099     printf_stderr(
1100         "*** You are running in chaos test mode. See ChaosMode.h. ***\n");
1101   }
1102 
1103 #ifdef XP_WIN
1104   // Some COM settings are global to the process and must be set before any non-
1105   // trivial COM is run in the application. Since these settings may affect
1106   // stability, we should instantiate COM ASAP so that we can ensure that these
1107   // global settings are configured before anything can interfere.
1108   mscom::ProcessRuntime mscom;
1109 #endif
1110 
1111   // The provider needs to outlive the call to shutting down XPCOM.
1112   XPCShellDirProvider dirprovider;
1113 
1114   {  // Start scoping nsCOMPtrs
1115     nsCOMPtr<nsIFile> appFile;
1116     rv = XRE_GetBinaryPath(getter_AddRefs(appFile));
1117     if (NS_FAILED(rv)) {
1118       printf("Couldn't find application file.\n");
1119       return 1;
1120     }
1121     nsCOMPtr<nsIFile> appDir;
1122     rv = appFile->GetParent(getter_AddRefs(appDir));
1123     if (NS_FAILED(rv)) {
1124       printf("Couldn't get application directory.\n");
1125       return 1;
1126     }
1127 
1128     dirprovider.SetAppFile(appFile);
1129 
1130     nsCOMPtr<nsIFile> greDir;
1131     if (argc > 1 && !strcmp(argv[1], "-g")) {
1132       if (argc < 3) {
1133         return usage();
1134       }
1135 
1136       rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(greDir));
1137       if (NS_FAILED(rv)) {
1138         printf("Couldn't use given GRE dir.\n");
1139         return 1;
1140       }
1141 
1142       dirprovider.SetGREDirs(greDir);
1143 
1144       argc -= 2;
1145       argv += 2;
1146     } else {
1147 #ifdef XP_MACOSX
1148       // On OSX, the GreD needs to point to Contents/Resources in the .app
1149       // bundle. Libraries will be loaded at a relative path to GreD, i.e.
1150       // ../MacOS.
1151       nsCOMPtr<nsIFile> tmpDir;
1152       XRE_GetFileFromPath(argv[0], getter_AddRefs(greDir));
1153       greDir->GetParent(getter_AddRefs(tmpDir));
1154       tmpDir->Clone(getter_AddRefs(greDir));
1155       tmpDir->SetNativeLeafName("Resources"_ns);
1156       bool dirExists = false;
1157       tmpDir->Exists(&dirExists);
1158       if (dirExists) {
1159         greDir = tmpDir.forget();
1160       }
1161       dirprovider.SetGREDirs(greDir);
1162 #else
1163       nsAutoString workingDir;
1164       if (!GetCurrentWorkingDirectory(workingDir)) {
1165         printf("GetCurrentWorkingDirectory failed.\n");
1166         return 1;
1167       }
1168       rv = NS_NewLocalFile(workingDir, true, getter_AddRefs(greDir));
1169       if (NS_FAILED(rv)) {
1170         printf("NS_NewLocalFile failed.\n");
1171         return 1;
1172       }
1173 #endif
1174     }
1175 
1176     if (argc > 1 && !strcmp(argv[1], "-a")) {
1177       if (argc < 3) {
1178         return usage();
1179       }
1180 
1181       nsCOMPtr<nsIFile> dir;
1182       rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(dir));
1183       if (NS_SUCCEEDED(rv)) {
1184         appDir = dir;
1185         dirprovider.SetAppDir(appDir);
1186       }
1187       if (NS_FAILED(rv)) {
1188         printf("Couldn't use given appdir.\n");
1189         return 1;
1190       }
1191       argc -= 2;
1192       argv += 2;
1193     }
1194 
1195     while (argc > 1 && !strcmp(argv[1], "-r")) {
1196       if (argc < 3) {
1197         return usage();
1198       }
1199 
1200       nsCOMPtr<nsIFile> lf;
1201       rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(lf));
1202       if (NS_FAILED(rv)) {
1203         printf("Couldn't get manifest file.\n");
1204         return 1;
1205       }
1206       XRE_AddManifestLocation(NS_APP_LOCATION, lf);
1207 
1208       argc -= 2;
1209       argv += 2;
1210     }
1211 
1212     const char* val = getenv("MOZ_CRASHREPORTER");
1213     if (val && *val && !CrashReporter::IsDummy()) {
1214       rv = CrashReporter::SetExceptionHandler(greDir, true);
1215       if (NS_FAILED(rv)) {
1216         printf("CrashReporter::SetExceptionHandler failed!\n");
1217         return 1;
1218       }
1219       MOZ_ASSERT(CrashReporter::GetEnabled());
1220     }
1221 
1222     if (argc > 1 && !strcmp(argv[1], "--greomni")) {
1223       nsCOMPtr<nsIFile> greOmni;
1224       XRE_GetFileFromPath(argv[2], getter_AddRefs(greOmni));
1225       XRE_InitOmnijar(greOmni, greOmni);
1226       argc -= 2;
1227       argv += 2;
1228     }
1229 
1230     rv = NS_InitXPCOM(nullptr, appDir, &dirprovider);
1231     if (NS_FAILED(rv)) {
1232       printf("NS_InitXPCOM failed!\n");
1233       return 1;
1234     }
1235 
1236     // xpc::ErrorReport::LogToConsoleWithStack needs this to print errors
1237     // to stderr.
1238     Preferences::SetBool("browser.dom.window.dump.enabled", true);
1239     Preferences::SetBool("devtools.console.stdout.chrome", true);
1240 
1241     AutoJSAPI jsapi;
1242     jsapi.Init();
1243     cx = jsapi.cx();
1244 
1245     // Override the default XPConnect interrupt callback. We could store the
1246     // old one and restore it before shutting down, but there's not really a
1247     // reason to bother.
1248     sScriptedInterruptCallback = new PersistentRootedValue;
1249     sScriptedInterruptCallback->init(cx, UndefinedValue());
1250 
1251     JS_AddInterruptCallback(cx, XPCShellInterruptCallback);
1252 
1253     argc--;
1254     argv++;
1255 
1256     nsCOMPtr<nsIPrincipal> systemprincipal;
1257     // Fetch the system principal and store it away in a global, to use for
1258     // script compilation in Load() and ProcessFile() (including interactive
1259     // eval loop)
1260     {
1261       nsCOMPtr<nsIScriptSecurityManager> securityManager =
1262           do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
1263       if (NS_SUCCEEDED(rv) && securityManager) {
1264         rv = securityManager->GetSystemPrincipal(
1265             getter_AddRefs(systemprincipal));
1266         if (NS_FAILED(rv)) {
1267           fprintf(gErrFile,
1268                   "+++ Failed to obtain SystemPrincipal from "
1269                   "ScriptSecurityManager service.\n");
1270         } else {
1271           // fetch the JS principals and stick in a global
1272           gJSPrincipals = nsJSPrincipals::get(systemprincipal);
1273           JS_HoldPrincipals(gJSPrincipals);
1274         }
1275       } else {
1276         fprintf(gErrFile,
1277                 "+++ Failed to get ScriptSecurityManager service, running "
1278                 "without principals");
1279       }
1280     }
1281 
1282     const JSSecurityCallbacks* scb = JS_GetSecurityCallbacks(cx);
1283     MOZ_ASSERT(
1284         scb,
1285         "We are assuming that nsScriptSecurityManager::Init() has been run");
1286     shellSecurityCallbacks = *scb;
1287     JS_SetSecurityCallbacks(cx, &shellSecurityCallbacks);
1288 
1289     auto backstagePass = MakeRefPtr<BackstagePass>();
1290 
1291     // Make the default XPCShell global use a fresh zone (rather than the
1292     // System Zone) to improve cross-zone test coverage.
1293     JS::RealmOptions options;
1294     options.creationOptions().setNewCompartmentAndZone();
1295     xpc::SetPrefableRealmOptions(options);
1296 
1297     // Even if we're building in a configuration where source is
1298     // discarded, there's no reason to do that on XPCShell, and doing so
1299     // might break various automation scripts.
1300     options.behaviors().setDiscardSource(false);
1301 
1302     JS::Rooted<JSObject*> glob(cx);
1303     rv = xpc::InitClassesWithNewWrappedGlobal(
1304         cx, static_cast<nsIGlobalObject*>(backstagePass), systemprincipal, 0,
1305         options, &glob);
1306     if (NS_FAILED(rv)) {
1307       return 1;
1308     }
1309 
1310     // Initialize e10s check on the main thread, if not already done
1311     BrowserTabsRemoteAutostart();
1312 #ifdef XP_WIN
1313     // Plugin may require audio session if installed plugin can initialize
1314     // asynchronized.
1315     AutoAudioSession audioSession;
1316 
1317     // Ensure that DLL Services are running
1318     RefPtr<DllServices> dllSvc(DllServices::Get());
1319     dllSvc->StartUntrustedModulesProcessor();
1320     auto dllServicesDisable =
1321         MakeScopeExit([&dllSvc]() { dllSvc->DisableFull(); });
1322 
1323 #  if defined(MOZ_SANDBOX)
1324     // Required for sandboxed child processes.
1325     if (aShellData->sandboxBrokerServices) {
1326       SandboxBroker::Initialize(aShellData->sandboxBrokerServices);
1327       SandboxBroker::GeckoDependentInitialize();
1328     } else {
1329       NS_WARNING(
1330           "Failed to initialize broker services, sandboxed "
1331           "processes will fail to start.");
1332     }
1333 #  endif
1334 #endif
1335 
1336 #ifdef MOZ_CODE_COVERAGE
1337     CodeCoverageHandler::Init();
1338 #endif
1339 
1340     {
1341       if (!glob) {
1342         return 1;
1343       }
1344 
1345       nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service());
1346       if (!appStartup) {
1347         return 1;
1348       }
1349       appStartup->DoneStartingUp();
1350 
1351       backstagePass->SetGlobalObject(glob);
1352 
1353       JSAutoRealm ar(cx, glob);
1354 
1355       if (!JS_InitReflectParse(cx, glob)) {
1356         return 1;
1357       }
1358 
1359       if (!JS_DefineFunctions(cx, glob, glob_functions)) {
1360         return 1;
1361       }
1362 
1363       nsAutoString workingDirectory;
1364       if (GetCurrentWorkingDirectory(workingDirectory)) {
1365         gWorkingDirectory = &workingDirectory;
1366       }
1367 
1368       JS_DefineProperty(cx, glob, "__LOCATION__", GetLocationProperty, nullptr,
1369                         0);
1370 
1371       {
1372 #ifdef FUZZING_INTERFACES
1373         if (fuzzHaveModule) {
1374 #  ifdef LIBFUZZER
1375           // argv[0] was removed previously, but libFuzzer expects it
1376           argc++;
1377           argv--;
1378 
1379           result = FuzzXPCRuntimeStart(&jsapi, &argc, &argv,
1380                                        aShellData->fuzzerDriver);
1381 #  elif __AFL_COMPILER
1382           MOZ_CRASH("AFL is unsupported for XPC runtime fuzzing integration");
1383 #  endif
1384         } else {
1385 #endif
1386           // We are almost certainly going to run script here, so we need an
1387           // AutoEntryScript. This is Gecko-specific and not in any spec.
1388           AutoEntryScript aes(backstagePass, "xpcshell argument processing");
1389 
1390           // If an exception is thrown, we'll set our return code
1391           // appropriately, and then let the AutoEntryScript destructor report
1392           // the error to the console.
1393           if (!ProcessArgs(aes, argv, argc, &dirprovider)) {
1394             if (gExitCode) {
1395               result = gExitCode;
1396             } else if (gQuitting) {
1397               result = 0;
1398             } else {
1399               result = EXITCODE_RUNTIME_ERROR;
1400             }
1401           }
1402 #ifdef FUZZING_INTERFACES
1403         }
1404 #endif
1405       }
1406 
1407       // Signal that we're now shutting down.
1408       nsCOMPtr<nsIObserver> obs = do_QueryInterface(appStartup);
1409       if (obs) {
1410         obs->Observe(nullptr, "quit-application-forced", nullptr);
1411       }
1412 
1413       JS_DropPrincipals(cx, gJSPrincipals);
1414       JS_SetAllNonReservedSlotsToUndefined(glob);
1415       JS::RootedObject lexicalEnv(cx, JS_GlobalLexicalEnvironment(glob));
1416       JS_SetAllNonReservedSlotsToUndefined(lexicalEnv);
1417       JS_GC(cx);
1418     }
1419     JS_GC(cx);
1420 
1421     dirprovider.ClearGREDirs();
1422     dirprovider.ClearAppDir();
1423     dirprovider.ClearPluginDir();
1424     dirprovider.ClearAppFile();
1425   }  // this scopes the nsCOMPtrs
1426 
1427   if (!XRE_ShutdownTestShell()) {
1428     NS_ERROR("problem shutting down testshell");
1429   }
1430 
1431   // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
1432   rv = NS_ShutdownXPCOM(nullptr);
1433   MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
1434 
1435   // Shut down the crashreporter service to prevent leaking some strings it
1436   // holds.
1437   if (CrashReporter::GetEnabled()) {
1438     CrashReporter::UnsetExceptionHandler();
1439   }
1440 
1441 #ifdef MOZ_GECKO_PROFILER
1442   // This must precede NS_LogTerm(), otherwise xpcshell return non-zero
1443   // during some tests, which causes failures.
1444   profiler_shutdown();
1445 #endif
1446 
1447   NS_LogTerm();
1448 
1449   return result;
1450 }
1451 
SetGREDirs(nsIFile * greDir)1452 void XPCShellDirProvider::SetGREDirs(nsIFile* greDir) {
1453   mGREDir = greDir;
1454   mGREDir->Clone(getter_AddRefs(mGREBinDir));
1455 
1456 #ifdef XP_MACOSX
1457   nsAutoCString leafName;
1458   mGREDir->GetNativeLeafName(leafName);
1459   if (leafName.EqualsLiteral("Resources")) {
1460     mGREBinDir->SetNativeLeafName("MacOS"_ns);
1461   }
1462 #endif
1463 }
1464 
SetAppFile(nsIFile * appFile)1465 void XPCShellDirProvider::SetAppFile(nsIFile* appFile) { mAppFile = appFile; }
1466 
SetAppDir(nsIFile * appDir)1467 void XPCShellDirProvider::SetAppDir(nsIFile* appDir) { mAppDir = appDir; }
1468 
SetPluginDir(nsIFile * pluginDir)1469 void XPCShellDirProvider::SetPluginDir(nsIFile* pluginDir) {
1470   mPluginDir = pluginDir;
1471 }
1472 
NS_IMETHODIMP_(MozExternalRefCountType)1473 NS_IMETHODIMP_(MozExternalRefCountType)
1474 XPCShellDirProvider::AddRef() { return 2; }
1475 
NS_IMETHODIMP_(MozExternalRefCountType)1476 NS_IMETHODIMP_(MozExternalRefCountType)
1477 XPCShellDirProvider::Release() { return 1; }
1478 
NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider,nsIDirectoryServiceProvider,nsIDirectoryServiceProvider2)1479 NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider, nsIDirectoryServiceProvider,
1480                         nsIDirectoryServiceProvider2)
1481 
1482 NS_IMETHODIMP
1483 XPCShellDirProvider::GetFile(const char* prop, bool* persistent,
1484                              nsIFile** result) {
1485   if (mGREDir && !strcmp(prop, NS_GRE_DIR)) {
1486     *persistent = true;
1487     return mGREDir->Clone(result);
1488   } else if (mGREBinDir && !strcmp(prop, NS_GRE_BIN_DIR)) {
1489     *persistent = true;
1490     return mGREBinDir->Clone(result);
1491   } else if (mAppFile && !strcmp(prop, XRE_EXECUTABLE_FILE)) {
1492     *persistent = true;
1493     return mAppFile->Clone(result);
1494   } else if (mGREDir && !strcmp(prop, NS_APP_PREF_DEFAULTS_50_DIR)) {
1495     nsCOMPtr<nsIFile> file;
1496     *persistent = true;
1497     if (NS_FAILED(mGREDir->Clone(getter_AddRefs(file))) ||
1498         NS_FAILED(file->AppendNative("defaults"_ns)) ||
1499         NS_FAILED(file->AppendNative("pref"_ns)))
1500       return NS_ERROR_FAILURE;
1501     file.forget(result);
1502     return NS_OK;
1503   }
1504 
1505   return NS_ERROR_FAILURE;
1506 }
1507 
1508 NS_IMETHODIMP
GetFiles(const char * prop,nsISimpleEnumerator ** result)1509 XPCShellDirProvider::GetFiles(const char* prop, nsISimpleEnumerator** result) {
1510   if (mGREDir && !strcmp(prop, "ChromeML")) {
1511     nsCOMArray<nsIFile> dirs;
1512 
1513     nsCOMPtr<nsIFile> file;
1514     mGREDir->Clone(getter_AddRefs(file));
1515     file->AppendNative("chrome"_ns);
1516     dirs.AppendObject(file);
1517 
1518     nsresult rv =
1519         NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(file));
1520     if (NS_SUCCEEDED(rv)) {
1521       dirs.AppendObject(file);
1522     }
1523 
1524     return NS_NewArrayEnumerator(result, dirs, NS_GET_IID(nsIFile));
1525   } else if (!strcmp(prop, NS_APP_PREFS_DEFAULTS_DIR_LIST)) {
1526     nsCOMArray<nsIFile> dirs;
1527     nsCOMPtr<nsIFile> appDir;
1528     bool exists;
1529     if (mAppDir && NS_SUCCEEDED(mAppDir->Clone(getter_AddRefs(appDir))) &&
1530         NS_SUCCEEDED(appDir->AppendNative("defaults"_ns)) &&
1531         NS_SUCCEEDED(appDir->AppendNative("preferences"_ns)) &&
1532         NS_SUCCEEDED(appDir->Exists(&exists)) && exists) {
1533       dirs.AppendObject(appDir);
1534       return NS_NewArrayEnumerator(result, dirs, NS_GET_IID(nsIFile));
1535     }
1536     return NS_ERROR_FAILURE;
1537   } else if (!strcmp(prop, NS_APP_PLUGINS_DIR_LIST)) {
1538     nsCOMArray<nsIFile> dirs;
1539     // Add the test plugin location passed in by the caller or through
1540     // runxpcshelltests.
1541     if (mPluginDir) {
1542       dirs.AppendObject(mPluginDir);
1543       // If there was no path specified, default to the one set up by automation
1544     } else {
1545       nsCOMPtr<nsIFile> file;
1546       bool exists;
1547       // We have to add this path, buildbot copies the test plugin directory
1548       // to (app)/bin when unpacking test zips.
1549       if (mGREDir) {
1550         mGREDir->Clone(getter_AddRefs(file));
1551         if (NS_SUCCEEDED(mGREDir->Clone(getter_AddRefs(file)))) {
1552           file->AppendNative("plugins"_ns);
1553           if (NS_SUCCEEDED(file->Exists(&exists)) && exists) {
1554             dirs.AppendObject(file);
1555           }
1556         }
1557       }
1558     }
1559     return NS_NewArrayEnumerator(result, dirs, NS_GET_IID(nsIFile));
1560   }
1561   return NS_ERROR_FAILURE;
1562 }
1563