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