/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 | } |