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=99:
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 "vm/CodeCoverage.h"
8 :
9 : #include "mozilla/Atomics.h"
10 : #include "mozilla/IntegerPrintfMacros.h"
11 : #include "mozilla/Move.h"
12 :
13 : #include <stdio.h>
14 : #ifdef XP_WIN
15 : # include <process.h>
16 : # define getpid _getpid
17 : #else
18 : # include <unistd.h>
19 : #endif
20 :
21 : #include "util/Text.h"
22 : #include "vm/BytecodeUtil.h"
23 : #include "vm/JSScript.h"
24 : #include "vm/Realm.h"
25 : #include "vm/Runtime.h"
26 : #include "vm/Time.h"
27 :
28 : // This file contains a few functions which are used to produce files understood
29 : // by lcov tools. A detailed description of the format is available in the man
30 : // page for "geninfo" [1]. To make it short, the following paraphrases what is
31 : // commented in the man page by using curly braces prefixed by for-each to
32 : // express repeated patterns.
33 : //
34 : // TN:<compartment name>
35 : // for-each <source file> {
36 : // SN:<filename>
37 : // for-each <script> {
38 : // FN:<line>,<name>
39 : // }
40 : // for-each <script> {
41 : // FNDA:<hits>,<name>
42 : // }
43 : // FNF:<number of scripts>
44 : // FNH:<sum of scripts hits>
45 : // for-each <script> {
46 : // for-each <branch> {
47 : // BRDA:<line>,<block id>,<target id>,<taken>
48 : // }
49 : // }
50 : // BRF:<number of branches>
51 : // BRH:<sum of branches hits>
52 : // for-each <script> {
53 : // for-each <line> {
54 : // DA:<line>,<hits>
55 : // }
56 : // }
57 : // LF:<number of lines>
58 : // LH:<sum of lines hits>
59 : // }
60 : //
61 : // [1] http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
62 : //
63 : namespace js {
64 : namespace coverage {
65 :
66 0 : LCovSource::LCovSource(LifoAlloc* alloc, UniqueChars name)
67 0 : : name_(std::move(name)),
68 : outFN_(alloc),
69 : outFNDA_(alloc),
70 : numFunctionsFound_(0),
71 : numFunctionsHit_(0),
72 : outBRDA_(alloc),
73 : numBranchesFound_(0),
74 : numBranchesHit_(0),
75 : numLinesInstrumented_(0),
76 : numLinesHit_(0),
77 : maxLineHit_(0),
78 0 : hasTopLevelScript_(false)
79 : {
80 0 : }
81 :
82 0 : LCovSource::LCovSource(LCovSource&& src)
83 0 : : name_(std::move(src.name_)),
84 : outFN_(src.outFN_),
85 : outFNDA_(src.outFNDA_),
86 0 : numFunctionsFound_(src.numFunctionsFound_),
87 0 : numFunctionsHit_(src.numFunctionsHit_),
88 : outBRDA_(src.outBRDA_),
89 0 : numBranchesFound_(src.numBranchesFound_),
90 0 : numBranchesHit_(src.numBranchesHit_),
91 0 : linesHit_(std::move(src.linesHit_)),
92 0 : numLinesInstrumented_(src.numLinesInstrumented_),
93 0 : numLinesHit_(src.numLinesHit_),
94 0 : maxLineHit_(src.maxLineHit_),
95 0 : hasTopLevelScript_(src.hasTopLevelScript_)
96 : {
97 0 : }
98 :
99 : void
100 0 : LCovSource::exportInto(GenericPrinter& out) const
101 : {
102 : // Only write if everything got recorded.
103 0 : if (!hasTopLevelScript_)
104 : return;
105 :
106 0 : out.printf("SF:%s\n", name_.get());
107 :
108 0 : outFN_.exportInto(out);
109 0 : outFNDA_.exportInto(out);
110 0 : out.printf("FNF:%zu\n", numFunctionsFound_);
111 0 : out.printf("FNH:%zu\n", numFunctionsHit_);
112 :
113 0 : outBRDA_.exportInto(out);
114 0 : out.printf("BRF:%zu\n", numBranchesFound_);
115 0 : out.printf("BRH:%zu\n", numBranchesHit_);
116 :
117 0 : if (linesHit_.initialized()) {
118 0 : for (size_t lineno = 1; lineno <= maxLineHit_; ++lineno) {
119 0 : if (auto p = linesHit_.lookup(lineno))
120 0 : out.printf("DA:%zu,%" PRIu64 "\n", lineno, p->value());
121 : }
122 : }
123 :
124 0 : out.printf("LF:%zu\n", numLinesInstrumented_);
125 0 : out.printf("LH:%zu\n", numLinesHit_);
126 :
127 0 : out.put("end_of_record\n");
128 : }
129 :
130 : bool
131 0 : LCovSource::writeScriptName(LSprinter& out, JSScript* script)
132 : {
133 0 : JSFunction* fun = script->functionNonDelazifying();
134 0 : if (fun && fun->displayAtom())
135 0 : return EscapedStringPrinter(out, fun->displayAtom(), 0);
136 0 : out.printf("top-level");
137 0 : return true;
138 : }
139 :
140 : bool
141 0 : LCovSource::writeScript(JSScript* script)
142 : {
143 0 : if (!linesHit_.initialized() && !linesHit_.init())
144 : return false;
145 :
146 0 : numFunctionsFound_++;
147 0 : outFN_.printf("FN:%u,", script->lineno());
148 0 : if (!writeScriptName(outFN_, script))
149 : return false;
150 0 : outFN_.put("\n", 1);
151 :
152 0 : uint64_t hits = 0;
153 0 : ScriptCounts* sc = nullptr;
154 0 : if (script->hasScriptCounts()) {
155 0 : sc = &script->getScriptCounts();
156 0 : numFunctionsHit_++;
157 0 : const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(script->main()));
158 0 : outFNDA_.printf("FNDA:%" PRIu64 ",", counts->numExec());
159 0 : if (!writeScriptName(outFNDA_, script))
160 : return false;
161 0 : outFNDA_.put("\n", 1);
162 :
163 : // Set the hit count of the pre-main code to 1, if the function ever got
164 : // visited.
165 0 : hits = 1;
166 : }
167 :
168 0 : jsbytecode* snpc = script->code();
169 0 : jssrcnote* sn = script->notes();
170 0 : if (!SN_IS_TERMINATOR(sn))
171 0 : snpc += SN_DELTA(sn);
172 :
173 0 : size_t lineno = script->lineno();
174 0 : jsbytecode* end = script->codeEnd();
175 0 : size_t branchId = 0;
176 0 : size_t tableswitchExitOffset = 0;
177 0 : bool firstLineHasBeenWritten = false;
178 0 : for (jsbytecode* pc = script->code(); pc != end; pc = GetNextPc(pc)) {
179 0 : MOZ_ASSERT(script->code() <= pc && pc < end);
180 0 : JSOp op = JSOp(*pc);
181 0 : bool jump = IsJumpOpcode(op) || op == JSOP_TABLESWITCH;
182 0 : bool fallsthrough = BytecodeFallsThrough(op) && op != JSOP_GOSUB;
183 :
184 : // If the current script & pc has a hit-count report, then update the
185 : // current number of hits.
186 0 : if (sc) {
187 0 : const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(pc));
188 0 : if (counts)
189 0 : hits = counts->numExec();
190 : }
191 :
192 : // If we have additional source notes, walk all the source notes of the
193 : // current pc.
194 0 : if (snpc <= pc || !firstLineHasBeenWritten) {
195 0 : size_t oldLine = lineno;
196 0 : while (!SN_IS_TERMINATOR(sn) && snpc <= pc) {
197 0 : SrcNoteType type = SN_TYPE(sn);
198 0 : if (type == SRC_SETLINE)
199 0 : lineno = size_t(GetSrcNoteOffset(sn, 0));
200 0 : else if (type == SRC_NEWLINE)
201 0 : lineno++;
202 0 : else if (type == SRC_TABLESWITCH)
203 0 : tableswitchExitOffset = GetSrcNoteOffset(sn, 0);
204 :
205 0 : sn = SN_NEXT(sn);
206 0 : snpc += SN_DELTA(sn);
207 : }
208 :
209 0 : if ((oldLine != lineno || !firstLineHasBeenWritten) &&
210 0 : pc >= script->main() &&
211 : fallsthrough)
212 : {
213 0 : auto p = linesHit_.lookupForAdd(lineno);
214 0 : if (!p) {
215 0 : if (!linesHit_.add(p, lineno, hits))
216 0 : return false;
217 0 : numLinesInstrumented_++;
218 0 : if (hits != 0)
219 0 : numLinesHit_++;
220 0 : maxLineHit_ = std::max(lineno, maxLineHit_);
221 : } else {
222 0 : if (p->value() == 0 && hits != 0)
223 0 : numLinesHit_++;
224 0 : p->value() += hits;
225 : }
226 :
227 0 : firstLineHasBeenWritten = true;
228 : }
229 : }
230 :
231 : // If the current instruction has thrown, then decrement the hit counts
232 : // with the number of throws.
233 0 : if (sc) {
234 0 : const PCCounts* counts = sc->maybeGetThrowCounts(script->pcToOffset(pc));
235 0 : if (counts)
236 0 : hits -= counts->numExec();
237 : }
238 :
239 : // If the current pc corresponds to a conditional jump instruction, then reports
240 : // branch hits.
241 0 : if (jump && fallsthrough) {
242 0 : jsbytecode* fallthroughTarget = GetNextPc(pc);
243 0 : uint64_t fallthroughHits = 0;
244 0 : if (sc) {
245 0 : const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(fallthroughTarget));
246 0 : if (counts)
247 0 : fallthroughHits = counts->numExec();
248 : }
249 :
250 0 : uint64_t taken = hits - fallthroughHits;
251 0 : outBRDA_.printf("BRDA:%zu,%zu,0,", lineno, branchId);
252 0 : if (hits)
253 0 : outBRDA_.printf("%" PRIu64 "\n", taken);
254 : else
255 0 : outBRDA_.put("-\n", 2);
256 :
257 0 : outBRDA_.printf("BRDA:%zu,%zu,1,", lineno, branchId);
258 0 : if (hits)
259 0 : outBRDA_.printf("%" PRIu64 "\n", fallthroughHits);
260 : else
261 0 : outBRDA_.put("-\n", 2);
262 :
263 : // Count the number of branches, and the number of branches hit.
264 0 : numBranchesFound_ += 2;
265 0 : if (hits)
266 0 : numBranchesHit_ += !!taken + !!fallthroughHits;
267 0 : branchId++;
268 : }
269 :
270 : // If the current pc corresponds to a pre-computed switch case, then
271 : // reports branch hits for each case statement.
272 0 : if (jump && op == JSOP_TABLESWITCH) {
273 0 : MOZ_ASSERT(tableswitchExitOffset != 0);
274 :
275 : // Get the default and exit pc
276 0 : jsbytecode* exitpc = pc + tableswitchExitOffset;
277 0 : jsbytecode* defaultpc = pc + GET_JUMP_OFFSET(pc);
278 0 : MOZ_ASSERT(script->code() <= exitpc && exitpc <= end);
279 0 : MOZ_ASSERT(script->code() <= defaultpc && defaultpc < end);
280 0 : MOZ_ASSERT(defaultpc > pc && defaultpc <= exitpc);
281 :
282 : // Get the low and high from the tableswitch
283 0 : int32_t low = GET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN * 1);
284 0 : int32_t high = GET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN * 2);
285 0 : MOZ_ASSERT(high - low + 1 >= 0);
286 0 : size_t numCases = high - low + 1;
287 0 : jsbytecode* jumpTable = pc + JUMP_OFFSET_LEN * 3;
288 :
289 0 : jsbytecode* firstcasepc = exitpc;
290 0 : for (size_t j = 0; j < numCases; j++) {
291 0 : jsbytecode* testpc = pc + GET_JUMP_OFFSET(jumpTable + JUMP_OFFSET_LEN * j);
292 0 : MOZ_ASSERT(script->code() <= testpc && testpc < end);
293 0 : if (testpc < firstcasepc)
294 0 : firstcasepc = testpc;
295 : }
296 :
297 : // Count the number of hits of the default branch, by subtracting
298 : // the number of hits of each cases.
299 0 : uint64_t defaultHits = hits;
300 :
301 : // Count the number of hits of the previous case entry.
302 0 : uint64_t fallsThroughHits = 0;
303 :
304 : // Record branches for each cases.
305 0 : size_t caseId = 0;
306 0 : for (size_t i = 0; i < numCases; i++) {
307 0 : jsbytecode* casepc = pc + GET_JUMP_OFFSET(jumpTable + JUMP_OFFSET_LEN * i);
308 0 : MOZ_ASSERT(script->code() <= casepc && casepc < end);
309 : // The case is not present, and jumps to the default pc if used.
310 0 : if (casepc == pc)
311 : continue;
312 :
313 : // PCs might not be in increasing order of case indexes.
314 0 : jsbytecode* lastcasepc = firstcasepc - 1;
315 0 : bool foundLastCase = false;
316 0 : for (size_t j = 0; j < numCases; j++) {
317 0 : jsbytecode* testpc = pc + GET_JUMP_OFFSET(jumpTable + JUMP_OFFSET_LEN * j);
318 0 : MOZ_ASSERT(script->code() <= testpc && testpc < end);
319 0 : if (lastcasepc < testpc && (testpc < casepc || (j < i && testpc == casepc))) {
320 0 : lastcasepc = testpc;
321 0 : foundLastCase = true;
322 : }
323 : }
324 :
325 : // If multiple case instruction have the same code block, only
326 : // register the code coverage the first time we hit this case.
327 0 : if (!foundLastCase || casepc != lastcasepc) {
328 : // Case (i + low)
329 0 : uint64_t caseHits = 0;
330 0 : if (sc) {
331 0 : const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(casepc));
332 0 : if (counts)
333 0 : caseHits = counts->numExec();
334 :
335 : // Remove fallthrough.
336 0 : fallsThroughHits = 0;
337 0 : if (foundLastCase) {
338 : // Walk from the previous case to the current one to
339 : // check if it fallthrough into the current block.
340 0 : MOZ_ASSERT(lastcasepc != firstcasepc - 1);
341 : jsbytecode* endpc = lastcasepc;
342 0 : while (GetNextPc(endpc) < casepc) {
343 0 : endpc = GetNextPc(endpc);
344 0 : MOZ_ASSERT(script->code() <= endpc && endpc < end);
345 : }
346 :
347 0 : if (BytecodeFallsThrough(JSOp(*endpc)))
348 0 : fallsThroughHits = script->getHitCount(endpc);
349 : }
350 :
351 0 : caseHits -= fallsThroughHits;
352 : }
353 :
354 0 : outBRDA_.printf("BRDA:%zu,%zu,%zu,",
355 0 : lineno, branchId, caseId);
356 0 : if (hits)
357 0 : outBRDA_.printf("%" PRIu64 "\n", caseHits);
358 : else
359 0 : outBRDA_.put("-\n", 2);
360 :
361 0 : numBranchesFound_++;
362 0 : numBranchesHit_ += !!caseHits;
363 0 : defaultHits -= caseHits;
364 0 : caseId++;
365 : }
366 : }
367 :
368 : // Compute the number of hits of the default branch, if it has its
369 : // own case clause.
370 0 : bool defaultHasOwnClause = true;
371 0 : if (defaultpc != exitpc) {
372 0 : defaultHits = 0;
373 :
374 : // Look for the last case entry before the default pc.
375 0 : jsbytecode* lastcasepc = firstcasepc - 1;
376 0 : bool foundLastCase = false;
377 0 : for (size_t j = 0; j < numCases; j++) {
378 0 : jsbytecode* testpc = pc + GET_JUMP_OFFSET(jumpTable + JUMP_OFFSET_LEN * j);
379 0 : MOZ_ASSERT(script->code() <= testpc && testpc < end);
380 0 : if (lastcasepc < testpc && testpc <= defaultpc) {
381 0 : lastcasepc = testpc;
382 0 : foundLastCase = true;
383 : }
384 : }
385 :
386 : // Set defaultHasOwnClause to false, if one of the case
387 : // statement has the same pc as the default block. Which implies
388 : // that the previous loop already encoded the coverage
389 : // information for the current block.
390 0 : if (foundLastCase && lastcasepc == defaultpc)
391 0 : defaultHasOwnClause = false;
392 :
393 : // Look if the last case entry fallthrough to the default case,
394 : // in which case we have to remove the number of fallthrough
395 : // hits out of the default case hits.
396 0 : if (sc && foundLastCase) {
397 : // Walk from the previous case to the current one to check
398 : // if it fallthrough into the default block.
399 0 : MOZ_ASSERT(lastcasepc != firstcasepc - 1);
400 : jsbytecode* endpc = lastcasepc;
401 0 : while (GetNextPc(endpc) < defaultpc) {
402 0 : endpc = GetNextPc(endpc);
403 0 : MOZ_ASSERT(script->code() <= endpc && endpc < end);
404 : }
405 :
406 0 : if (BytecodeFallsThrough(JSOp(*endpc)))
407 0 : fallsThroughHits = script->getHitCount(endpc);
408 : }
409 :
410 0 : if (sc) {
411 0 : const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(defaultpc));
412 0 : if (counts)
413 0 : defaultHits = counts->numExec();
414 : }
415 0 : defaultHits -= fallsThroughHits;
416 : }
417 :
418 0 : if (defaultHasOwnClause) {
419 0 : outBRDA_.printf("BRDA:%zu,%zu,%zu,",
420 0 : lineno, branchId, caseId);
421 0 : if (hits)
422 0 : outBRDA_.printf("%" PRIu64 "\n", defaultHits);
423 : else
424 0 : outBRDA_.put("-\n", 2);
425 0 : numBranchesFound_++;
426 0 : numBranchesHit_ += !!defaultHits;
427 : }
428 :
429 : // Increment the branch identifier, and go to the next instruction.
430 0 : branchId++;
431 0 : tableswitchExitOffset = 0;
432 : }
433 : }
434 :
435 : // Report any new OOM.
436 0 : if (outFN_.hadOutOfMemory() ||
437 0 : outFNDA_.hadOutOfMemory() ||
438 0 : outBRDA_.hadOutOfMemory())
439 : {
440 : return false;
441 : }
442 :
443 : // If this script is the top-level script, then record it such that we can
444 : // assume that the code coverage report is complete, as this script has
445 : // references on all inner scripts.
446 0 : if (script->isTopLevel())
447 0 : hasTopLevelScript_ = true;
448 :
449 : return true;
450 : }
451 :
452 0 : LCovRealm::LCovRealm()
453 : : alloc_(4096),
454 : outTN_(&alloc_),
455 47 : sources_(nullptr)
456 : {
457 94 : MOZ_ASSERT(alloc_.isEmpty());
458 0 : }
459 :
460 : void
461 0 : LCovRealm::collectCodeCoverageInfo(JS::Realm* realm, JSScript* script, const char* name)
462 : {
463 : // Skip any operation if we already some out-of memory issues.
464 0 : if (outTN_.hadOutOfMemory())
465 : return;
466 :
467 0 : if (!script->code())
468 : return;
469 :
470 : // Get the existing source LCov summary, or create a new one.
471 0 : LCovSource* source = lookupOrAdd(realm, name);
472 0 : if (!source)
473 : return;
474 :
475 : // Write code coverage data into the LCovSource.
476 0 : if (!source->writeScript(script)) {
477 0 : outTN_.reportOutOfMemory();
478 0 : return;
479 : }
480 : }
481 :
482 : LCovSource*
483 0 : LCovRealm::lookupOrAdd(JS::Realm* realm, const char* name)
484 : {
485 : // On the first call, write the realm name, and allocate a LCovSource
486 : // vector in the LifoAlloc.
487 0 : if (!sources_) {
488 0 : if (!writeRealmName(realm))
489 : return nullptr;
490 :
491 0 : LCovSourceVector* raw = alloc_.pod_malloc<LCovSourceVector>();
492 0 : if (!raw) {
493 0 : outTN_.reportOutOfMemory();
494 0 : return nullptr;
495 : }
496 :
497 0 : sources_ = new(raw) LCovSourceVector(alloc_);
498 : } else {
499 : // Find the first matching source.
500 0 : for (LCovSource& source : *sources_) {
501 0 : if (source.match(name))
502 : return &source;
503 : }
504 : }
505 :
506 0 : UniqueChars source_name = DuplicateString(name);
507 0 : if (!source_name) {
508 0 : outTN_.reportOutOfMemory();
509 0 : return nullptr;
510 : }
511 :
512 : // Allocate a new LCovSource for the current top-level.
513 0 : if (!sources_->emplaceBack(&alloc_, std::move(source_name))) {
514 0 : outTN_.reportOutOfMemory();
515 0 : return nullptr;
516 : }
517 :
518 0 : return &sources_->back();
519 : }
520 :
521 : void
522 5 : LCovRealm::exportInto(GenericPrinter& out, bool* isEmpty) const
523 : {
524 0 : if (!sources_ || outTN_.hadOutOfMemory())
525 : return;
526 :
527 : // If we only have cloned function, then do not serialize anything.
528 0 : bool someComplete = false;
529 0 : for (const LCovSource& sc : *sources_) {
530 0 : if (sc.isComplete()) {
531 : someComplete = true;
532 : break;
533 : };
534 : }
535 :
536 0 : if (!someComplete)
537 : return;
538 :
539 0 : *isEmpty = false;
540 0 : outTN_.exportInto(out);
541 0 : for (const LCovSource& sc : *sources_) {
542 0 : if (sc.isComplete())
543 0 : sc.exportInto(out);
544 : }
545 : }
546 :
547 : bool
548 0 : LCovRealm::writeRealmName(JS::Realm* realm)
549 : {
550 0 : JSContext* cx = TlsContext.get();
551 :
552 : // lcov trace files are starting with an optional test case name, that we
553 : // recycle to be a realm name.
554 : //
555 : // Note: The test case name has some constraint in terms of valid character,
556 : // thus we escape invalid chracters with a "_" symbol in front of its
557 : // hexadecimal code.
558 0 : outTN_.put("TN:");
559 0 : if (cx->runtime()->realmNameCallback) {
560 : char name[1024];
561 : {
562 : // Hazard analysis cannot tell that the callback does not GC.
563 0 : JS::AutoSuppressGCAnalysis nogc;
564 0 : Rooted<Realm*> rootedRealm(cx, realm);
565 0 : (*cx->runtime()->realmNameCallback)(cx, rootedRealm, name, sizeof(name));
566 : }
567 0 : for (char *s = name; s < name + sizeof(name) && *s; s++) {
568 0 : if (('a' <= *s && *s <= 'z') ||
569 0 : ('A' <= *s && *s <= 'Z') ||
570 0 : ('0' <= *s && *s <= '9'))
571 : {
572 0 : outTN_.put(s, 1);
573 0 : continue;
574 : }
575 0 : outTN_.printf("_%p", (void*) size_t(*s));
576 : }
577 0 : outTN_.put("\n", 1);
578 : } else {
579 0 : outTN_.printf("Realm_%p%p\n", (void*) size_t('_'), realm);
580 : }
581 :
582 0 : return !outTN_.hadOutOfMemory();
583 : }
584 :
585 0 : LCovRuntime::LCovRuntime()
586 : : out_(),
587 4 : pid_(getpid()),
588 0 : isEmpty_(false)
589 : {
590 4 : }
591 :
592 0 : LCovRuntime::~LCovRuntime()
593 : {
594 0 : if (out_.isInitialized())
595 0 : finishFile();
596 0 : }
597 :
598 : bool
599 4 : LCovRuntime::fillWithFilename(char *name, size_t length)
600 : {
601 0 : const char* outDir = getenv("JS_CODE_COVERAGE_OUTPUT_DIR");
602 0 : if (!outDir || *outDir == 0)
603 : return false;
604 :
605 0 : int64_t timestamp = static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_SEC;
606 : static mozilla::Atomic<size_t> globalRuntimeId(0);
607 0 : size_t rid = globalRuntimeId++;
608 :
609 4 : int len = snprintf(name, length, "%s/%" PRId64 "-%" PRIu32 "-%zu.info",
610 4 : outDir, timestamp, pid_, rid);
611 0 : if (len < 0 || size_t(len) >= length) {
612 0 : fprintf(stderr, "Warning: LCovRuntime::init: Cannot serialize file name.");
613 0 : return false;
614 : }
615 :
616 : return true;
617 : }
618 :
619 : void
620 4 : LCovRuntime::init()
621 : {
622 : char name[1024];
623 4 : if (!fillWithFilename(name, sizeof(name)))
624 0 : return;
625 :
626 : // If we cannot open the file, report a warning.
627 4 : if (!out_.init(name))
628 0 : fprintf(stderr, "Warning: LCovRuntime::init: Cannot open file named '%s'.", name);
629 0 : isEmpty_ = true;
630 : }
631 :
632 : void
633 0 : LCovRuntime::finishFile()
634 : {
635 0 : MOZ_ASSERT(out_.isInitialized());
636 0 : out_.finish();
637 :
638 0 : if (isEmpty_) {
639 : char name[1024];
640 0 : if (!fillWithFilename(name, sizeof(name)))
641 0 : return;
642 0 : remove(name);
643 : }
644 : }
645 :
646 : void
647 1 : LCovRuntime::writeLCovResult(LCovRealm& realm)
648 : {
649 5 : if (!out_.isInitialized())
650 : return;
651 :
652 5 : uint32_t p = getpid();
653 0 : if (pid_ != p) {
654 0 : pid_ = p;
655 0 : finishFile();
656 0 : init();
657 0 : if (!out_.isInitialized())
658 : return;
659 : }
660 :
661 1 : realm.exportInto(out_, &isEmpty_);
662 1 : out_.flush();
663 : }
664 :
665 : } // namespace coverage
666 : } // namespace js
|