1313// limitations under the License.
1414#include " coverage.h"
1515
16- #include < iostream>
16+ #include < cstddef>
17+ #include < cstdint>
1718
1819extern " C" {
1920void __sanitizer_cov_8bit_counters_init (uint8_t *start, uint8_t *end);
@@ -22,24 +23,47 @@ void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg,
2223}
2324
2425namespace {
25- // We register an array of 8-bit coverage counters with libFuzzer. The array is
26- // populated from JavaScript using Buffer .
26+ // Shared coverage counter buffer populated from JavaScript using Buffer.
27+ // Individual slices are registered with libFuzzer by RegisterNewCounters .
2728uint8_t *gCoverageCounters = nullptr ;
2829
29- // PC-Table is used by libfuzzer to keep track of program addresses
30+ // PC-Table is used by libFuzzer to keep track of program addresses
3031// corresponding to coverage counters. The flags determine whether the
3132// corresponding counter is the beginning of a function; we don't currently use
3233// it.
3334struct PCTableEntry {
3435 uintptr_t PC, PCFlags;
3536};
37+ static_assert (sizeof (PCTableEntry) == 2 * sizeof (uintptr_t ),
38+ " PCTableEntry must match sanitizer PC table layout" );
3639
37- // The array of supplementary information for coverage counters. Each entry
38- // corresponds to an entry in gCoverageCounters; since we don't know the actual
39- // addresses of our counters in JS land, we fill this table with fake
40- // information.
41- PCTableEntry *gPCEntries = nullptr ;
40+ void RegisterCounterRange (uint8_t *start, uint8_t *end) {
41+ if (start >= end) {
42+ return ;
43+ }
44+
45+ auto num_counters = static_cast <std::size_t >(end - start);
46+
47+ // libFuzzer requires an array containing the instruction addresses
48+ // associated with the coverage counters. Since these JavaScript counters are
49+ // synthetic and not associated with real code, we create PC entries with the
50+ // flag set to 0 to indicate they are not real function-entry PCs. The PC
51+ // value is set to the local counter index for identification purposes.
52+ //
53+ // Intentionally never freed: libFuzzer holds a raw pointer to this table via
54+ // __sanitizer_cov_pcs_init and may read it at any time.
55+ auto *pc_entries = new PCTableEntry[num_counters];
56+ for (std::size_t i = 0 ; i < num_counters; ++i) {
57+ pc_entries[i] = {i, 0 };
58+ }
59+
60+ auto *pc_entries_end = pc_entries + num_counters;
61+ __sanitizer_cov_8bit_counters_init (start, end);
62+ __sanitizer_cov_pcs_init (reinterpret_cast <const uintptr_t *>(pc_entries),
63+ reinterpret_cast <const uintptr_t *>(pc_entries_end));
64+ }
4265} // namespace
66+
4367void RegisterCoverageMap (const Napi::CallbackInfo &info) {
4468 if (info.Length () != 1 ) {
4569 throw Napi::Error::New (info.Env (),
@@ -52,14 +76,6 @@ void RegisterCoverageMap(const Napi::CallbackInfo &info) {
5276 auto buf = info[0 ].As <Napi::Buffer<uint8_t >>();
5377
5478 gCoverageCounters = reinterpret_cast <uint8_t *>(buf.Data ());
55- // Fill the PC table with fake entries. The only requirement is that the fake
56- // addresses must not collide with the locations of real counters (e.g., from
57- // instrumented C++ code). Therefore, we just use the address of the counter
58- // itself - it's in a statically allocated memory region under our control.
59- gPCEntries = new PCTableEntry[buf.Length ()];
60- for (std::size_t i = 0 ; i < buf.Length (); ++i) {
61- gPCEntries [i] = {i, 0 };
62- }
6379}
6480
6581void RegisterNewCounters (const Napi::CallbackInfo &info) {
@@ -84,23 +100,10 @@ void RegisterNewCounters(const Napi::CallbackInfo &info) {
84100 return ;
85101 }
86102
87- __sanitizer_cov_8bit_counters_init (gCoverageCounters + old_num_counters,
88- gCoverageCounters + new_num_counters);
89- __sanitizer_cov_pcs_init ((uintptr_t *)(gPCEntries + old_num_counters),
90- (uintptr_t *)(gPCEntries + new_num_counters));
103+ RegisterCounterRange (gCoverageCounters + old_num_counters,
104+ gCoverageCounters + new_num_counters);
91105}
92106
93- // Monotonically increasing fake PC so that each module's counters get
94- // unique program-counter entries that don't collide with the shared
95- // coverage map or with each other.
96- //
97- // With PCFlags=0, libFuzzer's core feedback loop (CollectFeatures) does
98- // not read the PC value — it works purely from counter indices. Unique
99- // PCs are still needed for forward-compatibility: __sanitizer_symbolize_pc
100- // support, __sanitizer_cov_get_observed_pcs for external tool integration,
101- // and -print_pcs cosmetic output all depend on distinct PC values.
102- static uintptr_t gNextModulePC = 0x10000000 ;
103-
104107// Register an independent coverage counter region for a single ES module.
105108// libFuzzer supports multiple disjoint counter regions; each call here
106109// hands it a fresh one.
@@ -116,17 +119,5 @@ void RegisterModuleCounters(const Napi::CallbackInfo &info) {
116119 return ;
117120 }
118121
119- // Intentionally never freed: libFuzzer holds a raw pointer to this table
120- // (via __sanitizer_cov_pcs_init) and may read it at any time — during the
121- // fuzz loop, coverage printing, or crash handling. The CJS path (line 59)
122- // uses the same pattern. Module count is bounded by project size, so the
123- // cumulative allocation is negligible.
124- auto *pcEntries = new PCTableEntry[size];
125- for (std::size_t i = 0 ; i < size; ++i) {
126- pcEntries[i] = {gNextModulePC ++, 0 };
127- }
128-
129- __sanitizer_cov_8bit_counters_init (buf.Data (), buf.Data () + size);
130- __sanitizer_cov_pcs_init (reinterpret_cast <uintptr_t *>(pcEntries),
131- reinterpret_cast <uintptr_t *>(pcEntries + size));
122+ RegisterCounterRange (buf.Data (), buf.Data () + size);
132123}
0 commit comments