ipc/testshell/XPCShellEnvironment.cpp

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     1 /* vim: set ts=8 sts=4 et sw=4 tw=80:
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include <stdlib.h>
     7 #include <errno.h>
     8 #ifdef HAVE_IO_H
     9 #include <io.h>     /* for isatty() */
    10 #endif
    11 #ifdef HAVE_UNISTD_H
    12 #include <unistd.h>     /* for isatty() */
    13 #endif
    15 #include "base/basictypes.h"
    17 #include "jsapi.h"
    18 #include "js/OldDebugAPI.h"
    20 #include "xpcpublic.h"
    22 #include "XPCShellEnvironment.h"
    24 #include "mozilla/XPCOM.h"
    26 #include "nsIChannel.h"
    27 #include "nsIClassInfo.h"
    28 #include "nsIDirectoryService.h"
    29 #include "nsIJSRuntimeService.h"
    30 #include "nsIPrincipal.h"
    31 #include "nsIScriptSecurityManager.h"
    32 #include "nsIURI.h"
    33 #include "nsIXPConnect.h"
    34 #include "nsIXPCScriptable.h"
    36 #include "nsCxPusher.h"
    37 #include "nsJSUtils.h"
    38 #include "nsJSPrincipals.h"
    39 #include "nsThreadUtils.h"
    40 #include "nsXULAppAPI.h"
    42 #include "BackstagePass.h"
    44 #include "TestShellChild.h"
    45 #include "TestShellParent.h"
    47 using mozilla::ipc::XPCShellEnvironment;
    48 using mozilla::ipc::TestShellChild;
    49 using mozilla::ipc::TestShellParent;
    50 using mozilla::AutoSafeJSContext;
    51 using namespace JS;
    53 namespace {
    55 static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
    57 class XPCShellDirProvider : public nsIDirectoryServiceProvider
    58 {
    59 public:
    60     NS_DECL_ISUPPORTS
    61     NS_DECL_NSIDIRECTORYSERVICEPROVIDER
    63     XPCShellDirProvider() { }
    64     ~XPCShellDirProvider() { }
    66     bool SetGREDir(const char *dir);
    67     void ClearGREDir() { mGREDir = nullptr; }
    69 private:
    70     nsCOMPtr<nsIFile> mGREDir;
    71 };
    73 inline XPCShellEnvironment*
    74 Environment(Handle<JSObject*> global)
    75 {
    76     AutoSafeJSContext cx;
    77     JSAutoCompartment ac(cx, global);
    78     Rooted<Value> v(cx);
    79     if (!JS_GetProperty(cx, global, "__XPCShellEnvironment", &v) ||
    80         !v.get().isDouble())
    81     {
    82         return nullptr;
    83     }
    84     return static_cast<XPCShellEnvironment*>(v.get().toPrivate());
    85 }
    87 static bool
    88 Print(JSContext *cx, unsigned argc, JS::Value *vp)
    89 {
    90     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    92     for (unsigned i = 0; i < args.length(); i++) {
    93         JSString *str = JS::ToString(cx, args[i]);
    94         if (!str)
    95             return false;
    96         JSAutoByteString bytes(cx, str);
    97         if (!bytes)
    98             return false;
    99         fprintf(stdout, "%s%s", i ? " " : "", bytes.ptr());
   100         fflush(stdout);
   101     }
   102     fputc('\n', stdout);
   103     args.rval().setUndefined();
   104     return true;
   105 }
   107 static bool
   108 GetLine(char *bufp,
   109         FILE *file,
   110         const char *prompt)
   111 {
   112     char line[256];
   113     fputs(prompt, stdout);
   114     fflush(stdout);
   115     if (!fgets(line, sizeof line, file))
   116         return false;
   117     strcpy(bufp, line);
   118     return true;
   119 }
   121 static bool
   122 Dump(JSContext *cx, unsigned argc, JS::Value *vp)
   123 {
   124     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   126     if (!args.length())
   127         return true;
   129     JSString *str = JS::ToString(cx, args[0]);
   130     if (!str)
   131         return false;
   132     JSAutoByteString bytes(cx, str);
   133     if (!bytes)
   134       return false;
   136     fputs(bytes.ptr(), stdout);
   137     fflush(stdout);
   138     return true;
   139 }
   141 static bool
   142 Load(JSContext *cx,
   143      unsigned argc,
   144      JS::Value *vp)
   145 {
   146     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   148     JS::Rooted<JSObject*> obj(cx, JS_THIS_OBJECT(cx, vp));
   149     if (!obj)
   150         return false;
   152     for (unsigned i = 0; i < args.length(); i++) {
   153         JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[i]));
   154         if (!str)
   155             return false;
   156         JSAutoByteString filename(cx, str);
   157         if (!filename)
   158             return false;
   159         FILE *file = fopen(filename.ptr(), "r");
   160         if (!file) {
   161             JS_ReportError(cx, "cannot open file '%s' for reading", filename.ptr());
   162             return false;
   163         }
   164         Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
   165         JS::CompileOptions options(cx);
   166         options.setUTF8(true)
   167                .setFileAndLine(filename.ptr(), 1);
   168         JS::Rooted<JSScript*> script(cx, JS::Compile(cx, obj, options, file));
   169         fclose(file);
   170         if (!script)
   171             return false;
   173         if (!JS_ExecuteScript(cx, obj, script)) {
   174             return false;
   175         }
   176     }
   177     args.rval().setUndefined();
   178     return true;
   179 }
   181 static bool
   182 Version(JSContext *cx,
   183         unsigned argc,
   184         JS::Value *vp)
   185 {
   186     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   187     args.rval().setInt32(JS_GetVersion(cx));
   188     if (args.get(0).isInt32())
   189         JS_SetVersionForCompartment(js::GetContextCompartment(cx),
   190                                     JSVersion(args[0].toInt32()));
   191     return true;
   192 }
   194 static bool
   195 BuildDate(JSContext *cx, unsigned argc, JS::Value *vp)
   196 {
   197     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   198     fprintf(stdout, "built on %s at %s\n", __DATE__, __TIME__);
   199     args.rval().setUndefined();
   200     return true;
   201 }
   203 static bool
   204 Quit(JSContext *cx,
   205      unsigned argc,
   206      JS::Value *vp)
   207 {
   208     Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
   209     XPCShellEnvironment* env = Environment(global);
   210     env->SetIsQuitting();
   212     return false;
   213 }
   215 static bool
   216 DumpXPC(JSContext *cx,
   217         unsigned argc,
   218         JS::Value *vp)
   219 {
   220     JS::CallArgs args = CallArgsFromVp(argc, vp);
   222     uint16_t depth = 2;
   223     if (args.length() > 0) {
   224         if (!JS::ToUint16(cx, args[0], &depth))
   225             return false;
   226     }
   228     nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID());
   229     if (xpc)
   230         xpc->DebugDump(int16_t(depth));
   231     args.rval().setUndefined();
   232     return true;
   233 }
   235 static bool
   236 GC(JSContext *cx,
   237    unsigned argc,
   238    JS::Value *vp)
   239 {
   240     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   242     JSRuntime *rt = JS_GetRuntime(cx);
   243     JS_GC(rt);
   244 #ifdef JS_GCMETER
   245     js_DumpGCStats(rt, stdout);
   246 #endif
   247     args.rval().setUndefined();
   248     return true;
   249 }
   251 #ifdef JS_GC_ZEAL
   252 static bool
   253 GCZeal(JSContext *cx, unsigned argc, JS::Value *vp)
   254 {
   255   CallArgs args = CallArgsFromVp(argc, vp);
   257   uint32_t zeal;
   258   if (!ToUint32(cx, args.get(0), &zeal))
   259     return false;
   261   JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
   262   return true;
   263 }
   264 #endif
   266 const JSFunctionSpec gGlobalFunctions[] =
   267 {
   268     JS_FS("print",           Print,          0,0),
   269     JS_FS("load",            Load,           1,0),
   270     JS_FS("quit",            Quit,           0,0),
   271     JS_FS("version",         Version,        1,0),
   272     JS_FS("build",           BuildDate,      0,0),
   273     JS_FS("dumpXPC",         DumpXPC,        1,0),
   274     JS_FS("dump",            Dump,           1,0),
   275     JS_FS("gc",              GC,             0,0),
   276  #ifdef JS_GC_ZEAL
   277     JS_FS("gczeal",          GCZeal,         1,0),
   278  #endif
   279     JS_FS_END
   280 };
   282 typedef enum JSShellErrNum
   283 {
   284 #define MSG_DEF(name, number, count, exception, format) \
   285     name = number,
   286 #include "jsshell.msg"
   287 #undef MSG_DEF
   288     JSShellErr_Limit
   289 #undef MSGDEF
   290 } JSShellErrNum;
   292 } /* anonymous namespace */
   294 void
   295 XPCShellEnvironment::ProcessFile(JSContext *cx,
   296                                  JS::Handle<JSObject*> obj,
   297                                  const char *filename,
   298                                  FILE *file,
   299                                  bool forceTTY)
   300 {
   301     XPCShellEnvironment* env = this;
   303     JS::Rooted<JS::Value> result(cx);
   304     int lineno, startline;
   305     bool ok, hitEOF;
   306     char *bufp, buffer[4096];
   307     JSString *str;
   309     if (forceTTY) {
   310         file = stdin;
   311     }
   312     else if (!isatty(fileno(file)))
   313     {
   314         /*
   315          * It's not interactive - just execute it.
   316          *
   317          * Support the UNIX #! shell hack; gobble the first line if it starts
   318          * with '#'.  TODO - this isn't quite compatible with sharp variables,
   319          * as a legal js program (using sharp variables) might start with '#'.
   320          * But that would require multi-character lookahead.
   321          */
   322         int ch = fgetc(file);
   323         if (ch == '#') {
   324             while((ch = fgetc(file)) != EOF) {
   325                 if(ch == '\n' || ch == '\r')
   326                     break;
   327             }
   328         }
   329         ungetc(ch, file);
   331         JSAutoRequest ar(cx);
   332         JSAutoCompartment ac(cx, obj);
   334         JS::CompileOptions options(cx);
   335         options.setUTF8(true)
   336                .setFileAndLine(filename, 1);
   337         JS::Rooted<JSScript*> script(cx, JS::Compile(cx, obj, options, file));
   338         if (script)
   339             (void)JS_ExecuteScript(cx, obj, script, &result);
   341         return;
   342     }
   344     /* It's an interactive filehandle; drop into read-eval-print loop. */
   345     lineno = 1;
   346     hitEOF = false;
   347     do {
   348         bufp = buffer;
   349         *bufp = '\0';
   351         JSAutoRequest ar(cx);
   352         JSAutoCompartment ac(cx, obj);
   354         /*
   355          * Accumulate lines until we get a 'compilable unit' - one that either
   356          * generates an error (before running out of source) or that compiles
   357          * cleanly.  This should be whenever we get a complete statement that
   358          * coincides with the end of a line.
   359          */
   360         startline = lineno;
   361         do {
   362             if (!GetLine(bufp, file, startline == lineno ? "js> " : "")) {
   363                 hitEOF = true;
   364                 break;
   365             }
   366             bufp += strlen(bufp);
   367             lineno++;
   368         } while (!JS_BufferIsCompilableUnit(cx, obj, buffer, strlen(buffer)));
   370         /* Clear any pending exception from previous failed compiles.  */
   371         JS_ClearPendingException(cx);
   372         JS::CompileOptions options(cx);
   373         options.setFileAndLine("typein", startline);
   374         JS::Rooted<JSScript*> script(cx,
   375                                      JS_CompileScript(cx, obj, buffer, strlen(buffer), options));
   376         if (script) {
   377             JSErrorReporter older;
   379             ok = JS_ExecuteScript(cx, obj, script, &result);
   380             if (ok && result != JSVAL_VOID) {
   381                 /* Suppress error reports from JS::ToString(). */
   382                 older = JS_SetErrorReporter(cx, nullptr);
   383                 str = JS::ToString(cx, result);
   384                 JSAutoByteString bytes;
   385                 if (str)
   386                     bytes.encodeLatin1(cx, str);
   387                 JS_SetErrorReporter(cx, older);
   389                 if (!!bytes)
   390                     fprintf(stdout, "%s\n", bytes.ptr());
   391                 else
   392                     ok = false;
   393             }
   394         }
   395     } while (!hitEOF && !env->IsQuitting());
   397     fprintf(stdout, "\n");
   398 }
   400 NS_IMETHODIMP_(MozExternalRefCountType)
   401 XPCShellDirProvider::AddRef()
   402 {
   403     return 2;
   404 }
   406 NS_IMETHODIMP_(MozExternalRefCountType)
   407 XPCShellDirProvider::Release()
   408 {
   409     return 1;
   410 }
   412 NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider, nsIDirectoryServiceProvider)
   414 bool
   415 XPCShellDirProvider::SetGREDir(const char *dir)
   416 {
   417     nsresult rv = XRE_GetFileFromPath(dir, getter_AddRefs(mGREDir));
   418     return NS_SUCCEEDED(rv);
   419 }
   421 NS_IMETHODIMP
   422 XPCShellDirProvider::GetFile(const char *prop,
   423                              bool *persistent,
   424                              nsIFile* *result)
   425 {
   426     if (mGREDir && !strcmp(prop, NS_GRE_DIR)) {
   427         *persistent = true;
   428         NS_ADDREF(*result = mGREDir);
   429         return NS_OK;
   430     }
   432     return NS_ERROR_FAILURE;
   433 }
   435 // static
   436 XPCShellEnvironment*
   437 XPCShellEnvironment::CreateEnvironment()
   438 {
   439     XPCShellEnvironment* env = new XPCShellEnvironment();
   440     if (env && !env->Init()) {
   441         delete env;
   442         env = nullptr;
   443     }
   444     return env;
   445 }
   447 XPCShellEnvironment::XPCShellEnvironment()
   448 :   mQuitting(false)
   449 {
   450 }
   452 XPCShellEnvironment::~XPCShellEnvironment()
   453 {
   455     AutoSafeJSContext cx;
   456     Rooted<JSObject*> global(cx, GetGlobalObject());
   457     if (global) {
   458         {
   459             JSAutoCompartment ac(cx, global);
   460             JS_SetAllNonReservedSlotsToUndefined(cx, global);
   461         }
   462         mGlobalHolder.Release();
   464         JSRuntime *rt = JS_GetRuntime(cx);
   465         JS_GC(rt);
   466     }
   467 }
   469 bool
   470 XPCShellEnvironment::Init()
   471 {
   472     nsresult rv;
   474     // unbuffer stdout so that output is in the correct order; note that stderr
   475     // is unbuffered by default
   476     setbuf(stdout, 0);
   478     nsCOMPtr<nsIJSRuntimeService> rtsvc =
   479         do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
   480     if (!rtsvc) {
   481         NS_ERROR("failed to get nsJSRuntimeService!");
   482         return false;
   483     }
   485     JSRuntime *rt;
   486     if (NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
   487         NS_ERROR("failed to get JSRuntime from nsJSRuntimeService!");
   488         return false;
   489     }
   491     if (!mGlobalHolder.Hold(rt)) {
   492         NS_ERROR("Can't protect global object!");
   493         return false;
   494     }
   496     AutoSafeJSContext cx;
   498     JS_SetContextPrivate(cx, this);
   500     nsCOMPtr<nsIXPConnect> xpc =
   501       do_GetService(nsIXPConnect::GetCID());
   502     if (!xpc) {
   503         NS_ERROR("failed to get nsXPConnect service!");
   504         return false;
   505     }
   507     nsCOMPtr<nsIPrincipal> principal;
   508     nsCOMPtr<nsIScriptSecurityManager> securityManager =
   509         do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
   510     if (NS_SUCCEEDED(rv) && securityManager) {
   511         rv = securityManager->GetSystemPrincipal(getter_AddRefs(principal));
   512         if (NS_FAILED(rv)) {
   513             fprintf(stderr, "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager service.\n");
   514         }
   515     } else {
   516         fprintf(stderr, "+++ Failed to get ScriptSecurityManager service, running without principals");
   517     }
   519     nsRefPtr<BackstagePass> backstagePass;
   520     rv = NS_NewBackstagePass(getter_AddRefs(backstagePass));
   521     if (NS_FAILED(rv)) {
   522         NS_ERROR("Failed to create backstage pass!");
   523         return false;
   524     }
   526     JS::CompartmentOptions options;
   527     options.setZone(JS::SystemZone)
   528            .setVersion(JSVERSION_LATEST);
   529     nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
   530     rv = xpc->InitClassesWithNewWrappedGlobal(cx,
   531                                               static_cast<nsIGlobalObject *>(backstagePass),
   532                                               principal, 0,
   533                                               options,
   534                                               getter_AddRefs(holder));
   535     if (NS_FAILED(rv)) {
   536         NS_ERROR("InitClassesWithNewWrappedGlobal failed!");
   537         return false;
   538     }
   540     JS::Rooted<JSObject*> globalObj(cx, holder->GetJSObject());
   541     if (!globalObj) {
   542         NS_ERROR("Failed to get global JSObject!");
   543         return false;
   544     }
   545     JSAutoCompartment ac(cx, globalObj);
   547     backstagePass->SetGlobalObject(globalObj);
   549     JS::Rooted<Value> privateVal(cx, PrivateValue(this));
   550     if (!JS_DefineProperty(cx, globalObj, "__XPCShellEnvironment",
   551                            privateVal, JSPROP_READONLY | JSPROP_PERMANENT,
   552                            JS_PropertyStub, JS_StrictPropertyStub) ||
   553         !JS_DefineFunctions(cx, globalObj, gGlobalFunctions) ||
   554         !JS_DefineProfilingFunctions(cx, globalObj))
   555     {
   556         NS_ERROR("JS_DefineFunctions failed!");
   557         return false;
   558     }
   560     mGlobalHolder = globalObj;
   562     FILE* runtimeScriptFile = fopen(kDefaultRuntimeScriptFilename, "r");
   563     if (runtimeScriptFile) {
   564         fprintf(stdout, "[loading '%s'...]\n", kDefaultRuntimeScriptFilename);
   565         ProcessFile(cx, globalObj, kDefaultRuntimeScriptFilename,
   566                     runtimeScriptFile, false);
   567         fclose(runtimeScriptFile);
   568     }
   570     return true;
   571 }
   573 bool
   574 XPCShellEnvironment::EvaluateString(const nsString& aString,
   575                                     nsString* aResult)
   576 {
   577   AutoSafeJSContext cx;
   578   JS::Rooted<JSObject*> global(cx, GetGlobalObject());
   579   JSAutoCompartment ac(cx, global);
   581   JS::CompileOptions options(cx);
   582   options.setFileAndLine("typein", 0);
   583   JS::Rooted<JSScript*> script(cx, JS_CompileUCScript(cx, global, aString.get(),
   584                                                       aString.Length(), options));
   585   if (!script) {
   586      return false;
   587   }
   589   if (aResult) {
   590       aResult->Truncate();
   591   }
   593   JS::Rooted<JS::Value> result(cx);
   594   bool ok = JS_ExecuteScript(cx, global, script, &result);
   595   if (ok && result != JSVAL_VOID) {
   596       JSErrorReporter old = JS_SetErrorReporter(cx, nullptr);
   597       JSString* str = JS::ToString(cx, result);
   598       nsDependentJSString depStr;
   599       if (str)
   600           depStr.init(cx, str);
   601       JS_SetErrorReporter(cx, old);
   603       if (!depStr.IsEmpty() && aResult) {
   604           aResult->Assign(depStr);
   605       }
   606   }
   608   return true;
   609 }

mercurial