Skip to content

Commit 78b037d

Browse files
committed
Handle prototype chain of imports object
1 parent a57de18 commit 78b037d

File tree

4 files changed

+68
-73
lines changed

4 files changed

+68
-73
lines changed

lib/asbind-instance/asbind-instance.js

Lines changed: 41 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@ import { isReservedExportKey } from "./reserved-export-keys";
66

77
const SECTION_NAME = "as-bind_bindings";
88

9-
// Need to traverse the importObject and bind all import functions
10-
function traverseObjectAndRunCallback(obj, callback, keys = [], baseObj = obj) {
11-
if (!obj) {
12-
return;
13-
}
14-
for (const key of Object.keys(obj)) {
15-
if (obj[key] && Object.getPrototypeOf(obj[key]) === Object.prototype) {
16-
traverseObjectAndRunCallback(obj[key], callback, [...keys, key], baseObj);
17-
continue;
18-
}
19-
callback(obj[key], [...keys, key], baseObj, key, obj);
9+
// Basically a deep-copy, but can be limited in levels.
10+
function copyObject(obj, { depth = Number.POSITIVE_INFINITY } = {}) {
11+
if (depth <= 0 || !obj || typeof obj !== "object") {
12+
return obj;
2013
}
14+
return Object.fromEntries(
15+
Object.entries(obj).map(([key, val]) => [
16+
key,
17+
copyObject(val, { depth: depth - 1 })
18+
])
19+
);
2120
}
2221

2322
async function compileStreaming(source) {
@@ -80,19 +79,17 @@ export default class AsbindInstance {
8079
}
8180

8281
async _instantiate(source, importObject) {
83-
this.importObject = importObject;
8482
this.module = await compileStreaming(source);
8583

8684
this._validate();
8785
this.typeDescriptor = extractTypeDescriptor(this.module);
88-
this._instantiateBindImportFunctions();
86+
this._instantiateBindImportFunctions(importObject);
8987
// Instantiate the module through the loader
9088
this.loadedModule = await asbindInstantiate(this.module, this.importObject);
9189
this._instantiateBindUnboundExports();
9290
}
9391

9492
_instantiateSync(source, importObject) {
95-
this.importObject = importObject;
9693
this.module = new WebAssembly.Module(source);
9794

9895
this._validate();
@@ -103,46 +100,41 @@ export default class AsbindInstance {
103100
}
104101

105102
_instantiateBindImportFunctions(importObject) {
106-
// Need to traverse the importObject and bind all import functions
107-
traverseObjectAndRunCallback(
108-
this.importObject,
109-
(importedFunction, keys, importObject, currentKey, currentObj) => {
110-
if (typeof importedFunction === "function") {
111-
// Save original function
112-
// TODO: Maybe use symbols here instead
113-
currentObj[`__asbind_unbound_${currentKey}`] = importedFunction;
114-
currentObj[currentKey] = bindImportFunction(
115-
this,
116-
importedFunction,
117-
keys
118-
);
119-
}
103+
this.importObject = copyObject(importObject, { depth: 2 });
104+
105+
for (const [moduleName, moduleDescriptor] of Object.entries(
106+
this.typeDescriptor.importedFunctions
107+
)) {
108+
for (const [importedFunctionName, descriptor] of Object.entries(
109+
moduleDescriptor
110+
)) {
111+
this.importObject[moduleName][
112+
`__asbind_unbound_${importedFunctionName}`
113+
] = importObject[moduleName][importedFunctionName];
114+
this.importObject[moduleName][
115+
importedFunctionName
116+
] = bindImportFunction(
117+
this,
118+
importObject[moduleName][importedFunctionName],
119+
descriptor
120+
);
120121
}
121-
);
122+
}
122123
}
123124

124125
_instantiateBindUnboundExports() {
125126
// Wrap appropriate the appropriate export functions
126127
const unboundExports = this.loadedModule.exports;
127-
this.exports = {};
128-
129-
traverseObjectAndRunCallback(
130-
unboundExports,
131-
(exportedValue, keys, importObject, currentKey, currentObj) => {
132-
if (
133-
typeof exportedValue === "function" &&
134-
!isReservedExportKey(currentKey)
135-
) {
136-
// Wrap the export
137-
this.exports[currentKey] = bindExportFunction(
138-
this,
139-
exportedValue,
140-
currentKey
141-
);
142-
} else {
143-
this.exports[currentKey] = exportedValue;
144-
}
145-
}
146-
);
128+
this.exports = copyObject(unboundExports, { depth: 1 });
129+
130+
for (const [exportedFunctionName, descriptor] of Object.entries(
131+
this.typeDescriptor.exportedFunctions
132+
)) {
133+
this.exports[exportedFunctionName] = bindExportFunction(
134+
this,
135+
unboundExports[exportedFunctionName],
136+
descriptor
137+
);
138+
}
147139
}
148140
}

lib/asbind-instance/bind-function.js

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,11 @@ function getFunctionFromKeyPath(baseObject, keys) {
1111
return result;
1212
}
1313

14-
// Function that takes in an asbindInstance, an imported function and the path to the imported function
15-
// on the import object.
16-
// (E.g ["env", "myObject", "myFunction"] for {env: myObject: {myFunction: () => {}}})
1714
export function bindImportFunction(
1815
asbindInstance,
1916
importedFunction,
20-
importObjectKeyPathToFunction
17+
importedFunctionDescriptor
2118
) {
22-
const importedFunctionDescriptor = getFunctionFromKeyPath(
23-
asbindInstance.typeDescriptor.importedFunctions,
24-
importObjectKeyPathToFunction
25-
);
26-
if (!importedFunctionDescriptor) {
27-
console.warn(
28-
`Unexpected function ${importObjectKeyPathToFunction.join(
29-
"."
30-
)} on import object, using pass-through.`
31-
);
32-
return importedFunction;
33-
}
34-
3519
// Grab type converter functions according to the type descriptor
3620
const argumentConverterFunctions = importedFunctionDescriptor.parameters.map(
3721
getAscToJsConverterForType
@@ -69,14 +53,8 @@ export function bindImportFunction(
6953
export function bindExportFunction(
7054
asbindInstance,
7155
exportedFunction,
72-
exportFunctionKey
56+
exportedFunctionDescriptor
7357
) {
74-
const exportedFunctionDescriptor =
75-
asbindInstance.typeDescriptor.exportedFunctions[exportFunctionKey];
76-
if (!exportedFunctionDescriptor) {
77-
throw Error(`Unknown function ${exportFunctionKey}`);
78-
}
79-
8058
// Grab type converter functions according to the type descriptor
8159
const argumentConverterFunctions = exportedFunctionDescriptor.parameters.map(
8260
getJsToAscConverterForType

test/tests/import-on-prototype/asc.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
declare function thing(): string;
2+
declare const otherThing: u32;
3+
4+
export function add(): string {
5+
return otherThing.toString() + thing();
6+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
describe("as-bind", function() {
2+
it("should handle the prototype chain on the imports object", async function() {
3+
debugger;
4+
const asc = {
5+
otherThing: new WebAssembly.Global({ value: "i32", mutable: false }, 4)
6+
};
7+
// This function will be “seen” by the WebAssembly engine as an imported
8+
// function but is not visible via Object.keys() or similar.
9+
Object.setPrototypeOf(asc, {
10+
thing() {
11+
return "2";
12+
}
13+
});
14+
assert(asc.thing() === "2");
15+
assert(Object.keys(asc).join(",") === "otherThing");
16+
const instance = await AsBind.instantiate(this.rawModule, { asc });
17+
assert(instance.exports.add() === "42");
18+
});
19+
});

0 commit comments

Comments
 (0)