Skip to content

Commit 771914e

Browse files
committed
feat(instrumentor): remap ESM PC locations via source maps
Apply the same PC location batching and source-map remapping logic in the ESM loader path. This keeps print_pcs and print_funcs aligned with original sources for ESM modules, with safe fallback to generated locations.
1 parent 4d1d1d7 commit 771914e

1 file changed

Lines changed: 20 additions & 9 deletions

File tree

packages/instrumentor/esm-loader.mts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ const { sourceCodeCoverage } =
4343
require("./plugins/sourceCodeCoverage.js") as typeof import("./plugins/sourceCodeCoverage.js");
4444
const { functionHooks } =
4545
require("./plugins/functionHooks.js") as typeof import("./plugins/functionHooks.js");
46+
const { buildPCLocationBatches } =
47+
require("./pcLocationBatches.js") as typeof import("./pcLocationBatches.js");
48+
const { extractSourceMap, toRawSourceMap } =
49+
require("./SourceMapRegistry.js") as typeof import("./SourceMapRegistry.js");
4650

4751
// The loader thread has its own CJS module cache, so this is a
4852
// separate HookManager instance from the main thread's. We populate
@@ -136,6 +140,7 @@ export const load: LoadFn = async function load(url, context, nextLoad) {
136140

137141
function instrumentModule(code: string, filename: string): string | null {
138142
drainHookUpdates();
143+
const inputSourceMap = extractSourceMap(code, filename);
139144

140145
const fuzzerCoverage = esmCodeCoverage();
141146

@@ -163,6 +168,7 @@ function instrumentModule(code: string, filename: string): string | null {
163168
filename,
164169
sourceFileName: filename,
165170
sourceMaps: true,
171+
inputSourceMap: toRawSourceMap(inputSourceMap) as any,
166172
plugins,
167173
sourceType: "module",
168174
});
@@ -176,8 +182,6 @@ function instrumentModule(code: string, filename: string): string | null {
176182
if (edges === 0 || !transformed?.code) {
177183
return null;
178184
}
179-
const displayFilename = stripProjectRootPrefix(filename);
180-
181185
// Build a preamble that runs on the main thread before the module
182186
// body. It allocates the per-module coverage counter buffer and,
183187
// when a source map is available, registers it with the main-thread
@@ -192,15 +196,22 @@ function instrumentModule(code: string, filename: string): string | null {
192196
// [id, line, col, funcIdx, isFuncEntry, ...]
193197
const edgeEntries = fuzzerCoverage.edgeEntries();
194198
if (edgeEntries.length > 0) {
195-
const flat = edgeEntries.flat();
196199
const funcNames = fuzzerCoverage.funcNames();
197-
preambleLines.push(
198-
`Fuzzer.coverageTracker.registerPCLocations(` +
199-
`${JSON.stringify(displayFilename)},` +
200-
`${JSON.stringify(funcNames)},` +
201-
`new Int32Array(${JSON.stringify(flat)}),` +
202-
`__jazzer_pcBase);`,
200+
const batches = buildPCLocationBatches(
201+
edgeEntries,
202+
filename,
203+
inputSourceMap,
204+
stripProjectRootPrefix,
203205
);
206+
for (const batch of batches) {
207+
preambleLines.push(
208+
`Fuzzer.coverageTracker.registerPCLocations(` +
209+
`${JSON.stringify(batch.filename)},` +
210+
`${JSON.stringify(funcNames)},` +
211+
`new Int32Array(${JSON.stringify(Array.from(batch.entries))}),` +
212+
`__jazzer_pcBase);`,
213+
);
214+
}
204215
}
205216

206217
if (transformed.map) {

0 commit comments

Comments
 (0)