Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: sw=4 ts=4 et :
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/. */
7 #include "mozilla/ArrayUtils.h"
9 #include "prenv.h"
10 #include "prerror.h"
11 #include "prio.h"
12 #include "prproces.h"
14 #include "nsMemory.h"
16 #include "mozilla/CondVar.h"
17 #include "mozilla/ReentrantMonitor.h"
18 #include "mozilla/Mutex.h"
20 #include "TestHarness.h"
22 using namespace mozilla;
24 static PRThread*
25 spawn(void (*run)(void*), void* arg)
26 {
27 return PR_CreateThread(PR_SYSTEM_THREAD,
28 run,
29 arg,
30 PR_PRIORITY_NORMAL,
31 PR_GLOBAL_THREAD,
32 PR_JOINABLE_THREAD,
33 0);
34 }
36 #define PASS() \
37 do { \
38 passed(__FUNCTION__); \
39 return NS_OK; \
40 } while (0)
42 #define FAIL(why) \
43 do { \
44 fail("%s | %s - %s", __FILE__, __FUNCTION__, why); \
45 return NS_ERROR_FAILURE; \
46 } while (0)
48 //-----------------------------------------------------------------------------
50 static const char* sPathToThisBinary;
51 static const char* sAssertBehaviorEnv = "XPCOM_DEBUG_BREAK=abort";
53 class Subprocess
54 {
55 public:
56 // not available until process finishes
57 int32_t mExitCode;
58 nsCString mStdout;
59 nsCString mStderr;
61 Subprocess(const char* aTestName) {
62 // set up stdio redirection
63 PRFileDesc* readStdin; PRFileDesc* writeStdin;
64 PRFileDesc* readStdout; PRFileDesc* writeStdout;
65 PRFileDesc* readStderr; PRFileDesc* writeStderr;
66 PRProcessAttr* pattr = PR_NewProcessAttr();
68 NS_ASSERTION(pattr, "couldn't allocate process attrs");
70 NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdin, &writeStdin),
71 "couldn't create child stdin pipe");
72 NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(readStdin, true),
73 "couldn't set child stdin inheritable");
74 PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardInput, readStdin);
76 NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdout, &writeStdout),
77 "couldn't create child stdout pipe");
78 NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStdout, true),
79 "couldn't set child stdout inheritable");
80 PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardOutput, writeStdout);
82 NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStderr, &writeStderr),
83 "couldn't create child stderr pipe");
84 NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStderr, true),
85 "couldn't set child stderr inheritable");
86 PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardError, writeStderr);
88 // set up argv with test name to run
89 char* const newArgv[3] = {
90 strdup(sPathToThisBinary),
91 strdup(aTestName),
92 0
93 };
95 // make sure the child will abort if an assertion fails
96 NS_ASSERTION(PR_SUCCESS == PR_SetEnv(sAssertBehaviorEnv),
97 "couldn't set XPCOM_DEBUG_BREAK env var");
99 PRProcess* proc;
100 NS_ASSERTION(proc = PR_CreateProcess(sPathToThisBinary,
101 newArgv,
102 0, // inherit environment
103 pattr),
104 "couldn't create process");
105 PR_Close(readStdin);
106 PR_Close(writeStdout);
107 PR_Close(writeStderr);
109 mProc = proc;
110 mStdinfd = writeStdin;
111 mStdoutfd = readStdout;
112 mStderrfd = readStderr;
114 free(newArgv[0]);
115 free(newArgv[1]);
116 PR_DestroyProcessAttr(pattr);
117 }
119 void RunToCompletion(uint32_t aWaitMs)
120 {
121 PR_Close(mStdinfd);
123 PRPollDesc pollfds[2];
124 int32_t nfds;
125 bool stdoutOpen = true, stderrOpen = true;
126 char buf[4096];
128 PRIntervalTime now = PR_IntervalNow();
129 PRIntervalTime deadline = now + PR_MillisecondsToInterval(aWaitMs);
131 while ((stdoutOpen || stderrOpen) && now < deadline) {
132 nfds = 0;
133 if (stdoutOpen) {
134 pollfds[nfds].fd = mStdoutfd;
135 pollfds[nfds].in_flags = PR_POLL_READ;
136 pollfds[nfds].out_flags = 0;
137 ++nfds;
138 }
139 if (stderrOpen) {
140 pollfds[nfds].fd = mStderrfd;
141 pollfds[nfds].in_flags = PR_POLL_READ;
142 pollfds[nfds].out_flags = 0;
143 ++nfds;
144 }
146 int32_t rv = PR_Poll(pollfds, nfds, deadline - now);
147 NS_ASSERTION(0 <= rv, PR_ErrorToName(PR_GetError()));
149 if (0 == rv) { // timeout
150 fputs("(timed out!)\n", stderr);
151 Finish(false); // abnormal
152 return;
153 }
155 for (int32_t i = 0; i < nfds; ++i) {
156 if (!pollfds[i].out_flags)
157 continue;
159 bool isStdout = mStdoutfd == pollfds[i].fd;
160 int32_t len = 0;
162 if (PR_POLL_READ & pollfds[i].out_flags) {
163 len = PR_Read(pollfds[i].fd, buf, sizeof(buf) - 1);
164 NS_ASSERTION(0 <= len, PR_ErrorToName(PR_GetError()));
165 }
166 else if (!(PR_POLL_HUP & pollfds[i].out_flags)) {
167 NS_ERROR(PR_ErrorToName(PR_GetError()));
168 }
170 if (0 < len) {
171 buf[len] = '\0';
172 if (isStdout)
173 mStdout += buf;
174 else
175 mStderr += buf;
176 }
177 else if (isStdout) {
178 stdoutOpen = false;
179 }
180 else {
181 stderrOpen = false;
182 }
183 }
185 now = PR_IntervalNow();
186 }
188 if (stdoutOpen)
189 fputs("(stdout still open!)\n", stderr);
190 if (stderrOpen)
191 fputs("(stderr still open!)\n", stderr);
192 if (now > deadline)
193 fputs("(timed out!)\n", stderr);
195 Finish(!stdoutOpen && !stderrOpen && now <= deadline);
196 }
198 private:
199 void Finish(bool normalExit) {
200 if (!normalExit) {
201 PR_KillProcess(mProc);
202 mExitCode = -1;
203 int32_t dummy;
204 PR_WaitProcess(mProc, &dummy);
205 }
206 else {
207 PR_WaitProcess(mProc, &mExitCode); // this had better not block ...
208 }
210 PR_Close(mStdoutfd);
211 PR_Close(mStderrfd);
212 }
214 PRProcess* mProc;
215 PRFileDesc* mStdinfd; // writeable
216 PRFileDesc* mStdoutfd; // readable
217 PRFileDesc* mStderrfd; // readable
218 };
220 //-----------------------------------------------------------------------------
221 // Harness for checking detector errors
222 bool
223 CheckForDeadlock(const char* test, const char* const* findTokens)
224 {
225 Subprocess proc(test);
226 proc.RunToCompletion(5000);
228 if (0 == proc.mExitCode)
229 return false;
231 int32_t idx = 0;
232 for (const char* const* tp = findTokens; *tp; ++tp) {
233 const char* const token = *tp;
234 #ifdef MOZILLA_INTERNAL_API
235 idx = proc.mStderr.Find(token, false, idx);
236 #else
237 nsCString tokenCString(token);
238 idx = proc.mStderr.Find(tokenCString, idx);
239 #endif
240 if (-1 == idx) {
241 printf("(missed token '%s' in output)\n", token);
242 puts("----------------------------------\n");
243 puts(proc.mStderr.get());
244 puts("----------------------------------\n");
245 return false;
246 }
247 idx += strlen(token);
248 }
250 return true;
251 }
253 //-----------------------------------------------------------------------------
254 // Single-threaded sanity tests
256 // Stupidest possible deadlock.
257 int
258 Sanity_Child()
259 {
260 mozilla::Mutex m1("dd.sanity.m1");
261 m1.Lock();
262 m1.Lock();
263 return 0; // not reached
264 }
266 nsresult
267 Sanity()
268 {
269 const char* const tokens[] = {
270 "###!!! ERROR: Potential deadlock detected",
271 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity.m1",
272 "=== Cycle completed at\n--- Mutex : dd.sanity.m1",
273 "###!!! Deadlock may happen NOW!", // better catch these easy cases...
274 "###!!! ASSERTION: Potential deadlock detected",
275 0
276 };
277 if (CheckForDeadlock("Sanity", tokens)) {
278 PASS();
279 } else {
280 FAIL("deadlock not detected");
281 }
282 }
284 // Slightly less stupid deadlock.
285 int
286 Sanity2_Child()
287 {
288 mozilla::Mutex m1("dd.sanity2.m1");
289 mozilla::Mutex m2("dd.sanity2.m2");
290 m1.Lock();
291 m2.Lock();
292 m1.Lock();
293 return 0; // not reached
294 }
296 nsresult
297 Sanity2()
298 {
299 const char* const tokens[] = {
300 "###!!! ERROR: Potential deadlock detected",
301 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity2.m1",
302 "--- Next dependency:\n--- Mutex : dd.sanity2.m2",
303 "=== Cycle completed at\n--- Mutex : dd.sanity2.m1",
304 "###!!! Deadlock may happen NOW!", // better catch these easy cases...
305 "###!!! ASSERTION: Potential deadlock detected",
306 0
307 };
308 if (CheckForDeadlock("Sanity2", tokens)) {
309 PASS();
310 } else {
311 FAIL("deadlock not detected");
312 }
313 }
316 int
317 Sanity3_Child()
318 {
319 mozilla::Mutex m1("dd.sanity3.m1");
320 mozilla::Mutex m2("dd.sanity3.m2");
321 mozilla::Mutex m3("dd.sanity3.m3");
322 mozilla::Mutex m4("dd.sanity3.m4");
324 m1.Lock();
325 m2.Lock();
326 m3.Lock();
327 m4.Lock();
328 m4.Unlock();
329 m3.Unlock();
330 m2.Unlock();
331 m1.Unlock();
333 m4.Lock();
334 m1.Lock();
335 return 0;
336 }
338 nsresult
339 Sanity3()
340 {
341 const char* const tokens[] = {
342 "###!!! ERROR: Potential deadlock detected",
343 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity3.m1",
344 "--- Next dependency:\n--- Mutex : dd.sanity3.m2",
345 "--- Next dependency:\n--- Mutex : dd.sanity3.m3",
346 "--- Next dependency:\n--- Mutex : dd.sanity3.m4",
347 "=== Cycle completed at\n--- Mutex : dd.sanity3.m1",
348 "###!!! ASSERTION: Potential deadlock detected",
349 0
350 };
351 if (CheckForDeadlock("Sanity3", tokens)) {
352 PASS();
353 } else {
354 FAIL("deadlock not detected");
355 }
356 }
359 int
360 Sanity4_Child()
361 {
362 mozilla::ReentrantMonitor m1("dd.sanity4.m1");
363 mozilla::Mutex m2("dd.sanity4.m2");
364 m1.Enter();
365 m2.Lock();
366 m1.Enter();
367 return 0;
368 }
370 nsresult
371 Sanity4()
372 {
373 const char* const tokens[] = {
374 "Re-entering ReentrantMonitor after acquiring other resources",
375 "###!!! ERROR: Potential deadlock detected",
376 "=== Cyclical dependency starts at\n--- ReentrantMonitor : dd.sanity4.m1",
377 "--- Next dependency:\n--- Mutex : dd.sanity4.m2",
378 "=== Cycle completed at\n--- ReentrantMonitor : dd.sanity4.m1",
379 "###!!! ASSERTION: Potential deadlock detected",
380 0
381 };
382 if (CheckForDeadlock("Sanity4", tokens)) {
383 PASS();
384 } else {
385 FAIL("deadlock not detected");
386 }
387 }
389 //-----------------------------------------------------------------------------
390 // Multithreaded tests
392 mozilla::Mutex* ttM1;
393 mozilla::Mutex* ttM2;
395 static void
396 TwoThreads_thread(void* arg)
397 {
398 int32_t m1First = NS_PTR_TO_INT32(arg);
399 if (m1First) {
400 ttM1->Lock();
401 ttM2->Lock();
402 ttM2->Unlock();
403 ttM1->Unlock();
404 }
405 else {
406 ttM2->Lock();
407 ttM1->Lock();
408 ttM1->Unlock();
409 ttM2->Unlock();
410 }
411 }
413 int
414 TwoThreads_Child()
415 {
416 ttM1 = new mozilla::Mutex("dd.twothreads.m1");
417 ttM2 = new mozilla::Mutex("dd.twothreads.m2");
418 if (!ttM1 || !ttM2)
419 NS_RUNTIMEABORT("couldn't allocate mutexes");
421 PRThread* t1 = spawn(TwoThreads_thread, (void*) 0);
422 PR_JoinThread(t1);
424 PRThread* t2 = spawn(TwoThreads_thread, (void*) 1);
425 PR_JoinThread(t2);
427 return 0;
428 }
430 nsresult
431 TwoThreads()
432 {
433 const char* const tokens[] = {
434 "###!!! ERROR: Potential deadlock detected",
435 "=== Cyclical dependency starts at\n--- Mutex : dd.twothreads.m2",
436 "--- Next dependency:\n--- Mutex : dd.twothreads.m1",
437 "=== Cycle completed at\n--- Mutex : dd.twothreads.m2",
438 "###!!! ASSERTION: Potential deadlock detected",
439 0
440 };
442 if (CheckForDeadlock("TwoThreads", tokens)) {
443 PASS();
444 } else {
445 FAIL("deadlock not detected");
446 }
447 }
450 mozilla::Mutex* cndMs[4];
451 const uint32_t K = 100000;
453 static void
454 ContentionNoDeadlock_thread(void* arg)
455 {
456 int32_t starti = NS_PTR_TO_INT32(arg);
458 for (uint32_t k = 0; k < K; ++k) {
459 for (int32_t i = starti; i < (int32_t) ArrayLength(cndMs); ++i)
460 cndMs[i]->Lock();
461 // comment out the next two lines for deadlocking fun!
462 for (int32_t i = ArrayLength(cndMs) - 1; i >= starti; --i)
463 cndMs[i]->Unlock();
465 starti = (starti + 1) % 3;
466 }
467 }
469 int
470 ContentionNoDeadlock_Child()
471 {
472 PRThread* threads[3];
474 for (uint32_t i = 0; i < ArrayLength(cndMs); ++i)
475 cndMs[i] = new mozilla::Mutex("dd.cnd.ms");
477 for (int32_t i = 0; i < (int32_t) ArrayLength(threads); ++i)
478 threads[i] = spawn(ContentionNoDeadlock_thread, NS_INT32_TO_PTR(i));
480 for (uint32_t i = 0; i < ArrayLength(threads); ++i)
481 PR_JoinThread(threads[i]);
483 for (uint32_t i = 0; i < ArrayLength(cndMs); ++i)
484 delete cndMs[i];
486 return 0;
487 }
489 nsresult
490 ContentionNoDeadlock()
491 {
492 const char * func = __func__;
493 Subprocess proc(func);
494 proc.RunToCompletion(60000);
495 if (0 != proc.mExitCode) {
496 printf("(expected 0 == return code, got %d)\n", proc.mExitCode);
497 puts("(output)\n----------------------------------\n");
498 puts(proc.mStdout.get());
499 puts("----------------------------------\n");
500 puts("(error output)\n----------------------------------\n");
501 puts(proc.mStderr.get());
502 puts("----------------------------------\n");
504 FAIL("deadlock");
505 }
506 PASS();
507 }
511 //-----------------------------------------------------------------------------
513 int
514 main(int argc, char** argv)
515 {
516 if (1 < argc) {
517 // XXX can we run w/o scoped XPCOM?
518 const char* test = argv[1];
519 ScopedXPCOM xpcom(test);
520 if (xpcom.failed())
521 return 1;
523 // running in a spawned process. call the specificed child function.
524 if (!strcmp("Sanity", test))
525 return Sanity_Child();
526 if (!strcmp("Sanity2", test))
527 return Sanity2_Child();
528 if (!strcmp("Sanity3", test))
529 return Sanity3_Child();
530 if (!strcmp("Sanity4", test))
531 return Sanity4_Child();
533 if (!strcmp("TwoThreads", test))
534 return TwoThreads_Child();
535 if (!strcmp("ContentionNoDeadlock", test))
536 return ContentionNoDeadlock_Child();
538 fail("%s | %s - unknown child test", __FILE__, __FUNCTION__);
539 return 2;
540 }
542 ScopedXPCOM xpcom("XPCOM deadlock detector correctness (" __FILE__ ")");
543 if (xpcom.failed())
544 return 1;
546 // in the first invocation of this process. we will be the "driver".
547 int rv = 0;
549 sPathToThisBinary = argv[0];
551 if (NS_FAILED(Sanity()))
552 rv = 1;
553 if (NS_FAILED(Sanity2()))
554 rv = 1;
555 if (NS_FAILED(Sanity3()))
556 rv = 1;
557 if (NS_FAILED(Sanity4()))
558 rv = 1;
560 if (NS_FAILED(TwoThreads()))
561 rv = 1;
562 if (NS_FAILED(ContentionNoDeadlock()))
563 rv = 1;
565 return rv;
566 }