Skip to content

Commit 006c5f6

Browse files
committed
wip
1 parent 15edee1 commit 006c5f6

File tree

7 files changed

+146
-65
lines changed

7 files changed

+146
-65
lines changed

instrumentation/CoverageInstru.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "CoverageInstru.hpp"
2+
#include <binaryen-c.h>
23
#include <fstream>
34
#include <ostream>
45
namespace wasmInstrumentation {
@@ -128,6 +129,7 @@ InstrumentationResponse CoverageInstru::instrument() const noexcept {
128129
mockWalker.mockWalk();
129130

130131
const std::string targetSourceMapPath = std::string{this->config->targetName} + ".map";
132+
BinaryenSetDebugInfo(true);
131133
const BinaryenModuleAllocateAndWriteResult result =
132134
BinaryenModuleAllocateAndWrite(&module, targetSourceMapPath.c_str());
133135
std::ofstream wasmFileStream(this->config->targetName.data(), std::ios::trunc | std::ios::binary);

package-lock.json

Lines changed: 25 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"glob": "^11.0.0",
3838
"ignore": "^7.0.3",
3939
"semver": "^7.5.3",
40+
"source-map": "^0.7.4",
4041
"wasmparser": "5.11.1"
4142
},
4243
"peerDependencies": {

src/core/execute.ts

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -49,52 +49,51 @@ async function nodeExecutor(
4949
importsArg.module = ins.module;
5050
importsArg.instance = ins.instance;
5151
importsArg.exports = ins.exports;
52+
5253
let isCrashed = false; // we don't want to crash any code after crash. AS' heap may be broken.
53-
try {
54-
executionRecorder.startTestFunction(`${instrumentResult.baseName} - init`);
55-
wasi.start(ins);
56-
} catch (error) {
54+
55+
const exceptionHandler = async (error: unknown) => {
5756
if (error instanceof WebAssembly.RuntimeError) {
5857
isCrashed = true;
5958
const errorMessage: ExecutionError = await handleWebAssemblyError(error, instrumentResult.instrumentedWasm);
6059
executionRecorder.notifyTestCrash(errorMessage);
61-
} else {
62-
// unrecoverable error, rethrow
63-
if (error instanceof Error) {
64-
console.error(error.stack);
65-
}
66-
throw new Error("node executor abort");
60+
return;
61+
}
62+
// unrecoverable error, rethrow
63+
if (error instanceof Error) {
64+
console.error(error.stack);
6765
}
66+
throw new Error("node executor abort");
67+
};
68+
69+
try {
70+
executionRecorder.startTestFunction(`${instrumentResult.baseName} - init`);
71+
wasi.start(ins);
72+
} catch (error) {
73+
exceptionHandler(error);
6874
}
6975
executionRecorder.finishTestFunction();
70-
// try {
71-
// const execTestFunction = ins.exports["executeTestFunction"];
72-
// assert(typeof execTestFunction === "function");
73-
// if (matchedTestNames === undefined) {
74-
// // By default, all testcases are executed
75-
// for (const functionInfo of executionRecorder.registerFunctions) {
76-
// const [testCaseName, functionIndex] = functionInfo;
77-
// executionRecorder.startTestFunction(testCaseName);
78-
// (execTestFunction as (a: number) => void)(functionIndex);
79-
// executionRecorder.finishTestFunction();
80-
// mockInstrumentFunc["mockFunctionStatus.clear"]();
81-
// }
82-
// } else {
83-
// for (const functionInfo of executionRecorder.registerFunctions) {
84-
// const [testCaseName, functionIndex] = functionInfo;
85-
// if (matchedTestNames.includes(testCaseName)) {
86-
// executionRecorder.startTestFunction(testCaseName);
87-
// (execTestFunction as (a: number) => void)(functionIndex);
88-
// executionRecorder.finishTestFunction();
89-
// mockInstrumentFunc["mockFunctionStatus.clear"]();
90-
// }
91-
// }
92-
// }
93-
// } catch (error) {
94-
// if (error instanceof Error) {
95-
// console.error(error.stack);
96-
// }
97-
// }
76+
77+
const execTestFunction = ins.exports["executeTestFunction"];
78+
assert(typeof execTestFunction === "function");
79+
80+
for (const functionInfo of executionRecorder.registerFunctions) {
81+
if (isCrashed) {
82+
break;
83+
}
84+
const [testCaseName, functionIndex] = functionInfo;
85+
if (matchedTestNames === undefined || matchedTestNames.includes(testCaseName)) {
86+
executionRecorder.startTestFunction(testCaseName);
87+
try {
88+
(execTestFunction as (a: number) => void)(functionIndex);
89+
} catch (error) {
90+
exceptionHandler(error);
91+
}
92+
executionRecorder.finishTestFunction();
93+
mockInstrumentFunc["mockFunctionStatus.clear"]();
94+
}
95+
}
96+
9897
coverageRecorder.outputTrace(instrumentResult.traceFile);
9998
return executionRecorder.result;
10099
}

src/utils/errorTraceHandler.ts

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { readFile } from "node:fs/promises";
44
import { parseSourceMapPath } from "./wasmparser.js";
5+
import { BasicSourceMapConsumer, IndexedSourceMapConsumer, SourceMapConsumer } from "source-map";
56

67
export interface WebAssemblyCallSite {
78
functionName: string;
@@ -12,7 +13,41 @@ export interface WebAssemblyCallSite {
1213

1314
interface WebAssemblyModuleInfo {
1415
wasmPath: string;
15-
sourceMapUrl: string | null;
16+
sourceMapConsumer: SourceMapHandler | null;
17+
}
18+
19+
type SourceMapHandler = BasicSourceMapConsumer | IndexedSourceMapConsumer;
20+
21+
interface SourceLocation {
22+
fileName: string;
23+
lineNumber: number;
24+
columnNumber: number;
25+
}
26+
27+
function getOriginLocationWithSourceMap(
28+
line: number | null,
29+
column: number | null,
30+
sourceMapConsumer: SourceMapHandler | null
31+
): SourceLocation | null {
32+
if (sourceMapConsumer === null || line === null || column === null) {
33+
return null;
34+
}
35+
const originPosition = sourceMapConsumer.originalPositionFor({
36+
line: line,
37+
column: column,
38+
});
39+
if (originPosition.source === null || originPosition.line === null || originPosition.column === null) {
40+
return null;
41+
}
42+
return {
43+
fileName: originPosition.source,
44+
lineNumber: originPosition.line,
45+
columnNumber: originPosition.column,
46+
};
47+
}
48+
49+
function getWebAssemblyFunctionName(callSite: NodeJS.CallSite): string {
50+
return callSite.getFunctionName() ?? `wasm-function[${callSite.getFunction()}]`;
1651
}
1752

1853
function createWebAssemblyCallSite(
@@ -23,45 +58,65 @@ function createWebAssemblyCallSite(
2358
// ignore non-wasm call sites
2459
return null;
2560
}
26-
if (moduleInfo.sourceMapUrl !== null) {
27-
// use source map
61+
const line: number | null = callSite.getLineNumber();
62+
const column: number | null = callSite.getColumnNumber();
63+
const originalPosition: SourceLocation | null = getOriginLocationWithSourceMap(
64+
line,
65+
column,
66+
moduleInfo.sourceMapConsumer
67+
);
68+
if (originalPosition) {
2869
return {
29-
fileName: moduleInfo.wasmPath,
30-
functionName: `wasm-function[${callSite.getFunction()}]`,
31-
lineNumber: callSite.getLineNumber() || -1,
32-
columnNumber: callSite.getColumnNumber() || -1,
70+
fileName: originalPosition.fileName,
71+
functionName: getWebAssemblyFunctionName(callSite),
72+
lineNumber: originalPosition.lineNumber,
73+
columnNumber: originalPosition.columnNumber,
3374
};
3475
}
35-
// default
76+
// fallback to the original call site
3677
return {
3778
fileName: moduleInfo.wasmPath,
38-
functionName: `wasm-function[${callSite.getFunction()}]`,
39-
lineNumber: callSite.getLineNumber() || -1,
40-
columnNumber: callSite.getColumnNumber() || -1,
79+
functionName: getWebAssemblyFunctionName(callSite),
80+
lineNumber: line || -1,
81+
columnNumber: column || -1,
4182
};
4283
}
4384

44-
export class ExecutionError {
45-
constructor(
46-
public message: string,
47-
public stacks: WebAssemblyCallSite[]
48-
) {}
85+
export interface ExecutionError {
86+
message: string;
87+
stacks: WebAssemblyCallSite[];
88+
}
89+
90+
async function getSourceMapConsumer(sourceMapPath: string): Promise<BasicSourceMapConsumer | IndexedSourceMapConsumer> {
91+
return new Promise<BasicSourceMapConsumer | IndexedSourceMapConsumer>(async (resolve, reject) => {
92+
let sourceMapContent: string | null = null;
93+
try {
94+
sourceMapContent = await readFile(sourceMapPath, "utf8");
95+
} catch (error) {
96+
reject(error);
97+
return;
98+
}
99+
SourceMapConsumer.with(sourceMapContent, null, (consumer) => {
100+
resolve(consumer);
101+
});
102+
});
49103
}
50104

51105
export async function handleWebAssemblyError(
52106
error: WebAssembly.RuntimeError,
53107
wasmPath: string
54108
): Promise<ExecutionError> {
55109
const wasmBuffer = await readFile(wasmPath);
56-
const sourceMapUrl = parseSourceMapPath(wasmBuffer.buffer as ArrayBuffer);
110+
const sourceMapPath = parseSourceMapPath(wasmBuffer.buffer as ArrayBuffer);
111+
const sourceMapConsumer = sourceMapPath ? await getSourceMapConsumer(sourceMapPath) : null;
57112
const originalPrepareStackTrace = Error.prepareStackTrace;
58113
let stacks: WebAssemblyCallSite[] = [];
59114
Error.prepareStackTrace = (_: Error, structuredStackTrace: NodeJS.CallSite[]) => {
60115
stacks = structuredStackTrace
61-
.map((callSite) => createWebAssemblyCallSite(callSite, { wasmPath, sourceMapUrl }))
116+
.map((callSite) => createWebAssemblyCallSite(callSite, { wasmPath, sourceMapConsumer }))
62117
.filter((callSite) => callSite != null);
63118
};
64119
error.stack; // trigger prepareStackTrace
65120
Error.prepareStackTrace = originalPrepareStackTrace;
66-
return new ExecutionError(error.message, stacks);
121+
return { message: error.message, stacks };
67122
}
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
import { test, expect } from "../../../assembly";
2-
import { log } from "./env";
3-
41
assert(false);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test, expect } from "../../../assembly";
2+
import { log } from "./env";
3+
4+
test("assertOnTest", () => {
5+
log("This test will fail due to an assertion error");
6+
assert(false, "This assertion is expected to fail");
7+
});

0 commit comments

Comments
 (0)