1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef vm_CodeCoverage_h
#define vm_CodeCoverage_h
#include "mozilla/Vector.h"
#include "ds/LifoAlloc.h"
#include "js/HashTable.h"
#include "js/TypeDecls.h"
#include "js/Utility.h"
#include "vm/Printer.h"
namespace js {
class ScriptSourceObject;
namespace coverage {
class LCovSource {
public:
LCovSource(LifoAlloc* alloc, JS::UniqueChars name);
LCovSource(LCovSource&& src);
~LCovSource() = default;
// Whether the given script name matches this LCovSource.
bool match(const char* name) const { return strcmp(name_.get(), name) == 0; }
// Whether the current source is complete and if it can be flushed.
bool isComplete() const { return hasTopLevelScript_; }
// Iterate over the bytecode and collect the lcov output based on the
// ScriptCounts counters.
bool writeScript(JSScript* script);
// Write the Lcov output in a buffer, such as the one associated with
// the runtime code coverage trace file.
void exportInto(GenericPrinter& out);
private:
// Write the script name in out.
bool writeScriptName(LSprinter& out, JSScript* script);
private:
// Name of the source file.
JS::UniqueChars name_;
// LifoAlloc strings which hold the filename of each function as
// well as the number of hits for each function.
LSprinter outFN_;
LSprinter outFNDA_;
size_t numFunctionsFound_;
size_t numFunctionsHit_;
// LifoAlloc string which hold branches statistics.
LSprinter outBRDA_;
size_t numBranchesFound_;
size_t numBranchesHit_;
// Holds lines statistics. When processing a line hit count, the hit count
// is added to any hit count already in the hash map so that we handle
// lines that belong to more than one JSScript or function in the same
// source file.
HashMap<size_t, uint64_t, DefaultHasher<size_t>, SystemAllocPolicy> linesHit_;
size_t numLinesInstrumented_;
size_t numLinesHit_;
size_t maxLineHit_;
// Status flags.
bool hasTopLevelScript_ : 1;
};
class LCovRealm {
public:
LCovRealm();
~LCovRealm();
// Collect code coverage information for the given source.
void collectCodeCoverageInfo(JS::Realm* realm, JSScript* topLevel,
const char* name);
// Write the Lcov output in a buffer, such as the one associated with
// the runtime code coverage trace file.
void exportInto(GenericPrinter& out, bool* isEmpty) const;
private:
// Write the script name in out.
bool writeRealmName(JS::Realm* realm);
// Return the LCovSource entry which matches the given ScriptSourceObject.
LCovSource* lookupOrAdd(JS::Realm* realm, const char* name);
private:
typedef mozilla::Vector<LCovSource, 16, LifoAllocPolicy<Fallible>>
LCovSourceVector;
// LifoAlloc backend for all temporary allocations needed to stash the
// strings to be written in the file.
LifoAlloc alloc_;
// LifoAlloc string which hold the name of the realm.
LSprinter outTN_;
// Vector of all sources which are used in this realm.
LCovSourceVector* sources_;
};
class LCovRuntime {
public:
LCovRuntime();
~LCovRuntime();
// If the environment variable JS_CODE_COVERAGE_OUTPUT_DIR is set to a
// directory, create a file inside this directory which uses the process
// ID, the thread ID and a timestamp to ensure the uniqueness of the
// file.
//
// At the end of the execution, this file should contains the LCOV output of
// all the scripts executed in the current JSRuntime.
void init();
// Check if we should collect code coverage information.
bool isEnabled() const {
static bool isEnabled_ = ([]() {
const char* outDir = getenv("JS_CODE_COVERAGE_OUTPUT_DIR");
return outDir && *outDir != 0;
})();
return isEnabled_;
}
// Write the aggregated result of the code coverage of a realm
// into a file.
void writeLCovResult(LCovRealm& realm);
private:
// When a process forks, the file will remain open, but 2 processes will
// have the same file. To avoid conflicting writes, we open a new file for
// the child process.
void maybeReopenAfterFork();
// Fill an array with the name of the file. Return false if we are unable to
// serialize the filename in this array.
bool fillWithFilename(char* name, size_t length);
// Finish the current opened file, and remove if it does not have any
// content.
void finishFile();
private:
// Output file which is created if code coverage is enabled.
Fprinter out_;
// The process' PID is used to watch for fork. When the process fork,
// we want to close the current file and open a new one.
uint32_t pid_;
// Flag used to report if the generated file is empty or not. If it is empty
// when the runtime is destroyed, then the file would be removed as an empty
// file is not a valid LCov file.
bool isEmpty_;
};
} // namespace coverage
} // namespace js
#endif // vm_Printer_h