Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/ipc/testshell/XPCShellEnvironment.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* vim: set ts=8 sts=4 et sw=4 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 <stdlib.h>
8
#include <errno.h>
9
#ifdef HAVE_IO_H
10
#include <io.h>     /* for isatty() */
11
#endif
12
#ifdef HAVE_UNISTD_H
13
#include <unistd.h>     /* for isatty() */
14
#endif
15
16
#include "base/basictypes.h"
17
18
#include "jsapi.h"
19
#include "js/CharacterEncoding.h"
20
#include "js/CompilationAndEvaluation.h"
21
#include "js/SourceBufferHolder.h"
22
23
#include "xpcpublic.h"
24
25
#include "XPCShellEnvironment.h"
26
27
#include "mozilla/XPCOM.h"
28
29
#include "nsIChannel.h"
30
#include "nsIClassInfo.h"
31
#include "nsIDirectoryService.h"
32
#include "nsIPrincipal.h"
33
#include "nsIScriptSecurityManager.h"
34
#include "nsIURI.h"
35
#include "nsIXPConnect.h"
36
#include "nsIXPCScriptable.h"
37
38
#include "nsJSUtils.h"
39
#include "nsJSPrincipals.h"
40
#include "nsThreadUtils.h"
41
#include "nsXULAppAPI.h"
42
43
#include "BackstagePass.h"
44
45
#include "TestShellChild.h"
46
#include "TestShellParent.h"
47
48
using mozilla::ipc::XPCShellEnvironment;
49
using mozilla::ipc::TestShellChild;
50
using mozilla::ipc::TestShellParent;
51
using mozilla::AutoSafeJSContext;
52
using mozilla::dom::AutoJSAPI;
53
using mozilla::dom::AutoEntryScript;
54
using namespace JS;
55
56
namespace {
57
58
static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
59
60
inline XPCShellEnvironment*
61
Environment(Handle<JSObject*> global)
62
0
{
63
0
    AutoJSAPI jsapi;
64
0
    if (!jsapi.Init(global)) {
65
0
        return nullptr;
66
0
    }
67
0
    JSContext* cx = jsapi.cx();
68
0
    Rooted<Value> v(cx);
69
0
    if (!JS_GetProperty(cx, global, "__XPCShellEnvironment", &v) ||
70
0
        !v.get().isDouble())
71
0
    {
72
0
        return nullptr;
73
0
    }
74
0
    return static_cast<XPCShellEnvironment*>(v.get().toPrivate());
75
0
}
76
77
static bool
78
Print(JSContext *cx, unsigned argc, JS::Value *vp)
79
0
{
80
0
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
81
0
82
0
    for (unsigned i = 0; i < args.length(); i++) {
83
0
        JSString *str = JS::ToString(cx, args[i]);
84
0
        if (!str)
85
0
            return false;
86
0
        JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str);
87
0
        if (!bytes)
88
0
            return false;
89
0
        fprintf(stdout, "%s%s", i ? " " : "", bytes.get());
90
0
        fflush(stdout);
91
0
    }
92
0
    fputc('\n', stdout);
93
0
    args.rval().setUndefined();
94
0
    return true;
95
0
}
96
97
static bool
98
GetLine(char *bufp,
99
        FILE *file,
100
        const char *prompt)
101
0
{
102
0
    char line[256];
103
0
    fputs(prompt, stdout);
104
0
    fflush(stdout);
105
0
    if (!fgets(line, sizeof line, file))
106
0
        return false;
107
0
    strcpy(bufp, line);
108
0
    return true;
109
0
}
110
111
static bool
112
Dump(JSContext *cx, unsigned argc, JS::Value *vp)
113
0
{
114
0
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
115
0
116
0
    if (!args.length())
117
0
        return true;
118
0
119
0
    JSString *str = JS::ToString(cx, args[0]);
120
0
    if (!str)
121
0
        return false;
122
0
    JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str);
123
0
    if (!bytes)
124
0
      return false;
125
0
126
0
    fputs(bytes.get(), stdout);
127
0
    fflush(stdout);
128
0
    return true;
129
0
}
130
131
static bool
132
Load(JSContext *cx,
133
     unsigned argc,
134
     JS::Value *vp)
135
0
{
136
0
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
137
0
138
0
    JS::RootedObject thisObject(cx);
139
0
    if (!args.computeThis(cx, &thisObject))
140
0
        return false;
141
0
    if (!JS_IsGlobalObject(thisObject)) {
142
0
        JS_ReportErrorASCII(cx, "Trying to load() into a non-global object");
143
0
        return false;
144
0
    }
145
0
146
0
    for (unsigned i = 0; i < args.length(); i++) {
147
0
        JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[i]));
148
0
        if (!str)
149
0
            return false;
150
0
        JS::UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
151
0
        if (!filename)
152
0
            return false;
153
0
        FILE *file = fopen(filename.get(), "r");
154
0
        if (!file) {
155
0
            filename = JS_EncodeStringToUTF8(cx, str);
156
0
            if (!filename)
157
0
                return false;
158
0
            JS_ReportErrorUTF8(cx, "cannot open file '%s' for reading", filename.get());
159
0
            return false;
160
0
        }
161
0
162
0
        JS::CompileOptions options(cx);
163
0
        options.setFileAndLine(filename.get(), 1);
164
0
165
0
        JS::Rooted<JSScript*> script(cx);
166
0
        bool ok = JS::CompileUtf8File(cx, options, file, &script);
167
0
        fclose(file);
168
0
        if (!ok)
169
0
            return false;
170
0
171
0
        if (!JS_ExecuteScript(cx, script)) {
172
0
            return false;
173
0
        }
174
0
    }
175
0
    args.rval().setUndefined();
176
0
    return true;
177
0
}
178
179
static bool
180
Quit(JSContext *cx,
181
     unsigned argc,
182
     JS::Value *vp)
183
0
{
184
0
    Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
185
0
    XPCShellEnvironment* env = Environment(global);
186
0
    env->SetIsQuitting();
187
0
188
0
    return false;
189
0
}
190
191
static bool
192
DumpXPC(JSContext *cx,
193
        unsigned argc,
194
        JS::Value *vp)
195
0
{
196
0
    JS::CallArgs args = CallArgsFromVp(argc, vp);
197
0
198
0
    uint16_t depth = 2;
199
0
    if (args.length() > 0) {
200
0
        if (!JS::ToUint16(cx, args[0], &depth))
201
0
            return false;
202
0
    }
203
0
204
0
    nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
205
0
    if (xpc)
206
0
        xpc->DebugDump(int16_t(depth));
207
0
    args.rval().setUndefined();
208
0
    return true;
209
0
}
210
211
static bool
212
GC(JSContext *cx,
213
   unsigned argc,
214
   JS::Value *vp)
215
0
{
216
0
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
217
0
218
0
    JS_GC(cx);
219
0
220
0
    args.rval().setUndefined();
221
0
    return true;
222
0
}
223
224
#ifdef JS_GC_ZEAL
225
static bool
226
GCZeal(JSContext *cx, unsigned argc, JS::Value *vp)
227
{
228
  CallArgs args = CallArgsFromVp(argc, vp);
229
230
  uint32_t zeal;
231
  if (!ToUint32(cx, args.get(0), &zeal))
232
    return false;
233
234
  JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
235
  return true;
236
}
237
#endif
238
239
const JSFunctionSpec gGlobalFunctions[] =
240
{
241
    JS_FN("print",           Print,          0,0),
242
    JS_FN("load",            Load,           1,0),
243
    JS_FN("quit",            Quit,           0,0),
244
    JS_FN("dumpXPC",         DumpXPC,        1,0),
245
    JS_FN("dump",            Dump,           1,0),
246
    JS_FN("gc",              GC,             0,0),
247
 #ifdef JS_GC_ZEAL
248
    JS_FN("gczeal",          GCZeal,         1,0),
249
 #endif
250
    JS_FS_END
251
};
252
253
typedef enum JSShellErrNum
254
{
255
#define MSG_DEF(name, number, count, exception, format) \
256
    name = number,
257
#include "jsshell.msg"
258
#undef MSG_DEF
259
    JSShellErr_Limit
260
#undef MSGDEF
261
} JSShellErrNum;
262
263
} /* anonymous namespace */
264
265
void
266
XPCShellEnvironment::ProcessFile(JSContext *cx,
267
                                 const char *filename,
268
                                 FILE *file,
269
                                 bool forceTTY)
270
0
{
271
0
    XPCShellEnvironment* env = this;
272
0
273
0
    JS::Rooted<JS::Value> result(cx);
274
0
    int lineno, startline;
275
0
    bool ok, hitEOF;
276
0
    char *bufp, buffer[4096];
277
0
    JSString *str;
278
0
279
0
    JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
280
0
    MOZ_ASSERT(global);
281
0
282
0
    if (forceTTY) {
283
0
        file = stdin;
284
0
    }
285
0
    else if (!isatty(fileno(file)))
286
0
    {
287
0
        /*
288
0
         * It's not interactive - just execute it.
289
0
         *
290
0
         * Support the UNIX #! shell hack; gobble the first line if it starts
291
0
         * with '#'.  TODO - this isn't quite compatible with sharp variables,
292
0
         * as a legal js program (using sharp variables) might start with '#'.
293
0
         * But that would require multi-character lookahead.
294
0
         */
295
0
        int ch = fgetc(file);
296
0
        if (ch == '#') {
297
0
            while((ch = fgetc(file)) != EOF) {
298
0
                if(ch == '\n' || ch == '\r')
299
0
                    break;
300
0
            }
301
0
        }
302
0
        ungetc(ch, file);
303
0
304
0
        JS::CompileOptions options(cx);
305
0
        options.setFileAndLine(filename, 1);
306
0
307
0
        JS::Rooted<JSScript*> script(cx);
308
0
        if (JS::CompileUtf8File(cx, options, file, &script))
309
0
            (void)JS_ExecuteScript(cx, script, &result);
310
0
311
0
        return;
312
0
    }
313
0
314
0
    /* It's an interactive filehandle; drop into read-eval-print loop. */
315
0
    lineno = 1;
316
0
    hitEOF = false;
317
0
    do {
318
0
        bufp = buffer;
319
0
        *bufp = '\0';
320
0
321
0
        /*
322
0
         * Accumulate lines until we get a 'compilable unit' - one that either
323
0
         * generates an error (before running out of source) or that compiles
324
0
         * cleanly.  This should be whenever we get a complete statement that
325
0
         * coincides with the end of a line.
326
0
         */
327
0
        startline = lineno;
328
0
        do {
329
0
            if (!GetLine(bufp, file, startline == lineno ? "js> " : "")) {
330
0
                hitEOF = true;
331
0
                break;
332
0
            }
333
0
            bufp += strlen(bufp);
334
0
            lineno++;
335
0
        } while (!JS_Utf8BufferIsCompilableUnit(cx, global, buffer, strlen(buffer)));
336
0
337
0
        /* Clear any pending exception from previous failed compiles.  */
338
0
        JS_ClearPendingException(cx);
339
0
340
0
        JS::CompileOptions options(cx);
341
0
        options.setFileAndLine("typein", startline);
342
0
343
0
        JS::Rooted<JSScript*> script(cx);
344
0
        if (JS::CompileUtf8(cx, options, buffer, strlen(buffer), &script)) {
345
0
            JS::WarningReporter older;
346
0
347
0
            ok = JS_ExecuteScript(cx, script, &result);
348
0
            if (ok && !result.isUndefined()) {
349
0
                /* Suppress warnings from JS::ToString(). */
350
0
                older = JS::SetWarningReporter(cx, nullptr);
351
0
                str = JS::ToString(cx, result);
352
0
                JS::UniqueChars bytes;
353
0
                if (str)
354
0
                    bytes = JS_EncodeStringToLatin1(cx, str);
355
0
                JS::SetWarningReporter(cx, older);
356
0
357
0
                if (!!bytes)
358
0
                    fprintf(stdout, "%s\n", bytes.get());
359
0
                else
360
0
                    ok = false;
361
0
            }
362
0
        }
363
0
    } while (!hitEOF && !env->IsQuitting());
364
0
365
0
    fprintf(stdout, "\n");
366
0
}
367
368
// static
369
XPCShellEnvironment*
370
XPCShellEnvironment::CreateEnvironment()
371
0
{
372
0
    auto* env = new XPCShellEnvironment();
373
0
    if (env && !env->Init()) {
374
0
        delete env;
375
0
        env = nullptr;
376
0
    }
377
0
    return env;
378
0
}
379
380
XPCShellEnvironment::XPCShellEnvironment()
381
:   mQuitting(false)
382
0
{
383
0
}
384
385
XPCShellEnvironment::~XPCShellEnvironment()
386
0
{
387
0
    if (GetGlobalObject()) {
388
0
        AutoJSAPI jsapi;
389
0
        if (!jsapi.Init(GetGlobalObject())) {
390
0
            return;
391
0
        }
392
0
        JSContext* cx = jsapi.cx();
393
0
        Rooted<JSObject*> global(cx, GetGlobalObject());
394
0
395
0
        {
396
0
            JSAutoRealm ar(cx, global);
397
0
            JS_SetAllNonReservedSlotsToUndefined(cx, global);
398
0
        }
399
0
        mGlobalHolder.reset();
400
0
401
0
        JS_GC(cx);
402
0
    }
403
0
}
404
405
bool
406
XPCShellEnvironment::Init()
407
0
{
408
0
    nsresult rv;
409
0
410
0
    // unbuffer stdout so that output is in the correct order; note that stderr
411
0
    // is unbuffered by default
412
0
    setbuf(stdout, 0);
413
0
414
0
    AutoSafeJSContext cx;
415
0
416
0
    mGlobalHolder.init(cx);
417
0
418
0
    nsCOMPtr<nsIPrincipal> principal;
419
0
    nsCOMPtr<nsIScriptSecurityManager> securityManager =
420
0
        do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
421
0
    if (NS_SUCCEEDED(rv) && securityManager) {
422
0
        rv = securityManager->GetSystemPrincipal(getter_AddRefs(principal));
423
0
        if (NS_FAILED(rv)) {
424
0
            fprintf(stderr, "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager service.\n");
425
0
        }
426
0
    } else {
427
0
        fprintf(stderr, "+++ Failed to get ScriptSecurityManager service, running without principals");
428
0
    }
429
0
430
0
    RefPtr<BackstagePass> backstagePass;
431
0
    rv = NS_NewBackstagePass(getter_AddRefs(backstagePass));
432
0
    if (NS_FAILED(rv)) {
433
0
        NS_ERROR("Failed to create backstage pass!");
434
0
        return false;
435
0
    }
436
0
437
0
    JS::RealmOptions options;
438
0
    options.creationOptions().setNewCompartmentInSystemZone();
439
0
    if (xpc::SharedMemoryEnabled())
440
0
        options.creationOptions().setSharedMemoryAndAtomicsEnabled(true);
441
0
442
0
    JS::Rooted<JSObject*> globalObj(cx);
443
0
    rv = xpc::InitClassesWithNewWrappedGlobal(cx,
444
0
                                              static_cast<nsIGlobalObject *>(backstagePass),
445
0
                                              principal, 0,
446
0
                                              options,
447
0
                                              &globalObj);
448
0
    if (NS_FAILED(rv)) {
449
0
        NS_ERROR("InitClassesWithNewWrappedGlobal failed!");
450
0
        return false;
451
0
    }
452
0
453
0
    if (!globalObj) {
454
0
        NS_ERROR("Failed to get global JSObject!");
455
0
        return false;
456
0
    }
457
0
    JSAutoRealm ar(cx, globalObj);
458
0
459
0
    backstagePass->SetGlobalObject(globalObj);
460
0
461
0
    JS::Rooted<Value> privateVal(cx, PrivateValue(this));
462
0
    if (!JS_DefineProperty(cx, globalObj, "__XPCShellEnvironment",
463
0
                           privateVal,
464
0
                           JSPROP_READONLY | JSPROP_PERMANENT) ||
465
0
        !JS_DefineFunctions(cx, globalObj, gGlobalFunctions) ||
466
0
        !JS_DefineProfilingFunctions(cx, globalObj))
467
0
    {
468
0
        NS_ERROR("JS_DefineFunctions failed!");
469
0
        return false;
470
0
    }
471
0
472
0
    mGlobalHolder = globalObj;
473
0
474
0
    FILE* runtimeScriptFile = fopen(kDefaultRuntimeScriptFilename, "r");
475
0
    if (runtimeScriptFile) {
476
0
        fprintf(stdout, "[loading '%s'...]\n", kDefaultRuntimeScriptFilename);
477
0
        ProcessFile(cx, kDefaultRuntimeScriptFilename,
478
0
                    runtimeScriptFile, false);
479
0
        fclose(runtimeScriptFile);
480
0
    }
481
0
482
0
    return true;
483
0
}
484
485
bool
486
XPCShellEnvironment::EvaluateString(const nsString& aString,
487
                                    nsString* aResult)
488
0
{
489
0
  AutoEntryScript aes(GetGlobalObject(),
490
0
                      "ipc XPCShellEnvironment::EvaluateString");
491
0
  JSContext* cx = aes.cx();
492
0
493
0
  JS::CompileOptions options(cx);
494
0
  options.setFileAndLine("typein", 0);
495
0
496
0
  JS::Rooted<JSScript*> script(cx);
497
0
  JS::SourceBufferHolder srcBuf(aString.get(), aString.Length(),
498
0
                                JS::SourceBufferHolder::NoOwnership);
499
0
  if (!JS::Compile(cx, options, srcBuf, &script))
500
0
  {
501
0
     return false;
502
0
  }
503
0
504
0
  if (aResult) {
505
0
      aResult->Truncate();
506
0
  }
507
0
508
0
  JS::Rooted<JS::Value> result(cx);
509
0
  bool ok = JS_ExecuteScript(cx, script, &result);
510
0
  if (ok && !result.isUndefined()) {
511
0
      JS::WarningReporter old = JS::SetWarningReporter(cx, nullptr);
512
0
      JSString* str = JS::ToString(cx, result);
513
0
      nsAutoJSString autoStr;
514
0
      if (str)
515
0
          autoStr.init(cx, str);
516
0
      JS::SetWarningReporter(cx, old);
517
0
518
0
      if (!autoStr.IsEmpty() && aResult) {
519
0
          aResult->Assign(autoStr);
520
0
      }
521
0
  }
522
0
523
0
  return true;
524
0
}