Skip to content

Commit 5172d4d

Browse files
authored
fix import-auto-injection cannot handle i64 (#13)
1 parent d85299f commit 5172d4d

File tree

9 files changed

+200
-57
lines changed

9 files changed

+200
-57
lines changed

package-lock.json

Lines changed: 23 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"test:cpp": "cmake -B build -S . && cmake --build build --target wasm-instrumentation-test wasm-opt && build/bin/wasm-instrumentation-test",
1919
"test": "npm run test:as && npm run test:ts && npm run test:cpp",
2020
"lint": "eslint src assembly tests/ts/test --max-warnings=0 && prettier -c .",
21-
"lint:fix": "eslint src assembly --fix && npx prettier --write .",
21+
"lint:fix": "eslint src assembly --fix && prettier --write .",
2222
"example": "node bin/as-test.js --config example/as-test.config.cjs ; node bin/as-test.js --config example/as-test.config.js"
2323
},
2424
"dependencies": {
@@ -43,6 +43,7 @@
4343
"@types/glob": "^7.2.0",
4444
"@types/jest": "^29.5.2",
4545
"@types/node": "^20.9.1",
46+
"wasmparser": "5.11.1",
4647
"assemblyscript-prettier": "^3.0.1",
4748
"cross-env": "^7.0.3",
4849
"jest": "^29.5.0",

src/core/execute.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { AssertResult } from "../assertResult.js";
77
import { Imports, ImportsArgument } from "../index.js";
88
import { IAssertResult, InstrumentResult } from "../interface.js";
99
import { mockInstruFunc, covInstruFunc } from "../utils/import.js";
10-
import { parseWasmImports, supplyDefaultFunction } from "../utils/index.js";
10+
import { supplyDefaultFunction } from "../utils/index.js";
11+
import { parseImportFunctionInfo } from "../utils/wasmparser.js";
1112
const readFile = promises.readFile;
1213

1314
function nodeExecutor(wasms: string[], outFolder: string, imports: Imports) {
@@ -31,9 +32,8 @@ function nodeExecutor(wasms: string[], outFolder: string, imports: Imports) {
3132
...userDefinedImportsObject,
3233
} as ASImports;
3334
const binary = await readFile(wasm);
34-
const importList = await parseWasmImports(binary);
35-
// supplying default function here, so no more need to define all of them in as-test.js
36-
supplyDefaultFunction(importList, importObject);
35+
const importFuncList = parseImportFunctionInfo(binary);
36+
supplyDefaultFunction(importFuncList, importObject);
3737
const ins = await instantiate(binary, importObject);
3838
importsArg.module = ins.module;
3939
importsArg.instance = ins.instance;

src/interface.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
*/
44
// input
55

6+
import { Type } from "wasmparser";
7+
68
// instrumented file information
79
export interface InstrumentResult {
810
sourceWasm: string;
@@ -64,6 +66,13 @@ export interface IAssertResult {
6466
failed_info: AssertFailMessage;
6567
}
6668

69+
export interface ImportFunctionInfo {
70+
module: string;
71+
name: string;
72+
args: Type[];
73+
return: Type | undefined;
74+
}
75+
6776
// output
6877
export class Rate {
6978
used = 0;

src/utils/index.ts

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Imports as ASImports } from "@assemblyscript/loader";
2+
import { ImportFunctionInfo } from "../interface.js";
3+
import { TypeKind } from "wasmparser/dist/cjs/WasmParser.js";
24

35
export function json2map<V>(json: Record<string, V>): Map<string, V> {
46
const res = new Map<string, V>();
@@ -43,36 +45,18 @@ export function checkGenerics(functionName: string): string | undefined {
4345
return;
4446
}
4547

46-
// list imports of a given wasm binary (buffer)
47-
// importList format should be as follows:
48-
// [
49-
// { module: 'env', name: 'memory', kind: 'memory' },
50-
// { module: 'env', name: 'myFunction', kind: 'function' },
51-
// ...
52-
// ]
53-
export async function parseWasmImports(binary: Buffer) {
54-
const mod = await WebAssembly.compile(binary);
55-
const importList = WebAssembly.Module.imports(mod);
56-
57-
return importList;
58-
}
59-
60-
export function supplyDefaultFunction(importList: WebAssembly.ModuleImportDescriptor[], importObject: ASImports) {
61-
for (const imp of importList) {
62-
if (imp.kind === "function") {
63-
const moduleName = imp.module;
64-
const funcName = imp.name;
65-
if (importObject[moduleName]?.[funcName] === undefined) {
66-
if (importObject[moduleName] === undefined) {
67-
importObject[moduleName] = {};
68-
}
69-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
70-
(importObject[moduleName] as any)[funcName] = (...args: any[]): any => {
71-
// notify that a default function has been called
72-
console.log(`Default stub called for ${moduleName}.${funcName}, args:`, args);
73-
return 0;
74-
};
48+
export function supplyDefaultFunction(infos: ImportFunctionInfo[], importObject: ASImports) {
49+
for (const info of infos) {
50+
const module = info.module;
51+
const name = info.name;
52+
if (importObject[module]?.[name] === undefined) {
53+
if (importObject[module] === undefined) {
54+
importObject[module] = {};
7555
}
56+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
57+
(importObject[module] as any)[name] = (..._args: unknown[]): unknown => {
58+
return info.return?.kind === TypeKind.i64 ? BigInt(0) : 0;
59+
};
7660
}
7761
}
7862
}

src/utils/wasmparser.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {
2+
BinaryReader,
3+
BinaryReaderState,
4+
IImportEntry,
5+
ExternalKind,
6+
ISectionInformation,
7+
SectionCode,
8+
ITypeEntry,
9+
} from "wasmparser";
10+
import { ImportFunctionInfo } from "../interface.js";
11+
import assert from "node:assert";
12+
13+
export function parseImportFunctionInfo(buf: Uint8Array) {
14+
const reader = new BinaryReader();
15+
const types: ITypeEntry[] = [];
16+
const result: ImportFunctionInfo[] = [];
17+
reader.setData(buf, 0, buf.length);
18+
// eslint-disable-next-line no-constant-condition
19+
while (true) {
20+
if (!reader.read()) {
21+
return result;
22+
}
23+
switch (reader.state) {
24+
case BinaryReaderState.END_WASM: {
25+
break;
26+
}
27+
case BinaryReaderState.ERROR: {
28+
throw reader.error;
29+
}
30+
case BinaryReaderState.END_SECTION: {
31+
break;
32+
}
33+
case BinaryReaderState.BEGIN_SECTION: {
34+
const sectionInfo = reader.result as ISectionInformation;
35+
switch (sectionInfo.id) {
36+
case SectionCode.Type:
37+
case SectionCode.Import: {
38+
break;
39+
}
40+
default: {
41+
reader.skipSection();
42+
break;
43+
}
44+
}
45+
break;
46+
}
47+
case BinaryReaderState.TYPE_SECTION_ENTRY: {
48+
const typeEntry = reader.result as ITypeEntry;
49+
types.push(typeEntry);
50+
break;
51+
}
52+
case BinaryReaderState.IMPORT_SECTION_ENTRY: {
53+
const importInfo = reader.result as IImportEntry;
54+
const decoder = new TextDecoder("utf8");
55+
if (importInfo.kind === ExternalKind.Function) {
56+
const typeIdx = importInfo.funcTypeIndex;
57+
assert(typeIdx !== undefined, "ImportFunction must have a typeIndex");
58+
const typeItem = types[typeIdx];
59+
assert(typeItem !== undefined, "ImportFunction must have a typeItem");
60+
assert(typeItem.params !== undefined);
61+
assert(typeItem.returns !== undefined);
62+
const returnValue = typeItem.returns.length === 0 ? undefined : typeItem.returns[0];
63+
result.push({
64+
module: decoder.decode(importInfo.module),
65+
name: decoder.decode(importInfo.field),
66+
args: typeItem.params,
67+
return: returnValue,
68+
});
69+
}
70+
break;
71+
}
72+
default: {
73+
break;
74+
}
75+
}
76+
}
77+
}
129 Bytes
Binary file not shown.

tests/ts/test/utils/utils.test.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import fs from "fs-extra";
22
import { join } from "node:path";
33
import { Imports as ASImports } from "@assemblyscript/loader";
44
import { fileURLToPath, URL } from "node:url";
5-
import { DebugInfo, CovDebugInfo } from "../../../../src/interface.js";
5+
import { DebugInfo, CovDebugInfo, ImportFunctionInfo } from "../../../../src/interface.js";
66
import {
77
isIncluded,
88
json2map,
99
checkFunctionName,
1010
checkGenerics,
1111
supplyDefaultFunction,
1212
} from "../../../../src/utils/index.js";
13+
import { Type } from "wasmparser";
1314

1415
const __dirname = fileURLToPath(new URL(".", import.meta.url));
1516

@@ -75,31 +76,28 @@ test("checkGenerics", () => {
7576

7677
describe("supplyDefaultFunction", () => {
7778
test("supplyTest", () => {
78-
const mockImportList: WebAssembly.ModuleImportDescriptor[] = [
79-
{ kind: "function", module: "myenv", name: "processEvent" },
80-
{ kind: "function", module: "externalMath", name: "add" },
81-
{ kind: "function", module: "system", name: "getStatus" },
82-
{ kind: "function", module: "logger", name: "logWarning" },
83-
{ kind: "function", module: "customOps", name: "combineValues" },
84-
{ kind: "global", module: "myenv", name: "globalVar" },
85-
{ kind: "memory", module: "other", name: "memChange" },
79+
const mockInfos: ImportFunctionInfo[] = [
80+
{ module: "ns", name: "ut.i32", args: [new Type(-1)], return: new Type(-1) },
81+
{ module: "ns", name: "ut.i64", args: [new Type(-2)], return: new Type(-2) },
82+
{ module: "ns", name: "ut.f32", args: [new Type(-3)], return: new Type(-3) },
83+
{ module: "ns", name: "ut.f64", args: [new Type(-4)], return: new Type(-4) },
8684
];
8785

8886
const mockImportObject: ASImports = {
89-
myenv: {},
90-
externalMath: {},
91-
system: {},
92-
logger: {},
93-
customOps: {},
87+
env: {},
88+
wasi_snapshot_preview1: {},
9489
};
90+
supplyDefaultFunction(mockInfos, mockImportObject);
9591

96-
supplyDefaultFunction(mockImportList, mockImportObject);
97-
98-
expect(typeof mockImportObject["myenv"]?.["processEvent"]).toBe("function");
99-
expect(typeof mockImportObject["system"]?.["getStatus"]).toBe("function");
100-
expect(typeof mockImportObject["logger"]?.["logWarning"]).toBe("function");
101-
expect(typeof mockImportObject["customOps"]?.["combineValues"]).toBe("function");
102-
expect(mockImportObject["myenv"]?.["globalVar"]).toBeUndefined();
103-
expect(mockImportObject["other"]?.["memChange"]).toBeUndefined();
92+
expect(typeof mockImportObject["ns"]?.["ut.i32"]).toBe("function");
93+
expect(typeof mockImportObject["ns"]?.["ut.i64"]).toBe("function");
94+
expect(typeof mockImportObject["ns"]?.["ut.f32"]).toBe("function");
95+
expect(typeof mockImportObject["ns"]?.["ut.f64"]).toBe("function");
96+
/* eslint-disable @typescript-eslint/ban-types */
97+
expect((mockImportObject["ns"]?.["ut.i32"] as Function)(0)).toEqual(0);
98+
expect((mockImportObject["ns"]?.["ut.i64"] as Function)(0)).toEqual(BigInt(0));
99+
expect((mockImportObject["ns"]?.["ut.f32"] as Function)(0)).toEqual(0);
100+
expect((mockImportObject["ns"]?.["ut.f64"] as Function)(0)).toEqual(0);
101+
/* eslint-enable @typescript-eslint/ban-types */
104102
});
105103
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { parseImportFunctionInfo } from "../../../../src/utils/wasmparser.js";
2+
import { readFileSync } from "node:fs";
3+
import { join } from "node:path";
4+
import { fileURLToPath, URL } from "node:url";
5+
import { Type } from "wasmparser";
6+
7+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
8+
9+
test("parseImportFunctionInfo", () => {
10+
const fp = join(__dirname, "..", "..", "fixture", "defaultImportTest.wasm");
11+
const buf = readFileSync(fp);
12+
const expectedInfo = [
13+
{
14+
module: "env",
15+
name: "abort",
16+
args: [new Type(-1), new Type(-1), new Type(-1), new Type(-1)],
17+
return: undefined,
18+
},
19+
{
20+
module: "env",
21+
name: "logInfo",
22+
args: [new Type(-1), new Type(-1)],
23+
return: undefined,
24+
},
25+
{
26+
module: "env",
27+
name: "getTimeSinceEpoch",
28+
args: [],
29+
return: new Type(-2),
30+
},
31+
{
32+
module: "ns",
33+
name: "ut.i32",
34+
args: [],
35+
return: new Type(-1),
36+
},
37+
{
38+
module: "ns",
39+
name: "ut.f32",
40+
args: [new Type(-2)],
41+
return: new Type(-3),
42+
},
43+
{
44+
module: "ns",
45+
name: "ut.f64",
46+
args: [],
47+
return: new Type(-4),
48+
},
49+
];
50+
51+
expect(parseImportFunctionInfo(buf)).toEqual(expectedInfo);
52+
});

0 commit comments

Comments
 (0)