Skip to content

Commit c1916ee

Browse files
committed
refactor(fuzzer): share coverage counter registration
1 parent 214291a commit c1916ee

1 file changed

Lines changed: 36 additions & 45 deletions

File tree

packages/fuzzer/shared/coverage.cpp

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
// limitations under the License.
1414
#include "coverage.h"
1515

16-
#include <iostream>
16+
#include <cstddef>
17+
#include <cstdint>
1718

1819
extern "C" {
1920
void __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

2425
namespace {
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.
2728
uint8_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.
3334
struct 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+
4367
void 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

6581
void 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

Comments
 (0)