WebAssemblyParser.js 5.1 KB

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