vitest-pool-assemblyscript 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BINARYEN_VERSION +1 -0
- package/LICENSE +53 -0
- package/README.md +607 -0
- package/assembly/compare.ts +219 -0
- package/assembly/describe.ts +104 -0
- package/assembly/expect.ts +335 -0
- package/assembly/index.ts +14 -0
- package/assembly/options.ts +198 -0
- package/assembly/test.ts +147 -0
- package/assembly/tsconfig.json +6 -0
- package/binding.gyp +62 -0
- package/dist/ast-visitor-DC3SuTzs.mjs +310 -0
- package/dist/ast-visitor-DC3SuTzs.mjs.map +1 -0
- package/dist/compile-runner-8h0dBwG2.mjs +80 -0
- package/dist/compile-runner-8h0dBwG2.mjs.map +1 -0
- package/dist/compiler/transforms/strip-inline.d.mts +18 -0
- package/dist/compiler/transforms/strip-inline.d.mts.map +1 -0
- package/dist/compiler/transforms/strip-inline.mjs +38 -0
- package/dist/compiler/transforms/strip-inline.mjs.map +1 -0
- package/dist/compiler-CN6BRK_N.mjs +295 -0
- package/dist/compiler-CN6BRK_N.mjs.map +1 -0
- package/dist/config/index-v3.d.mts +111 -0
- package/dist/config/index-v3.d.mts.map +1 -0
- package/dist/config/index-v3.mjs +11 -0
- package/dist/config/index-v3.mjs.map +1 -0
- package/dist/config/index.d.mts +4 -0
- package/dist/config/index.mjs +8 -0
- package/dist/constants-CA50WBdr.mjs +130 -0
- package/dist/constants-CA50WBdr.mjs.map +1 -0
- package/dist/coverage-merge-0WqdC-dq.mjs +22 -0
- package/dist/coverage-merge-0WqdC-dq.mjs.map +1 -0
- package/dist/coverage-provider/index.d.mts +15 -0
- package/dist/coverage-provider/index.d.mts.map +1 -0
- package/dist/coverage-provider/index.mjs +535 -0
- package/dist/coverage-provider/index.mjs.map +1 -0
- package/dist/custom-provider-options-CF5C1kXb.d.mts +26 -0
- package/dist/custom-provider-options-CF5C1kXb.d.mts.map +1 -0
- package/dist/debug-IeEHsxy0.mjs +195 -0
- package/dist/debug-IeEHsxy0.mjs.map +1 -0
- package/dist/index-internal.d.mts +23 -0
- package/dist/index-internal.d.mts.map +1 -0
- package/dist/index-internal.mjs +4 -0
- package/dist/index-v3.d.mts +7 -0
- package/dist/index-v3.d.mts.map +1 -0
- package/dist/index-v3.mjs +206 -0
- package/dist/index-v3.mjs.map +1 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +8 -0
- package/dist/load-user-imports-J9eaAW0_.mjs +801 -0
- package/dist/load-user-imports-J9eaAW0_.mjs.map +1 -0
- package/dist/pool-runner-init-CEwLyNI3.d.mts +8 -0
- package/dist/pool-runner-init-CEwLyNI3.d.mts.map +1 -0
- package/dist/pool-runner-init-d5qScS41.mjs +400 -0
- package/dist/pool-runner-init-d5qScS41.mjs.map +1 -0
- package/dist/pool-thread/compile-worker-thread.d.mts +7 -0
- package/dist/pool-thread/compile-worker-thread.d.mts.map +1 -0
- package/dist/pool-thread/compile-worker-thread.mjs +42 -0
- package/dist/pool-thread/compile-worker-thread.mjs.map +1 -0
- package/dist/pool-thread/test-worker-thread.d.mts +7 -0
- package/dist/pool-thread/test-worker-thread.d.mts.map +1 -0
- package/dist/pool-thread/test-worker-thread.mjs +39 -0
- package/dist/pool-thread/test-worker-thread.mjs.map +1 -0
- package/dist/pool-thread/v3-tinypool-thread.d.mts +7 -0
- package/dist/pool-thread/v3-tinypool-thread.d.mts.map +1 -0
- package/dist/pool-thread/v3-tinypool-thread.mjs +57 -0
- package/dist/pool-thread/v3-tinypool-thread.mjs.map +1 -0
- package/dist/resolve-config-as1w-Qyz.mjs +65 -0
- package/dist/resolve-config-as1w-Qyz.mjs.map +1 -0
- package/dist/test-runner-B2BpyPNK.mjs +142 -0
- package/dist/test-runner-B2BpyPNK.mjs.map +1 -0
- package/dist/types-8KKo9Hbf.d.mts +228 -0
- package/dist/types-8KKo9Hbf.d.mts.map +1 -0
- package/dist/vitest-file-tasks-BUwzh375.mjs +61 -0
- package/dist/vitest-file-tasks-BUwzh375.mjs.map +1 -0
- package/dist/vitest-tasks-BKS7689f.mjs +319 -0
- package/dist/vitest-tasks-BKS7689f.mjs.map +1 -0
- package/dist/worker-rpc-channel-lbhK7Qz8.mjs +25 -0
- package/dist/worker-rpc-channel-lbhK7Qz8.mjs.map +1 -0
- package/package.json +112 -0
- package/prebuilds/linux-x64/vitest-pool-assemblyscript.glibc.node +0 -0
- package/scripts/install.js +91 -0
- package/scripts/setup-binaryen.js +179 -0
- package/src/native-instrumentation/addon.cpp +788 -0
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* wasm-binaryen-debug Native Addon
|
|
3
|
+
*
|
|
4
|
+
* Wraps Binaryen's C++ API to extract detailed debug information from WebAssembly binaries.
|
|
5
|
+
* Provides expression-level debug locations that the JavaScript API doesn't expose.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include <napi.h>
|
|
9
|
+
#include <vector>
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <map>
|
|
12
|
+
#include <unordered_set>
|
|
13
|
+
#include <sstream>
|
|
14
|
+
|
|
15
|
+
// Binaryen C++ API headers
|
|
16
|
+
#include "wasm-binary.h"
|
|
17
|
+
#include "wasm-io.h"
|
|
18
|
+
#include "wasm-builder.h"
|
|
19
|
+
#include "ir/module-utils.h"
|
|
20
|
+
#include "ir/names.h"
|
|
21
|
+
#include "cfg/cfg-traversal.h"
|
|
22
|
+
#include "support/name.h"
|
|
23
|
+
#include "pass.h"
|
|
24
|
+
|
|
25
|
+
using namespace wasm;
|
|
26
|
+
|
|
27
|
+
// 32
|
|
28
|
+
const uint32_t BYTES_PER_COUNTER = 4;
|
|
29
|
+
|
|
30
|
+
// 1 page = 64KB / 4bytes (32bits) each = 16384 counters
|
|
31
|
+
const uint32_t COUNTERS_PER_PAGE = 16384;
|
|
32
|
+
|
|
33
|
+
// TODO - pass these through the call stack as params instead
|
|
34
|
+
// for now we don't expect them to change between different calls
|
|
35
|
+
// in the same thread over the same vitest run, so it's safe to use this approach
|
|
36
|
+
thread_local bool DEBUG = false;
|
|
37
|
+
thread_local std::string LOG_PREFIX = "InstNative";
|
|
38
|
+
|
|
39
|
+
struct SourceDebugLocation {
|
|
40
|
+
bool exists;
|
|
41
|
+
uint32_t fileIndex; // Debug location file index
|
|
42
|
+
uint32_t lineNumber; // Debug location line number
|
|
43
|
+
uint32_t columnNumber; // Debug location column number
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Structure to hold expression information during AST walk
|
|
48
|
+
*/
|
|
49
|
+
struct ExpressionInfo {
|
|
50
|
+
std::string type; // Expression type name
|
|
51
|
+
uint32_t fileIndex; // Debug location file index
|
|
52
|
+
uint32_t lineNumber; // Debug location line number
|
|
53
|
+
uint32_t columnNumber; // Debug location column number
|
|
54
|
+
bool hasDebugLocation; // Whether debug location exists
|
|
55
|
+
bool isBranch; // Whether this is a branch expression
|
|
56
|
+
uint32_t branchPaths; // Number of branch paths (if isBranch)
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Structure to hold basic block information
|
|
61
|
+
*/
|
|
62
|
+
struct BasicBlockInfo {
|
|
63
|
+
std::vector<size_t> expressionIndices; // Indices into the flat expression array
|
|
64
|
+
std::vector<size_t> branches; // Indices of blocks this block branches to
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Data structure to collect function info during instrumentation
|
|
68
|
+
struct FunctionInfo {
|
|
69
|
+
std::string name;
|
|
70
|
+
uint32_t coverageMemoryIndex;
|
|
71
|
+
SourceDebugLocation representativeLocation;
|
|
72
|
+
std::vector<ExpressionInfo> expressions;
|
|
73
|
+
std::vector<BasicBlockInfo> blocks;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Custom content structure for CFGWalker
|
|
78
|
+
*/
|
|
79
|
+
struct BlockContent {
|
|
80
|
+
std::vector<Expression*> expressions;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Walker to extract expression and basic block information using CFGWalker
|
|
85
|
+
*
|
|
86
|
+
* This walker traverses the AST in a single pass to collect:
|
|
87
|
+
* 1. All expressions with their debug locations and types
|
|
88
|
+
* 2. Basic block groupings with branch edges
|
|
89
|
+
*/
|
|
90
|
+
struct DebugInfoWalker : public WalkerPass<CFGWalker<DebugInfoWalker, UnifiedExpressionVisitor<DebugInfoWalker>, BlockContent>> {
|
|
91
|
+
Module* module;
|
|
92
|
+
|
|
93
|
+
// Results for current function
|
|
94
|
+
std::vector<ExpressionInfo> expressions;
|
|
95
|
+
std::vector<BasicBlockInfo> blocks;
|
|
96
|
+
|
|
97
|
+
// Map from BasicBlock pointer to block index for building branches
|
|
98
|
+
std::map<BasicBlock*, size_t> blockIndexMap;
|
|
99
|
+
|
|
100
|
+
explicit DebugInfoWalker(Module* m) : module(m) {}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Called for each expression during CFG walk
|
|
104
|
+
* Collects expression info and adds to current basic block
|
|
105
|
+
*/
|
|
106
|
+
void visitExpression(Expression* curr) {
|
|
107
|
+
// skip collecting expressions if:
|
|
108
|
+
// - Not currently inside a basicBlock (`currBasicBlock` provided by CFGWalker)
|
|
109
|
+
// - expression is a Block (Blocks are only containers and have no debug locations)
|
|
110
|
+
if (!currBasicBlock || curr->is<Block>()) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Get debug location from function's debugLocations map
|
|
115
|
+
Function* func = getFunction();
|
|
116
|
+
ExpressionInfo info;
|
|
117
|
+
info.hasDebugLocation = false;
|
|
118
|
+
|
|
119
|
+
// Check debugLocations map
|
|
120
|
+
auto it = func->debugLocations.find(curr);
|
|
121
|
+
if (it != func->debugLocations.end() && it->second.has_value()) {
|
|
122
|
+
const auto& loc = it->second.value();
|
|
123
|
+
info.fileIndex = loc.fileIndex;
|
|
124
|
+
info.lineNumber = loc.lineNumber;
|
|
125
|
+
info.columnNumber = loc.columnNumber;
|
|
126
|
+
info.hasDebugLocation = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// skip expressions without debug locations
|
|
130
|
+
// TODO - determine if this will cause problems in branch coverage
|
|
131
|
+
if (!info.hasDebugLocation) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
info.type = getExpressionName(curr); // Expression type string
|
|
136
|
+
|
|
137
|
+
// Determine if this is a branch expression and count paths
|
|
138
|
+
info.isBranch = false;
|
|
139
|
+
info.branchPaths = 0;
|
|
140
|
+
|
|
141
|
+
// TODO - determine if we're missing any branch types (SIMDTernary?)
|
|
142
|
+
if (curr->is<If>()) {
|
|
143
|
+
info.isBranch = true;
|
|
144
|
+
auto* ifExpr = curr->cast<If>();
|
|
145
|
+
info.branchPaths = ifExpr->ifFalse ? 2 : 1; // If/else = 2, if only = 1
|
|
146
|
+
} else if (curr->is<Break>()) {
|
|
147
|
+
info.isBranch = true;
|
|
148
|
+
info.branchPaths = 2; // Branch taken or not (conditional break)
|
|
149
|
+
} else if (curr->is<Select>()) {
|
|
150
|
+
info.isBranch = true;
|
|
151
|
+
info.branchPaths = 2; // True or false condition
|
|
152
|
+
} else if (curr->is<Switch>()) {
|
|
153
|
+
info.isBranch = true;
|
|
154
|
+
auto* switchExpr = curr->cast<Switch>();
|
|
155
|
+
info.branchPaths = switchExpr->targets.size() + 1; // N targets + default
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Add to flat expressions array
|
|
159
|
+
expressions.push_back(info);
|
|
160
|
+
|
|
161
|
+
// Add expression to current basic block's content
|
|
162
|
+
currBasicBlock->contents.expressions.push_back(curr);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Called for each function
|
|
167
|
+
* Walks the function and collects expression + basic block data
|
|
168
|
+
*/
|
|
169
|
+
void doWalkFunction(Function* func) {
|
|
170
|
+
// Reset state for this function
|
|
171
|
+
expressions.clear();
|
|
172
|
+
blocks.clear();
|
|
173
|
+
blockIndexMap.clear();
|
|
174
|
+
|
|
175
|
+
// Walk the function using CFGWalker
|
|
176
|
+
CFGWalker<DebugInfoWalker, UnifiedExpressionVisitor<DebugInfoWalker>, BlockContent>::doWalkFunction(func);
|
|
177
|
+
|
|
178
|
+
// After walk, build out basic block info with expression indices.
|
|
179
|
+
// `basicBlocks` provided by CFGWalker, now populated after the function walk
|
|
180
|
+
size_t exprIndex = 0;
|
|
181
|
+
for (auto& bb : basicBlocks) {
|
|
182
|
+
BasicBlockInfo blockInfo;
|
|
183
|
+
|
|
184
|
+
// Store the index for this block
|
|
185
|
+
blockIndexMap[bb.get()] = blocks.size();
|
|
186
|
+
|
|
187
|
+
// Record expression indices for this block
|
|
188
|
+
size_t expressionCount = bb->contents.expressions.size();
|
|
189
|
+
for (size_t i = 0; i < expressionCount; i++) {
|
|
190
|
+
blockInfo.expressionIndices.push_back(exprIndex++);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
blocks.push_back(blockInfo);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Now build branch edges between blocks
|
|
197
|
+
for (size_t i = 0; i < basicBlocks.size(); i++) {
|
|
198
|
+
auto& bb = basicBlocks[i];
|
|
199
|
+
for (auto* outBlock : bb->out) {
|
|
200
|
+
auto it = blockIndexMap.find(outBlock);
|
|
201
|
+
if (it != blockIndexMap.end()) {
|
|
202
|
+
blocks[i].branches.push_back(it->second);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
bool startsWith(const std::string& str, const std::string& prefix) {
|
|
210
|
+
return str.compare(0, prefix.length(), prefix) == 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Check if a function should be instrumented for coverage
|
|
215
|
+
*/
|
|
216
|
+
bool shouldInstrumentFunction(Function* func, std::string& excludedLibraryFilePrefix) {
|
|
217
|
+
const std::string& name = func->name.toString();
|
|
218
|
+
|
|
219
|
+
// Skip functions without a body
|
|
220
|
+
if (!func->body) {
|
|
221
|
+
if (DEBUG) {
|
|
222
|
+
std::cout << LOG_PREFIX << " - Skip Reason: Empty Function Body" << std::endl;
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Skip if this is an import (has non-empty module)
|
|
228
|
+
if (func->module.size() > 0) {
|
|
229
|
+
if (DEBUG) {
|
|
230
|
+
std::cout << LOG_PREFIX << " - Skip Reason: Imported from \"" << func->module.toString() << "\"" << std::endl;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Skip library functions
|
|
236
|
+
if (excludedLibraryFilePrefix.length() > 0 && startsWith(name, excludedLibraryFilePrefix)) {
|
|
237
|
+
if (DEBUG) {
|
|
238
|
+
std::cout << LOG_PREFIX << " - Skip Reason: Library file" << std::endl;
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Compiler-generated entry point
|
|
244
|
+
if (name.compare("~start") == 0) {
|
|
245
|
+
if (DEBUG) {
|
|
246
|
+
std::cout << LOG_PREFIX << " - Skip Reason: Module entry point" << std::endl;
|
|
247
|
+
}
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Find representative expression within a function's Block-type body (Return preferred, then first non-Const)
|
|
256
|
+
*/
|
|
257
|
+
SourceDebugLocation getRepresentativeLocationInBlockBody(
|
|
258
|
+
Block* blockBody,
|
|
259
|
+
const std::unordered_map<wasm::Expression*, std::optional<wasm::Function::DebugLocation>> debugLocations
|
|
260
|
+
) {
|
|
261
|
+
SourceDebugLocation repLoc = { exists: false, fileIndex: 0, lineNumber: 0, columnNumber: 0 };
|
|
262
|
+
|
|
263
|
+
if (DEBUG) {
|
|
264
|
+
std::cout << LOG_PREFIX << " - Checking func Block body: " << blockBody->list.size() << " body expressions" << std::endl;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (size_t i = 0; i < blockBody->list.size(); i++) {
|
|
268
|
+
Expression* exprInBlockBody = blockBody->list[i];
|
|
269
|
+
|
|
270
|
+
if (exprInBlockBody) {
|
|
271
|
+
auto it = debugLocations.find(exprInBlockBody);
|
|
272
|
+
if (it != debugLocations.end() && it->second.has_value()) {
|
|
273
|
+
const auto& loc = it->second.value();
|
|
274
|
+
|
|
275
|
+
repLoc.exists = true;
|
|
276
|
+
repLoc.fileIndex = loc.fileIndex;
|
|
277
|
+
repLoc.lineNumber = loc.lineNumber;
|
|
278
|
+
repLoc.columnNumber = loc.columnNumber;
|
|
279
|
+
|
|
280
|
+
if (DEBUG) {
|
|
281
|
+
std::cout << LOG_PREFIX << " - Block body expr [" << i << "] (" << getExpressionName(exprInBlockBody) << ")="
|
|
282
|
+
<< loc.fileIndex << ":" << loc.lineNumber << ":" << loc.columnNumber << " - break" << std::endl;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
break;
|
|
286
|
+
|
|
287
|
+
} else if (DEBUG) {
|
|
288
|
+
std::cout << LOG_PREFIX << " - Block body expr [" << i << "] (" << getExpressionName(exprInBlockBody) << ") - No location" << std::endl;
|
|
289
|
+
}
|
|
290
|
+
} else if (DEBUG) {
|
|
291
|
+
std::cout << LOG_PREFIX << " - WARNING: Block body expr [" << i << "] - EMPTY" << std::endl;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return repLoc;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
SourceDebugLocation getRepresentativeLocation(Function* func) {
|
|
299
|
+
SourceDebugLocation repLoc = { exists: false, fileIndex: 0, lineNumber: 0, columnNumber: 0 };
|
|
300
|
+
|
|
301
|
+
// Get body expression debug location
|
|
302
|
+
Expression* body = func->body;
|
|
303
|
+
|
|
304
|
+
if (!body) {
|
|
305
|
+
if (DEBUG) {
|
|
306
|
+
std::cout << LOG_PREFIX << " - Function has no body expression - No debug locations available to check" << std::endl;
|
|
307
|
+
}
|
|
308
|
+
return repLoc;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const std::string bodyType = getExpressionName(body);
|
|
312
|
+
|
|
313
|
+
if (body->is<Load>() || body->is<Store>()) {
|
|
314
|
+
// Load/Store body:
|
|
315
|
+
// - Compiler-generated functions with no expressions with locations
|
|
316
|
+
// - LOAD: Compiler-generated class member getters (field value getters, function member getters)
|
|
317
|
+
// - STORE: Compiler-generated class member value setters (field value setters)
|
|
318
|
+
//
|
|
319
|
+
// Note: compiler-generated class member function setters use a Block body also,
|
|
320
|
+
// but their expressions (Store+Call) have no locations
|
|
321
|
+
if (DEBUG) {
|
|
322
|
+
std::cout << LOG_PREFIX << " - Compiler-generated accessor function (body=" << bodyType << ") - No location" << std::endl;
|
|
323
|
+
}
|
|
324
|
+
return repLoc;
|
|
325
|
+
} else if (body->is<Block>()) {
|
|
326
|
+
// Block body:
|
|
327
|
+
// - Block expressions are only containers and have no source locations of their own
|
|
328
|
+
// - Examine expressions within the block body to find location, if one exists
|
|
329
|
+
if (DEBUG) {
|
|
330
|
+
std::cout << LOG_PREFIX << " - Checking function Block body expression list" << std::endl;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
repLoc = getRepresentativeLocationInBlockBody(body->cast<Block>(), func->debugLocations);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// use body expression's debug location if available
|
|
337
|
+
auto it = func->debugLocations.find(body);
|
|
338
|
+
if (it != func->debugLocations.end() && it->second.has_value()) {
|
|
339
|
+
const auto& loc = it->second.value();
|
|
340
|
+
repLoc.exists = true;
|
|
341
|
+
repLoc.fileIndex = loc.fileIndex;
|
|
342
|
+
repLoc.lineNumber = loc.lineNumber;
|
|
343
|
+
repLoc.columnNumber = loc.columnNumber;
|
|
344
|
+
|
|
345
|
+
if (DEBUG) {
|
|
346
|
+
std::cout << LOG_PREFIX << " - Using function body (" << bodyType << ")="
|
|
347
|
+
<< repLoc.fileIndex << ":" << repLoc.lineNumber << ":" << repLoc.columnNumber << std::endl;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!repLoc.exists && DEBUG) {
|
|
352
|
+
std::cout << LOG_PREFIX << " - Warning: Location expected on function body (" << bodyType << ") - No location found" << std::endl;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return repLoc;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Instrument WASM binary for coverage and regenerate source map
|
|
360
|
+
*
|
|
361
|
+
* This function:
|
|
362
|
+
* 1. Reads WASM binary with source map
|
|
363
|
+
* 2. Adds __coverage_memory import (multi-memory for coverage counters)
|
|
364
|
+
* 3. Instruments each user function with coverage counter increment
|
|
365
|
+
* 4. Extracts debug information
|
|
366
|
+
* 5. Writes instrumented binary with regenerated source map
|
|
367
|
+
*
|
|
368
|
+
* @param wasmBuffer - Node.js Buffer containing WASM binary
|
|
369
|
+
* @param sourceMapBuffer - Node.js Buffer containing source map JSON
|
|
370
|
+
* @returns Object with { instrumentedWasm, sourceMap, debugInfo }
|
|
371
|
+
*/
|
|
372
|
+
Napi::Object InstrumentForCoverage(const Napi::CallbackInfo& info) {
|
|
373
|
+
Napi::Env env = info.Env();
|
|
374
|
+
|
|
375
|
+
// Validate arguments
|
|
376
|
+
if (info.Length() < 3) {
|
|
377
|
+
Napi::TypeError::New(env, "Expected 3 arguments: wasmBuffer, sourceMapBuffer, instrumentationOptions")
|
|
378
|
+
.ThrowAsJavaScriptException();
|
|
379
|
+
return Napi::Object::New(env);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!info[0].IsBuffer()) {
|
|
383
|
+
Napi::TypeError::New(env, "Argument 0 (wasmBuffer) must be a Buffer (WASM binary)")
|
|
384
|
+
.ThrowAsJavaScriptException();
|
|
385
|
+
return Napi::Object::New(env);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!info[1].IsBuffer()) {
|
|
389
|
+
Napi::TypeError::New(env, "Argument 1 (sourceMapBuffer) must be a Buffer (source map)")
|
|
390
|
+
.ThrowAsJavaScriptException();
|
|
391
|
+
return Napi::Object::New(env);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!info[2].IsObject()) {
|
|
395
|
+
Napi::TypeError::New(env, "Argument 2 (instrumentationOptions) must be supplied as an object")
|
|
396
|
+
.ThrowAsJavaScriptException();
|
|
397
|
+
return Napi::Object::New(env);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
// Extract buffer data
|
|
402
|
+
Napi::Buffer<char> wasmBuf = info[0].As<Napi::Buffer<char>>();
|
|
403
|
+
Napi::Buffer<char> sourceMapBuf = info[1].As<Napi::Buffer<char>>();
|
|
404
|
+
|
|
405
|
+
// Extract options
|
|
406
|
+
const Napi::Object options = info[2].As<Napi::Object>();
|
|
407
|
+
|
|
408
|
+
// Extracted options
|
|
409
|
+
std::unordered_set<std::string> excludedFiles;
|
|
410
|
+
std::string excludedLibraryFilePrefix;
|
|
411
|
+
// 1 page = 64KB / 4bytes (32bits) each = 16384 counters
|
|
412
|
+
uint32_t coverageMemoryPagesMin = 1;
|
|
413
|
+
// 4 pages = 256KB / 4bytes (32bits) each = 65536 counters
|
|
414
|
+
uint32_t coverageMemoryPagesMax = 4;
|
|
415
|
+
uint32_t maxCounters = coverageMemoryPagesMax * COUNTERS_PER_PAGE;
|
|
416
|
+
|
|
417
|
+
if (options.Has("logPrefix")) {
|
|
418
|
+
Napi::Value logPrefixProp = options.Get("logPrefix");
|
|
419
|
+
if (logPrefixProp.IsString()) {
|
|
420
|
+
LOG_PREFIX = logPrefixProp.As<Napi::String>().Utf8Value();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (options.Has("debug")) {
|
|
425
|
+
Napi::Value debugProperty = options.Get("debug");
|
|
426
|
+
if (debugProperty.IsBoolean()) {
|
|
427
|
+
DEBUG = debugProperty.As<Napi::Boolean>().Value();
|
|
428
|
+
|
|
429
|
+
if (DEBUG) {
|
|
430
|
+
std::cout << LOG_PREFIX << " - OPTIONS - DEBUG enabled" << std::endl;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (options.Has("excludedFiles")) {
|
|
436
|
+
Napi::Value excludedFilesProperty = options.Get("excludedFiles");
|
|
437
|
+
if (excludedFilesProperty.IsArray()) {
|
|
438
|
+
const Napi::Array filesArray = excludedFilesProperty.As<Napi::Array>();
|
|
439
|
+
|
|
440
|
+
const uint32_t count = filesArray.Length();
|
|
441
|
+
if (DEBUG && count > 0) {
|
|
442
|
+
std::cout << LOG_PREFIX << " - OPTIONS - " << count << " Excluded Files:" << std::endl;
|
|
443
|
+
} else if (DEBUG) {
|
|
444
|
+
std::cout << LOG_PREFIX << " - 0 Excluded Files" << std::endl;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
for (size_t i = 0; i < count; i++) {
|
|
448
|
+
Napi::Value fileItem = filesArray[i];
|
|
449
|
+
if (fileItem.IsString()) {
|
|
450
|
+
const std::string file = fileItem.As<Napi::String>().Utf8Value();
|
|
451
|
+
excludedFiles.insert(file);
|
|
452
|
+
if (DEBUG) {
|
|
453
|
+
std::cout << LOG_PREFIX << " - [" << i << "] \"" << file << "\"" << std::endl;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (options.Has("excludedLibraryFilePrefix")) {
|
|
461
|
+
Napi::Value libraryFilePrefixProperty = options.Get("excludedLibraryFilePrefix");
|
|
462
|
+
if (libraryFilePrefixProperty.IsString()) {
|
|
463
|
+
excludedLibraryFilePrefix = libraryFilePrefixProperty.As<Napi::String>().Utf8Value();
|
|
464
|
+
|
|
465
|
+
if (DEBUG) {
|
|
466
|
+
std::cout << LOG_PREFIX << " - OPTIONS - Excluded Library File Prefix: \"" << excludedLibraryFilePrefix << "\"" << std::endl;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (options.Has("coverageMemoryPagesMin")) {
|
|
472
|
+
Napi::Value coverageMinProperty = options.Get("coverageMemoryPagesMin");
|
|
473
|
+
if (coverageMinProperty.IsNumber()) {
|
|
474
|
+
coverageMemoryPagesMin = coverageMinProperty.As<Napi::Number>().Int32Value();
|
|
475
|
+
|
|
476
|
+
if (DEBUG) {
|
|
477
|
+
const uint32_t minCounters = coverageMemoryPagesMin * COUNTERS_PER_PAGE;
|
|
478
|
+
std::cout << LOG_PREFIX << " - OPTIONS - Coverage Memory Pages MIN: " << coverageMemoryPagesMin
|
|
479
|
+
<< " (" << minCounters << " counters)" << std::endl;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (options.Has("coverageMemoryPagesMax")) {
|
|
485
|
+
Napi::Value coverageMaxProperty = options.Get("coverageMemoryPagesMax");
|
|
486
|
+
if (coverageMaxProperty.IsNumber()) {
|
|
487
|
+
coverageMemoryPagesMax = coverageMaxProperty.As<Napi::Number>().Int32Value();
|
|
488
|
+
maxCounters = coverageMemoryPagesMax * COUNTERS_PER_PAGE;
|
|
489
|
+
|
|
490
|
+
if (DEBUG) {
|
|
491
|
+
std::cout << LOG_PREFIX << " - OPTIONS - Coverage Memory Pages MAX: " << coverageMemoryPagesMax
|
|
492
|
+
<< " (" << maxCounters << " counters)" << std::endl;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
std::vector<char> wasmData(wasmBuf.Data(), wasmBuf.Data() + wasmBuf.Length());
|
|
498
|
+
std::vector<char> sourceMapData(sourceMapBuf.Data(), sourceMapBuf.Data() + sourceMapBuf.Length());
|
|
499
|
+
|
|
500
|
+
// Parse WASM binary with source map
|
|
501
|
+
Module module;
|
|
502
|
+
WasmBinaryReader reader(module, FeatureSet::All, wasmData, sourceMapData);
|
|
503
|
+
reader.setDebugInfo(true);
|
|
504
|
+
reader.read();
|
|
505
|
+
|
|
506
|
+
if (DEBUG) {
|
|
507
|
+
std::cout << LOG_PREFIX << " - Read binary module with " << module.functions.size() << " functions" << std::endl;
|
|
508
|
+
std::cout << LOG_PREFIX << " - Debug source files: " << module.debugInfoFileNames.size() << std::endl;
|
|
509
|
+
for (size_t i = 0; i < module.debugInfoFileNames.size(); i++) {
|
|
510
|
+
std::cout << LOG_PREFIX << " - [" << i << "] " << module.debugInfoFileNames[i] << std::endl;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Instrument functions and collect debug info
|
|
515
|
+
Builder builder(module);
|
|
516
|
+
uint32_t coverageIndex = 0;
|
|
517
|
+
std::vector<FunctionInfo> instrumentedFunctions;
|
|
518
|
+
|
|
519
|
+
// Enable multi-memory feature for coverage memory
|
|
520
|
+
module.features.setMultiMemory(true);
|
|
521
|
+
|
|
522
|
+
// Add __coverage_memory import
|
|
523
|
+
// This is a secondary memory used to store coverage counters
|
|
524
|
+
Name coverageMemoryName("__coverage_memory");
|
|
525
|
+
auto coverageMemory = Builder::makeMemory(coverageMemoryName);
|
|
526
|
+
coverageMemory->module = "__as_pool_env__";
|
|
527
|
+
coverageMemory->base = "__coverage_memory";
|
|
528
|
+
coverageMemory->initial = coverageMemoryPagesMin;
|
|
529
|
+
coverageMemory->max = coverageMemoryPagesMax;
|
|
530
|
+
coverageMemory->shared = false;
|
|
531
|
+
module.addMemory(std::move(coverageMemory));
|
|
532
|
+
|
|
533
|
+
// Create walker for debug info extraction
|
|
534
|
+
DebugInfoWalker walker(&module);
|
|
535
|
+
|
|
536
|
+
ModuleUtils::iterDefinedFunctions(module, [&](Function* func) {
|
|
537
|
+
std::string funcName = func->name.toString();
|
|
538
|
+
|
|
539
|
+
if (coverageIndex >= maxCounters) {
|
|
540
|
+
if (DEBUG) {
|
|
541
|
+
std::cout << LOG_PREFIX << " - ERROR: Processing function: \"" << funcName << "\""
|
|
542
|
+
<< " Further instrumentation would exceed max covergare memory size" << std::endl;
|
|
543
|
+
}
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (DEBUG) {
|
|
548
|
+
std::cout << LOG_PREFIX << " - Processing function: \"" << funcName << "\"" << std::endl;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Check if this function should be instrumented
|
|
552
|
+
if (!shouldInstrumentFunction(func, excludedLibraryFilePrefix)) {
|
|
553
|
+
if (DEBUG) {
|
|
554
|
+
std::cout << LOG_PREFIX << " - SKIP function (quick filtered): \"" << funcName << "\"" << std::endl;
|
|
555
|
+
}
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Walk function to collect expressions and basic blocks
|
|
560
|
+
walker.walkFunctionInModule(func, &module);
|
|
561
|
+
|
|
562
|
+
if (DEBUG) {
|
|
563
|
+
std::cout << LOG_PREFIX << " - CFG Walked function - expressions with locations: " << walker.expressions.size() << std::endl;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const SourceDebugLocation representativeLocation = getRepresentativeLocation(func);
|
|
567
|
+
|
|
568
|
+
// skip function if it has no representative location
|
|
569
|
+
if (!representativeLocation.exists) {
|
|
570
|
+
if (DEBUG) {
|
|
571
|
+
std::cout << LOG_PREFIX << " - SKIP function (No Representative Location, body=" << getExpressionName(func->body) << "): "
|
|
572
|
+
<< "\"" << funcName << "\"" << std::endl;
|
|
573
|
+
}
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Skip function if located within excluded file
|
|
578
|
+
const std::string functionDebugFilePath = module.debugInfoFileNames[representativeLocation.fileIndex];
|
|
579
|
+
if (excludedFiles.find(functionDebugFilePath) != excludedFiles.end()) {
|
|
580
|
+
if (DEBUG) {
|
|
581
|
+
std::cout << LOG_PREFIX << " - SKIP function (excluded location file [" << representativeLocation.fileIndex << "] \""
|
|
582
|
+
<< functionDebugFilePath <<"\"): \"" << funcName << "\"" << std::endl;
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (DEBUG) {
|
|
588
|
+
std::cout << LOG_PREFIX << " - Selected reprLoc=" << representativeLocation.fileIndex << ":" << representativeLocation.lineNumber
|
|
589
|
+
<< ":" << representativeLocation.columnNumber << " | Now instrumenting with coverageMemoryIndex [" << coverageIndex << "]"
|
|
590
|
+
<< " | " << std::endl;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Store function info for later output
|
|
594
|
+
FunctionInfo funcInfo;
|
|
595
|
+
funcInfo.name = funcName;
|
|
596
|
+
funcInfo.representativeLocation = representativeLocation;
|
|
597
|
+
funcInfo.coverageMemoryIndex = coverageIndex;
|
|
598
|
+
funcInfo.expressions = walker.expressions;
|
|
599
|
+
funcInfo.blocks = walker.blocks;
|
|
600
|
+
|
|
601
|
+
// add to list
|
|
602
|
+
instrumentedFunctions.push_back(funcInfo);
|
|
603
|
+
|
|
604
|
+
// Coverage instrumentation:
|
|
605
|
+
// counter = i32.load(addr, __coverage_memory)
|
|
606
|
+
// incremented = counter + 1
|
|
607
|
+
// i32.store(addr, incremented, __coverage_memory)
|
|
608
|
+
|
|
609
|
+
const uint32_t counterAddressVal = coverageIndex * BYTES_PER_COUNTER;
|
|
610
|
+
Expression* counterAddress = builder.makeConstantExpression(Literal(counterAddressVal));
|
|
611
|
+
|
|
612
|
+
// Load current counter value
|
|
613
|
+
Expression* counterValue = builder.makeLoad(
|
|
614
|
+
BYTES_PER_COUNTER, // bytes - size
|
|
615
|
+
false, // signed - false, treat as unsigned (and no extension needed anyway)
|
|
616
|
+
0, // offset - none, we already calculate the address based on data size
|
|
617
|
+
BYTES_PER_COUNTER, // align - we should always be aligned
|
|
618
|
+
counterAddress, // address
|
|
619
|
+
Type::i32,
|
|
620
|
+
coverageMemoryName
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
// Increment counter
|
|
624
|
+
Expression* incrementedCounter = builder.makeBinary(
|
|
625
|
+
AddInt32,
|
|
626
|
+
counterValue,
|
|
627
|
+
builder.makeConst(1)
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
Expression* storeCounter = builder.makeStore(
|
|
631
|
+
BYTES_PER_COUNTER, // bytes
|
|
632
|
+
0, // offset
|
|
633
|
+
BYTES_PER_COUNTER, // align hint
|
|
634
|
+
counterAddress, // address
|
|
635
|
+
incrementedCounter, // value
|
|
636
|
+
Type::i32,
|
|
637
|
+
coverageMemoryName
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
// Prepend instrumentation to function body
|
|
641
|
+
func->body = builder.makeSequence(storeCounter, func->body, func->body->type);
|
|
642
|
+
|
|
643
|
+
if (DEBUG) {
|
|
644
|
+
std::cout << LOG_PREFIX << " - INSTRUMENTED \"" << funcName << "\" | coverageMemoryIndex [" << coverageIndex << "]"
|
|
645
|
+
<< " | reprLoc=" << representativeLocation.fileIndex << ":" << representativeLocation.lineNumber
|
|
646
|
+
<< ":" << representativeLocation.columnNumber << std::endl;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
coverageIndex++;
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
if (DEBUG) {
|
|
653
|
+
std::cout << LOG_PREFIX << " - Instrumentation complete: " << coverageIndex << " functions instrumented" << std::endl;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Write instrumented module with source map regeneration
|
|
657
|
+
BufferWithRandomAccess outputBuffer;
|
|
658
|
+
PassOptions passOptions;
|
|
659
|
+
WasmBinaryWriter writer(&module, outputBuffer, passOptions);
|
|
660
|
+
writer.setNamesSection(true);
|
|
661
|
+
|
|
662
|
+
// Set up source map output stream
|
|
663
|
+
std::ostringstream sourceMapStream;
|
|
664
|
+
writer.setSourceMap(&sourceMapStream, "output.wasm");
|
|
665
|
+
|
|
666
|
+
writer.write();
|
|
667
|
+
|
|
668
|
+
// Build result object
|
|
669
|
+
Napi::Object result = Napi::Object::New(env);
|
|
670
|
+
|
|
671
|
+
// Convert instrumented binary to Buffer
|
|
672
|
+
Napi::Buffer<char> instrumentedWasm = Napi::Buffer<char>::Copy(
|
|
673
|
+
env,
|
|
674
|
+
reinterpret_cast<const char*>(outputBuffer.data()),
|
|
675
|
+
outputBuffer.size()
|
|
676
|
+
);
|
|
677
|
+
result.Set("instrumentedWasm", instrumentedWasm);
|
|
678
|
+
|
|
679
|
+
// Convert source map to string
|
|
680
|
+
std::string sourceMapStr = sourceMapStream.str();
|
|
681
|
+
result.Set("sourceMap", Napi::String::New(env, sourceMapStr));
|
|
682
|
+
|
|
683
|
+
// Build debug info object (similar structure to ExtractDebugInfo output)
|
|
684
|
+
Napi::Object debugInfo = Napi::Object::New(env);
|
|
685
|
+
|
|
686
|
+
// Add debug source files
|
|
687
|
+
Napi::Array debugSourceFiles = Napi::Array::New(env, module.debugInfoFileNames.size());
|
|
688
|
+
for (size_t i = 0; i < module.debugInfoFileNames.size(); i++) {
|
|
689
|
+
debugSourceFiles[i] = Napi::String::New(env, module.debugInfoFileNames[i]);
|
|
690
|
+
}
|
|
691
|
+
debugInfo.Set("debugSourceFiles", debugSourceFiles);
|
|
692
|
+
|
|
693
|
+
// Add function information
|
|
694
|
+
Napi::Array functions = Napi::Array::New(env, instrumentedFunctions.size());
|
|
695
|
+
|
|
696
|
+
for (size_t i = 0; i < instrumentedFunctions.size(); i++) {
|
|
697
|
+
const auto& funcInfo = instrumentedFunctions[i];
|
|
698
|
+
Napi::Object funcObj = Napi::Object::New(env);
|
|
699
|
+
|
|
700
|
+
funcObj.Set("name", Napi::String::New(env, funcInfo.name));
|
|
701
|
+
funcObj.Set("wasmIndex", Napi::Number::New(env, i));
|
|
702
|
+
funcObj.Set("coverageMemoryIndex", Napi::Number::New(env, funcInfo.coverageMemoryIndex));
|
|
703
|
+
|
|
704
|
+
Napi::Object reprLoc = Napi::Object::New(env);
|
|
705
|
+
reprLoc.Set("fileIndex", Napi::Number::New(env, funcInfo.representativeLocation.fileIndex));
|
|
706
|
+
reprLoc.Set("line", Napi::Number::New(env, funcInfo.representativeLocation.lineNumber));
|
|
707
|
+
reprLoc.Set("column", Napi::Number::New(env, funcInfo.representativeLocation.columnNumber));
|
|
708
|
+
funcObj.Set("representativeLocation", reprLoc);
|
|
709
|
+
|
|
710
|
+
// Add expressions array
|
|
711
|
+
Napi::Array expressions = Napi::Array::New(env, funcInfo.expressions.size());
|
|
712
|
+
for (size_t j = 0; j < funcInfo.expressions.size(); j++) {
|
|
713
|
+
const auto& expr = funcInfo.expressions[j];
|
|
714
|
+
Napi::Object exprObj = Napi::Object::New(env);
|
|
715
|
+
|
|
716
|
+
exprObj.Set("type", Napi::String::New(env, expr.type));
|
|
717
|
+
exprObj.Set("isBranch", Napi::Boolean::New(env, expr.isBranch));
|
|
718
|
+
if (expr.isBranch) {
|
|
719
|
+
exprObj.Set("branchPaths", Napi::Number::New(env, expr.branchPaths));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (expr.hasDebugLocation) {
|
|
723
|
+
Napi::Object location = Napi::Object::New(env);
|
|
724
|
+
location.Set("fileIndex", Napi::Number::New(env, expr.fileIndex));
|
|
725
|
+
location.Set("line", Napi::Number::New(env, expr.lineNumber));
|
|
726
|
+
location.Set("column", Napi::Number::New(env, expr.columnNumber));
|
|
727
|
+
exprObj.Set("location", location);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
expressions[j] = exprObj;
|
|
731
|
+
}
|
|
732
|
+
funcObj.Set("expressions", expressions);
|
|
733
|
+
|
|
734
|
+
// Add basic blocks array
|
|
735
|
+
Napi::Array basicBlocks = Napi::Array::New(env, funcInfo.blocks.size());
|
|
736
|
+
for (size_t j = 0; j < funcInfo.blocks.size(); j++) {
|
|
737
|
+
const auto& block = funcInfo.blocks[j];
|
|
738
|
+
Napi::Object blockObj = Napi::Object::New(env);
|
|
739
|
+
|
|
740
|
+
blockObj.Set("index", Napi::Number::New(env, j));
|
|
741
|
+
|
|
742
|
+
Napi::Array exprIndices = Napi::Array::New(env, block.expressionIndices.size());
|
|
743
|
+
for (size_t k = 0; k < block.expressionIndices.size(); k++) {
|
|
744
|
+
exprIndices[k] = Napi::Number::New(env, block.expressionIndices[k]);
|
|
745
|
+
}
|
|
746
|
+
blockObj.Set("expressionIndices", exprIndices);
|
|
747
|
+
|
|
748
|
+
Napi::Array branches = Napi::Array::New(env, block.branches.size());
|
|
749
|
+
for (size_t k = 0; k < block.branches.size(); k++) {
|
|
750
|
+
Napi::Object branchObj = Napi::Object::New(env);
|
|
751
|
+
branchObj.Set("targetBlockIndex", Napi::Number::New(env, block.branches[k]));
|
|
752
|
+
branches[k] = branchObj;
|
|
753
|
+
}
|
|
754
|
+
blockObj.Set("branches", branches);
|
|
755
|
+
|
|
756
|
+
basicBlocks[j] = blockObj;
|
|
757
|
+
}
|
|
758
|
+
funcObj.Set("basicBlocks", basicBlocks);
|
|
759
|
+
|
|
760
|
+
functions[i] = funcObj;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
debugInfo.Set("functions", functions);
|
|
764
|
+
|
|
765
|
+
result.Set("debugInfo", debugInfo);
|
|
766
|
+
|
|
767
|
+
return result;
|
|
768
|
+
|
|
769
|
+
} catch (const std::exception& e) {
|
|
770
|
+
Napi::Error::New(env, std::string("Failed to instrument for coverage: ") + e.what())
|
|
771
|
+
.ThrowAsJavaScriptException();
|
|
772
|
+
return Napi::Object::New(env);
|
|
773
|
+
} catch (...) {
|
|
774
|
+
Napi::Error::New(env, "Failed to instrument for coverage: Unknown error")
|
|
775
|
+
.ThrowAsJavaScriptException();
|
|
776
|
+
return Napi::Object::New(env);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Initialize the addon
|
|
782
|
+
*/
|
|
783
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
784
|
+
exports.Set("instrumentForCoverage", Napi::Function::New(env, InstrumentForCoverage));
|
|
785
|
+
return exports;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
NODE_API_MODULE(wasm_binaryen_debug, Init);
|