UNPKG

5.25 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const t = require("@webassemblyjs/ast");
9const { moduleContextFromModuleAST } = require("@webassemblyjs/ast");
10const { decode } = require("@webassemblyjs/wasm-parser");
11const Parser = require("../Parser");
12const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
13const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
14const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
15
16/** @typedef {import("../Module")} Module */
17/** @typedef {import("../Parser").ParserState} ParserState */
18/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
19
20const JS_COMPAT_TYPES = new Set(["i32", "f32", "f64"]);
21
22/**
23 * @param {t.Signature} signature the func signature
24 * @returns {null | string} the type incompatible with js types
25 */
26const getJsIncompatibleType = signature => {
27 for (const param of signature.params) {
28 if (!JS_COMPAT_TYPES.has(param.valtype)) {
29 return `${param.valtype} as parameter`;
30 }
31 }
32 for (const type of signature.results) {
33 if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`;
34 }
35 return null;
36};
37
38/**
39 * TODO why are there two different Signature types?
40 * @param {t.FuncSignature} signature the func signature
41 * @returns {null | string} the type incompatible with js types
42 */
43const getJsIncompatibleTypeOfFuncSignature = signature => {
44 for (const param of signature.args) {
45 if (!JS_COMPAT_TYPES.has(param)) {
46 return `${param} as parameter`;
47 }
48 }
49 for (const type of signature.result) {
50 if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`;
51 }
52 return null;
53};
54
55const decoderOpts = {
56 ignoreCodeSection: true,
57 ignoreDataSection: true,
58
59 // this will avoid having to lookup with identifiers in the ModuleContext
60 ignoreCustomNameSection: true
61};
62
63class WebAssemblyParser extends Parser {
64 constructor(options) {
65 super();
66 this.hooks = Object.freeze({});
67 this.options = options;
68 }
69
70 /**
71 * @param {string | Buffer | PreparsedAst} source the source to parse
72 * @param {ParserState} state the parser state
73 * @returns {ParserState} the parser state
74 */
75 parse(source, state) {
76 if (!Buffer.isBuffer(source)) {
77 throw new Error("WebAssemblyParser input must be a Buffer");
78 }
79
80 // flag it as ESM
81 state.module.buildInfo.strict = true;
82 state.module.buildMeta.exportsType = "namespace";
83
84 // parse it
85 const program = decode(source, decoderOpts);
86 const module = program.body[0];
87
88 const moduleContext = moduleContextFromModuleAST(module);
89
90 // extract imports and exports
91 const exports = [];
92 let jsIncompatibleExports = (state.module.buildMeta.jsIncompatibleExports =
93 undefined);
94
95 const importedGlobals = [];
96 t.traverse(module, {
97 ModuleExport({ node }) {
98 const descriptor = node.descr;
99
100 if (descriptor.exportType === "Func") {
101 const funcIdx = descriptor.id.value;
102
103 /** @type {t.FuncSignature} */
104 const funcSignature = moduleContext.getFunction(funcIdx);
105
106 const incompatibleType =
107 getJsIncompatibleTypeOfFuncSignature(funcSignature);
108
109 if (incompatibleType) {
110 if (jsIncompatibleExports === undefined) {
111 jsIncompatibleExports =
112 state.module.buildMeta.jsIncompatibleExports = {};
113 }
114 jsIncompatibleExports[node.name] = incompatibleType;
115 }
116 }
117
118 exports.push(node.name);
119
120 if (node.descr && node.descr.exportType === "Global") {
121 const refNode = importedGlobals[node.descr.id.value];
122 if (refNode) {
123 const dep = new WebAssemblyExportImportedDependency(
124 node.name,
125 refNode.module,
126 refNode.name,
127 refNode.descr.valtype
128 );
129
130 state.module.addDependency(dep);
131 }
132 }
133 },
134
135 Global({ node }) {
136 const init = node.init[0];
137
138 let importNode = null;
139
140 if (init.id === "get_global") {
141 const globalIdx = init.args[0].value;
142
143 if (globalIdx < importedGlobals.length) {
144 importNode = importedGlobals[globalIdx];
145 }
146 }
147
148 importedGlobals.push(importNode);
149 },
150
151 ModuleImport({ node }) {
152 /** @type {false | string} */
153 let onlyDirectImport = false;
154
155 if (t.isMemory(node.descr) === true) {
156 onlyDirectImport = "Memory";
157 } else if (t.isTable(node.descr) === true) {
158 onlyDirectImport = "Table";
159 } else if (t.isFuncImportDescr(node.descr) === true) {
160 const incompatibleType = getJsIncompatibleType(node.descr.signature);
161 if (incompatibleType) {
162 onlyDirectImport = `Non-JS-compatible Func Signature (${incompatibleType})`;
163 }
164 } else if (t.isGlobalType(node.descr) === true) {
165 const type = node.descr.valtype;
166 if (!JS_COMPAT_TYPES.has(type)) {
167 onlyDirectImport = `Non-JS-compatible Global Type (${type})`;
168 }
169 }
170
171 const dep = new WebAssemblyImportDependency(
172 node.module,
173 node.name,
174 node.descr,
175 onlyDirectImport
176 );
177
178 state.module.addDependency(dep);
179
180 if (t.isGlobalType(node.descr)) {
181 importedGlobals.push(node);
182 }
183 }
184 });
185
186 state.module.addDependency(new StaticExportsDependency(exports, false));
187
188 return state;
189 }
190}
191
192module.exports = WebAssemblyParser;