api.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft Corporation and contributors. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. let verbose = false;
  6. let doInvalid = true;
  7. const buf = readbuffer("binaries/api.wasm");
  8. const imports = {
  9. test: {
  10. fn() {return 1;},
  11. fn2() {return 2.14;},
  12. memory: new WebAssembly.Memory({initial: 1, maximum: 1}),
  13. g1: 45,
  14. g2: -8,
  15. },
  16. table: {"": new WebAssembly.Table({element: "anyfunc", initial: 30, maximum: 100})}
  17. };
  18. function overrideImports(overrides) {
  19. return {
  20. test: Object.assign({}, imports.test, overrides),
  21. table: overrides.table ? {"":overrides.table} : imports.table
  22. };
  23. }
  24. function objectToString(o, maxDepth = 5, depth = 0) {
  25. if (depth >= maxDepth) {
  26. return o.toString();
  27. }
  28. if (Array.isArray(o)) {
  29. return `[${o.map(e => objectToString(e, maxDepth, depth + 1)).join(", ")}]`;
  30. } else if (typeof o === "object") {
  31. return `{${Object.keys(o).map(k => `"${k}":"${objectToString(o[k], maxDepth, depth + 1)}"`).join(", ")}}`;
  32. }
  33. return o.toString();
  34. }
  35. function test(module, {exports} = {}) {
  36. if (module) {
  37. console.log("Testing module");
  38. console.log("exports");
  39. console.log(objectToString(WebAssembly.Module.exports(module)));
  40. console.log("imports");
  41. console.log(objectToString(WebAssembly.Module.imports(module)));
  42. }
  43. if (exports) {
  44. console.log("Testing instance");
  45. console.log(`f1: ${exports.f1()}`);
  46. console.log(`fn: ${exports.fn()}`);
  47. console.log(`fn2: ${exports.fn2()}`);
  48. console.log(`g1: ${exports.g1}`);
  49. console.log(`g2: ${exports.g2}`);
  50. console.log(`g3: ${exports.g3}`);
  51. }
  52. }
  53. async function testInvalidCases(tests) {
  54. if (!doInvalid) return;
  55. let i = 0;
  56. for (const testCase of tests) {
  57. if (verbose) {
  58. console.log(testCase);
  59. }
  60. try {
  61. await testCase();
  62. console.log(`Test ${i++} failed. Should have thrown error`);
  63. } catch (e) {
  64. console.log(`Test ${i++} passed. Expected Error: ${e}`);
  65. }
  66. }
  67. }
  68. function createView(bytes) {
  69. const buffer = new ArrayBuffer(bytes.length);
  70. const view = new Uint8Array(buffer);
  71. for (let i = 0; i < bytes.length; ++i) {
  72. view[i] = bytes.charCodeAt(i);
  73. }
  74. return view;
  75. }
  76. const invalidBuffers = [
  77. ,
  78. "",
  79. "123",
  80. 4568,
  81. {},
  82. {length: 15},
  83. function() {},
  84. new ArrayBuffer(),
  85. createView("\x00\x61\x73\x6d\x0d\x00\x00\x00\x01\x85\x80\x80"),
  86. new Proxy(buf, {get(target, name) {return target[name];}}),
  87. ];
  88. const invalidImports = [
  89. ,
  90. "",
  91. 123,
  92. function() {},
  93. {wrongNamespace: imports.test, table: imports.table},
  94. ];
  95. const invalidModules = [
  96. null,
  97. "123",
  98. {},
  99. new Proxy({}, {})
  100. ];
  101. async function testValidate() {
  102. console.log("\nWebAssembly.validate tests");
  103. await testInvalidCases([
  104. // Invalid buffer source
  105. ...(invalidBuffers.map(b => () => {
  106. if (!WebAssembly.validate(b)) {
  107. throw new Error("Buffer source is the right type, but doesn't validate");
  108. }
  109. })),
  110. ]);
  111. console.log(`Validate: ${WebAssembly.validate(buf)}`);
  112. }
  113. async function testCompile() {
  114. console.log("\nWebAssembly.compile tests");
  115. await testInvalidCases([
  116. ...(invalidBuffers.map(b => () => WebAssembly.compile(b))),
  117. ]);
  118. const module = await WebAssembly.compile(buf);
  119. test(module);
  120. }
  121. async function testInstantiate(baseModule) {
  122. console.log("\nWebAssembly.instantiate tests");
  123. await testInvalidCases([
  124. // Invalid buffer source
  125. ...(invalidBuffers.map(b => () => WebAssembly.instantiate(b))),
  126. // Invalid Imports
  127. ...(invalidImports.map(i => () => WebAssembly.instantiate(buf, i))),
  128. ...(invalidImports.map(i => () => WebAssembly.instantiate(baseModule, i))),
  129. ]);
  130. const {module, instance} = await WebAssembly.instantiate(buf, imports);
  131. test(module, instance);
  132. const instance2 = await WebAssembly.instantiate(baseModule, imports);
  133. test(baseModule, instance2);
  134. }
  135. async function testModuleConstructor() {
  136. console.log("\nnew WebAssembly.Module tests");
  137. await testInvalidCases([
  138. () => WebAssembly.Module(),
  139. () => WebAssembly.Module(buf),
  140. ...(invalidBuffers.map(b => () => new WebAssembly.Module(b))),
  141. ]);
  142. const module = new WebAssembly.Module(buf);
  143. test(module);
  144. }
  145. async function testModuleApi() {
  146. console.log("\nWebAssembly.Module api tests");
  147. console.log("\nWebAssembly.Module.exports invalid tests");
  148. await testInvalidCases([
  149. ...(invalidModules.map(b => () => WebAssembly.Module.exports(b))),
  150. ]);
  151. console.log("\nWebAssembly.Module.imports invalid tests");
  152. await testInvalidCases([
  153. ...(invalidModules.map(b => () => WebAssembly.Module.imports(b))),
  154. ]);
  155. }
  156. async function testModuleCustomSection(baseModule) {
  157. console.log("\nWebAssembly.Module.customSections tests");
  158. await testInvalidCases([
  159. ...(invalidModules.map(b => () => WebAssembly.Module.customSections(b))),
  160. () => WebAssembly.Module.customSections(baseModule),
  161. () => WebAssembly.Module.customSections(baseModule, {toString() {throw new Error("Doesn't support toString");}}),
  162. () => WebAssembly.Module.customSections(baseModule, Symbol()),
  163. ]);
  164. const baseSections = WebAssembly.Module.customSections(baseModule, "");
  165. if (!Array.isArray(baseSections) || baseSections.length !== 0) {
  166. console.log("Invalid result for WebAssembly.Module.customSections");
  167. console.log(baseSections);
  168. }
  169. let passed = 0, failed = 0;
  170. function compare(module, customSectionName, expected) {
  171. const sections = WebAssembly.Module.customSections(module, customSectionName);
  172. if (sections.length !== expected.length) {
  173. console.log(`Invalid length. Got ${sections.length}, expected ${expected.length}. ${(new Error()).stack}`);
  174. ++failed;
  175. return;
  176. }
  177. let asExpected = true;
  178. for (let iSection = 0; iSection < sections.length; ++iSection) {
  179. const resBuffer = sections[iSection];
  180. const expectedStr = expected[iSection];
  181. const view = new Uint8Array(resBuffer);
  182. let resStr = "";
  183. for (let i = 0; i < view.length; ++i) {
  184. resStr += String.fromCharCode(view[i]);
  185. }
  186. if (resStr !== expectedStr) {
  187. asExpected = false;
  188. console.log(`Invalid buffer result. Got ${resStr}, expected ${expectedStr}. ${(new Error()).stack}`);
  189. }
  190. }
  191. passed += asExpected;
  192. failed += !asExpected;
  193. }
  194. // See wasts/custom_section.wast for reference
  195. const module1 = new WebAssembly.Module(createView("\x00\x61\x73\x6d\x0d\x00\x00\x00\x00\x24\x10\x61\x20\x63\x75\x73\x74\x6f\x6d\x20\x73\x65\x63\x74\x69\x6f\x6e\x74\x68\x69\x73\x20\x69\x73\x20\x74\x68\x65\x20\x70\x61\x79\x6c\x6f\x61\x64\x00\x20\x10\x61\x20\x63\x75\x73\x74\x6f\x6d\x20\x73\x65\x63\x74\x69\x6f\x6e\x74\x68\x69\x73\x20\x69\x73\x20\x70\x61\x79\x6c\x6f\x61\x64\x00\x11\x10\x61\x20\x63\x75\x73\x74\x6f\x6d\x20\x73\x65\x63\x74\x69\x6f\x6e\x00\x10\x00\x74\x68\x69\x73\x20\x69\x73\x20\x70\x61\x79\x6c\x6f\x61\x64\x00\x01\x00\x00\x24\x10\x00\x00\x63\x75\x73\x74\x6f\x6d\x20\x73\x65\x63\x74\x69\x6f\x00\x74\x68\x69\x73\x20\x69\x73\x20\x74\x68\x65\x20\x70\x61\x79\x6c\x6f\x61\x64"));
  196. compare(module1, "a custom section", [
  197. "this is the payload",
  198. "this is payload",
  199. "",
  200. ]);
  201. compare(module1, "", [
  202. "this is payload",
  203. "",
  204. ]);
  205. compare(module1, "\00\00custom sectio\00", [
  206. "this is the payload",
  207. ]);
  208. const module2 = new WebAssembly.Module(createView("\x00\x61\x73\x6d\x0d\x00\x00\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x01\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x02\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x03\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x04\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x05\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x06\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x07\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x09\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x0a\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x0b\x01\x00\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64\x00\x0e\x06\x63\x75\x73\x74\x6f\x6d\x70\x61\x79\x6c\x6f\x61\x64"));
  209. compare(module2, "custom", new Proxy({length: 22}, {
  210. get: (target, name) => name in target ? target[name] : "payload"
  211. }));
  212. const module3 = new WebAssembly.Module(createView("\x00\x61\x73\x6d\x0d\x00\x00\x00\x01\x07\x01\x60\x02\x7f\x7f\x01\x7f\x00\x1a\x06\x63\x75\x73\x74\x6f\x6d\x74\x68\x69\x73\x20\x69\x73\x20\x74\x68\x65\x20\x70\x61\x79\x6c\x6f\x61\x64\x03\x02\x01\x00\x07\x0a\x01\x06\x61\x64\x64\x54\x77\x6f\x00\x00\x0a\x09\x01\x07\x00\x20\x00\x20\x01\x6a\x0b\x00\x1b\x07\x63\x75\x73\x74\x6f\x6d\x32\x74\x68\x69\x73\x20\x69\x73\x20\x74\x68\x65\x20\x70\x61\x79\x6c\x6f\x61\x64"));
  213. compare(module3, "custom", ["this is the payload"]);
  214. compare(module3, "custom2", ["this is the payload"]);
  215. console.log(`${passed}/${passed + failed} tests passed`);
  216. }
  217. async function testInstanceConstructor(module) {
  218. console.log("\nnew WebAssembly.Instance tests");
  219. const instance = new WebAssembly.Instance(module, imports);
  220. await testInvalidCases([
  221. () => WebAssembly.Instance(),
  222. () => WebAssembly.Instance(module, imports),
  223. ...(invalidImports.map(i => () => new WebAssembly.Instance(module, i))),
  224. () => new WebAssembly.Instance(module, overrideImports({fn: 4})),
  225. () => new WebAssembly.Instance(module, overrideImports({fn2: instance.exports.fn /*wrong signature*/})),
  226. () => new WebAssembly.Instance(module, overrideImports({memory: 123})),
  227. () => new WebAssembly.Instance(module, overrideImports({memory: ""})),
  228. () => new WebAssembly.Instance(module, overrideImports({memory: {}})),
  229. () => new WebAssembly.Instance(module, overrideImports({table: 123})),
  230. () => new WebAssembly.Instance(module, overrideImports({table: "135"})),
  231. () => new WebAssembly.Instance(module, overrideImports({table: {}})),
  232. ]);
  233. test(null, instance);
  234. }
  235. async function testMemoryApi(baseModule) {
  236. console.log("\nWebAssembly.Memory tests");
  237. await testInvalidCases([
  238. () => WebAssembly.Memory({initial: 1, maximum: 2}),
  239. () => new WebAssembly.Memory(),
  240. () => new WebAssembly.Memory(0),
  241. () => new WebAssembly.Memory(""),
  242. () => Reflect.apply(WebAssembly.Memory.prototype.buffer, null, []),
  243. () => Reflect.apply(WebAssembly.Memory.prototype.buffer, {}, []),
  244. () => Reflect.apply(WebAssembly.Memory.prototype.buffer, baseModule, []),
  245. () => Reflect.apply(WebAssembly.Memory.prototype.grow, null, []),
  246. () => Reflect.apply(WebAssembly.Memory.prototype.grow, {}, []),
  247. () => Reflect.apply(WebAssembly.Memory.prototype.grow, baseModule, []),
  248. // todo:: test invalid memory imports, need to find the spec
  249. ]);
  250. const toHex = v => typeof v === "number" ? `0x${v.toString(16)}` : "undefined";
  251. const memory = new WebAssembly.Memory({initial: 1, maximum: 2});
  252. const {exports: {load}} = new WebAssembly.Instance(baseModule, overrideImports({memory}));
  253. const v1 = load(1);
  254. const v2 = load(5);
  255. const v3 = load(9) & 0xFFFF;
  256. console.log(`${toHex(v1)} == "0123"`);
  257. console.log(`${toHex(v2)} == "4567"`);
  258. console.log(`${toHex(v3)} == "89"`);
  259. memory.buffer = null; // make sure we can't modify the buffer
  260. let view = new Int32Array(memory.buffer);
  261. view[0] = 45;
  262. console.log(`heap32[0] = ${load(0)}`);
  263. function testOutOfBounds(index) {
  264. try {
  265. load(index);
  266. console.log("Should have trap with out of bounds error");
  267. } catch (e) {
  268. if (e instanceof WebAssembly.RuntimeError) {
  269. console.log(`Correctly trap on heap access at ${index}`);
  270. } else {
  271. console.log(`Unexpected Error: ${e}`);
  272. }
  273. }
  274. }
  275. const pageSize = 64 * 1024;
  276. console.log(`memory.buffer.byteLength = ${memory.buffer.byteLength} == ${pageSize}`);
  277. {
  278. view[pageSize / 4 - 1] = 0x12345678;
  279. console.log(`view32[${pageSize / 4 - 1}] = ${toHex(view[pageSize / 4 - 1])}`);
  280. console.log(`view32[${pageSize / 4}] = ${toHex(view[pageSize / 4])}`);
  281. console.log(`heap[${pageSize - 4}] = ${toHex(load(pageSize - 4))}`);
  282. testOutOfBounds(pageSize - 3);
  283. testOutOfBounds(pageSize - 2);
  284. testOutOfBounds(pageSize - 1);
  285. testOutOfBounds(pageSize);
  286. }
  287. console.log("grow by 1 page");
  288. memory.grow(1);
  289. console.log(`memory.buffer.byteLength = ${memory.buffer.byteLength} == 2 * ${pageSize}`);
  290. view = new Int32Array(memory.buffer); // buffer has been detached, reset the view
  291. {
  292. view[2 * pageSize / 4 - 1] = 0x87654321;
  293. console.log(`view32[${2 * pageSize / 4 - 1}] = ${toHex(view[2 * pageSize / 4 - 1])}`);
  294. console.log(`view32[${2 * pageSize / 4}] = ${toHex(view[2 * pageSize / 4])}`);
  295. console.log(`heap[${pageSize - 4}] = ${toHex(load(pageSize - 4))}`);
  296. console.log(`heap[${2 * pageSize - 4}] = ${toHex(load(2 * pageSize - 4))}`);
  297. testOutOfBounds(2 * pageSize - 3);
  298. testOutOfBounds(2 * pageSize - 2);
  299. testOutOfBounds(2 * pageSize - 1);
  300. testOutOfBounds(2 * pageSize);
  301. }
  302. try {
  303. memory.grow(1);
  304. console.log("Should have trap when growing past maximum");
  305. } catch (e) {
  306. if (e instanceof RangeError) {
  307. console.log("Correctly trap when growing past maximum");
  308. } else {
  309. console.log(`Unexpected Error: ${e}`);
  310. }
  311. }
  312. }
  313. async function testTableApi(baseModule) {
  314. //new WebAssembly.Table({element: "anyfunc", initial: 30, maximum: 100})
  315. const table = new WebAssembly.Table({element: "anyfunc", initial: 30, maximum: 100});
  316. console.log("\nWebAssembly.Table tests");
  317. await testInvalidCases([
  318. () => WebAssembly.Table({element: "anyfunc", initial: 30, maximum: 100}),
  319. () => new WebAssembly.Table(),
  320. () => new WebAssembly.Table(0),
  321. () => new WebAssembly.Table(""),
  322. () => new WebAssembly.Table({element: "notanyfunc", initial: 30, maximum: 100}),
  323. () => Reflect.apply(WebAssembly.Table.prototype.length, null, []),
  324. () => Reflect.apply(WebAssembly.Table.prototype.length, {}, []),
  325. () => Reflect.apply(WebAssembly.Table.prototype.length, baseModule, []),
  326. () => Reflect.apply(WebAssembly.Table.prototype.grow, null, []),
  327. () => Reflect.apply(WebAssembly.Table.prototype.grow, {}, []),
  328. () => Reflect.apply(WebAssembly.Table.prototype.grow, baseModule, []),
  329. () => Reflect.apply(WebAssembly.Table.prototype.get, null, [0]),
  330. () => Reflect.apply(WebAssembly.Table.prototype.get, {}, [0]),
  331. () => Reflect.apply(WebAssembly.Table.prototype.get, baseModule, [0]),
  332. () => table.get(30),
  333. () => table.get(100),
  334. () => Reflect.apply(WebAssembly.Table.prototype.get, table, [30]),
  335. () => Reflect.apply(WebAssembly.Table.prototype.get, table, [100]),
  336. ]);
  337. const {instance: {exports: {"f32.load": load}}} = await WebAssembly.instantiate(readbuffer("f32address.wasm"), {});
  338. const {exports: {fn, call_i32, call_f32}} = new WebAssembly.Instance(baseModule, overrideImports({table, fn2: load}));
  339. const {exports: {fn: myFn}} = new WebAssembly.Instance(baseModule, overrideImports({fn: () => 123456}));
  340. await testInvalidCases([
  341. () => table.set(30, fn),
  342. () => table.set(100, fn),
  343. () => table.set(0),
  344. () => table.set(0, {}),
  345. () => table.set(0, ""),
  346. () => table.set(0, 123),
  347. () => table.set(0, function(){}),
  348. () => table.set(0, () => 2),
  349. () => call_i32(0),
  350. () => call_f32(0),
  351. () => call_i32(29),
  352. () => call_i32(30),
  353. () => call_i32(2),
  354. () => call_f32(1),
  355. ]);
  356. console.log(`Current length: ${table.length}`);
  357. table.length = 456;
  358. console.log(`Length after attempt to modify : ${table.length}`);
  359. console.log(`Is element in table the same as the one exported: ${table.get(1) === fn}`);
  360. console.log(`Unset element should be null: ${table.get(0)}`);
  361. table.set(0, myFn);
  362. console.log(`call_i32(0): ${call_i32(0)}`);
  363. console.log(`call_i32(1): ${call_i32(1)}`);
  364. console.log(`call_f32(2): ${call_f32(2)}`);
  365. table.set(0, fn);
  366. console.log(`call_i32(0): ${call_i32(0)}`);
  367. table.set(29, myFn);
  368. table.grow(1);
  369. table.set(30, myFn);
  370. console.log(`call_i32(29): ${call_i32(29)}`);
  371. console.log(`call_i32(30): ${call_i32(30)}`);
  372. }
  373. async function main() {
  374. const args = WScript.Arguments;
  375. verbose = args.includes("-v");
  376. doInvalid = !args.includes("-i");
  377. const [start = 0, end = 99] = args
  378. .filter(a => !a.startsWith("-"))
  379. .map(a => parseInt(a));
  380. const baseModule = new WebAssembly.Module(buf);
  381. const tests = [
  382. /*0*/ testValidate,
  383. /*1*/ testCompile,
  384. /*2*/ testInstantiate,
  385. /*3*/ testModuleConstructor,
  386. /*4*/ testModuleApi,
  387. /*5*/ testModuleCustomSection,
  388. /*6*/ testInstanceConstructor,
  389. /*7*/ testMemoryApi,
  390. /*8*/ testTableApi,
  391. ];
  392. for (let i = 0; i < tests.length; ++i) {
  393. if (i >= start && i <= end) {
  394. try {
  395. await tests[i](baseModule);
  396. } catch (e) {
  397. console.log("Unexpected error");
  398. console.log(e.stack);
  399. }
  400. }
  401. }
  402. }
  403. main().then(() => console.log("done"), err => {
  404. console.log(err);
  405. console.log(err.stack);
  406. });