context.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.QuickJSContext = void 0;
  4. const debug_1 = require("./debug");
  5. const deferred_promise_1 = require("./deferred-promise");
  6. const errors_1 = require("./errors");
  7. const lifetime_1 = require("./lifetime");
  8. const memory_1 = require("./memory");
  9. const types_1 = require("./types");
  10. /**
  11. * @private
  12. */
  13. class ContextMemory extends memory_1.ModuleMemory {
  14. /** @private */
  15. constructor(args) {
  16. super(args.module);
  17. this.scope = new lifetime_1.Scope();
  18. this.copyJSValue = (ptr) => {
  19. return this.ffi.QTS_DupValuePointer(this.ctx.value, ptr);
  20. };
  21. this.freeJSValue = (ptr) => {
  22. this.ffi.QTS_FreeValuePointer(this.ctx.value, ptr);
  23. };
  24. args.ownedLifetimes?.forEach((lifetime) => this.scope.manage(lifetime));
  25. this.owner = args.owner;
  26. this.module = args.module;
  27. this.ffi = args.ffi;
  28. this.rt = args.rt;
  29. this.ctx = this.scope.manage(args.ctx);
  30. }
  31. get alive() {
  32. return this.scope.alive;
  33. }
  34. dispose() {
  35. return this.scope.dispose();
  36. }
  37. /**
  38. * Track `lifetime` so that it is disposed when this scope is disposed.
  39. */
  40. manage(lifetime) {
  41. return this.scope.manage(lifetime);
  42. }
  43. consumeJSCharPointer(ptr) {
  44. const str = this.module.UTF8ToString(ptr);
  45. this.ffi.QTS_FreeCString(this.ctx.value, ptr);
  46. return str;
  47. }
  48. heapValueHandle(ptr) {
  49. return new lifetime_1.Lifetime(ptr, this.copyJSValue, this.freeJSValue, this.owner);
  50. }
  51. }
  52. /**
  53. * QuickJSContext wraps a QuickJS Javascript context (JSContext*) within a
  54. * runtime. The contexts within the same runtime may exchange objects freely.
  55. * You can think of separate runtimes like different domains in a browser, and
  56. * the contexts within a runtime like the different windows open to the same
  57. * domain. The {@link runtime} references the context's runtime.
  58. *
  59. * This class's methods return {@link QuickJSHandle}, which wrap C pointers (JSValue*).
  60. * It's the caller's responsibility to call `.dispose()` on any
  61. * handles you create to free memory once you're done with the handle.
  62. *
  63. * Use {@link QuickJSRuntime.newContext} or {@link QuickJSWASMModule.newContext}
  64. * to create a new QuickJSContext.
  65. *
  66. * Create QuickJS values inside the interpreter with methods like
  67. * [[newNumber]], [[newString]], [[newArray]], [[newObject]],
  68. * [[newFunction]], and [[newPromise]].
  69. *
  70. * Call [[setProp]] or [[defineProp]] to customize objects. Use those methods
  71. * with [[global]] to expose the values you create to the interior of the
  72. * interpreter, so they can be used in [[evalCode]].
  73. *
  74. * Use [[evalCode]] or [[callFunction]] to execute Javascript inside the VM. If
  75. * you're using asynchronous code inside the QuickJSContext, you may need to also
  76. * call [[executePendingJobs]]. Executing code inside the runtime returns a
  77. * result object representing successful execution or an error. You must dispose
  78. * of any such results to avoid leaking memory inside the VM.
  79. *
  80. * Implement memory and CPU constraints at the runtime level, using [[runtime]].
  81. * See {@link QuickJSRuntime} for more information.
  82. *
  83. */
  84. // TODO: Manage own callback registration
  85. class QuickJSContext {
  86. /**
  87. * Use {@link QuickJS.createVm} to create a QuickJSContext instance.
  88. */
  89. constructor(args) {
  90. /** @private */
  91. this._undefined = undefined;
  92. /** @private */
  93. this._null = undefined;
  94. /** @private */
  95. this._false = undefined;
  96. /** @private */
  97. this._true = undefined;
  98. /** @private */
  99. this._global = undefined;
  100. /** @private */
  101. this._BigInt = undefined;
  102. /** @private */
  103. this.fnNextId = -32768; // min value of signed 16bit int used by Quickjs
  104. /** @private */
  105. this.fnMaps = new Map();
  106. /**
  107. * @hidden
  108. */
  109. this.cToHostCallbacks = {
  110. callFunction: (ctx, this_ptr, argc, argv, fn_id) => {
  111. if (ctx !== this.ctx.value) {
  112. throw new Error("QuickJSContext instance received C -> JS call with mismatched ctx");
  113. }
  114. const fn = this.getFunction(fn_id);
  115. if (!fn) {
  116. // this "throw" is not catch-able from the TS side. could we somehow handle this higher up?
  117. throw new Error(`QuickJSContext had no callback with id ${fn_id}`);
  118. }
  119. return lifetime_1.Scope.withScopeMaybeAsync(this, function* (awaited, scope) {
  120. const thisHandle = scope.manage(new lifetime_1.WeakLifetime(this_ptr, this.memory.copyJSValue, this.memory.freeJSValue, this.runtime));
  121. const argHandles = new Array(argc);
  122. for (let i = 0; i < argc; i++) {
  123. const ptr = this.ffi.QTS_ArgvGetJSValueConstPointer(argv, i);
  124. argHandles[i] = scope.manage(new lifetime_1.WeakLifetime(ptr, this.memory.copyJSValue, this.memory.freeJSValue, this.runtime));
  125. }
  126. try {
  127. const result = yield* awaited(fn.apply(thisHandle, argHandles));
  128. if (result) {
  129. if ("error" in result && result.error) {
  130. (0, debug_1.debugLog)("throw error", result.error);
  131. throw result.error;
  132. }
  133. const handle = scope.manage(result instanceof lifetime_1.Lifetime ? result : result.value);
  134. return this.ffi.QTS_DupValuePointer(this.ctx.value, handle.value);
  135. }
  136. return 0;
  137. }
  138. catch (error) {
  139. return this.errorToHandle(error).consume((errorHandle) => this.ffi.QTS_Throw(this.ctx.value, errorHandle.value));
  140. }
  141. });
  142. },
  143. };
  144. this.runtime = args.runtime;
  145. this.module = args.module;
  146. this.ffi = args.ffi;
  147. this.rt = args.rt;
  148. this.ctx = args.ctx;
  149. this.memory = new ContextMemory({
  150. ...args,
  151. owner: this.runtime,
  152. });
  153. args.callbacks.setContextCallbacks(this.ctx.value, this.cToHostCallbacks);
  154. this.dump = this.dump.bind(this);
  155. this.getString = this.getString.bind(this);
  156. this.getNumber = this.getNumber.bind(this);
  157. this.resolvePromise = this.resolvePromise.bind(this);
  158. }
  159. // @implement Disposable ----------------------------------------------------
  160. get alive() {
  161. return this.memory.alive;
  162. }
  163. /**
  164. * Dispose of this VM's underlying resources.
  165. *
  166. * @throws Calling this method without disposing of all created handles
  167. * will result in an error.
  168. */
  169. dispose() {
  170. this.memory.dispose();
  171. }
  172. // Globals ------------------------------------------------------------------
  173. /**
  174. * [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined).
  175. */
  176. get undefined() {
  177. if (this._undefined) {
  178. return this._undefined;
  179. }
  180. // Undefined is a constant, immutable value in QuickJS.
  181. const ptr = this.ffi.QTS_GetUndefined();
  182. return (this._undefined = new lifetime_1.StaticLifetime(ptr));
  183. }
  184. /**
  185. * [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null).
  186. */
  187. get null() {
  188. if (this._null) {
  189. return this._null;
  190. }
  191. // Null is a constant, immutable value in QuickJS.
  192. const ptr = this.ffi.QTS_GetNull();
  193. return (this._null = new lifetime_1.StaticLifetime(ptr));
  194. }
  195. /**
  196. * [`true`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/true).
  197. */
  198. get true() {
  199. if (this._true) {
  200. return this._true;
  201. }
  202. // True is a constant, immutable value in QuickJS.
  203. const ptr = this.ffi.QTS_GetTrue();
  204. return (this._true = new lifetime_1.StaticLifetime(ptr));
  205. }
  206. /**
  207. * [`false`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/false).
  208. */
  209. get false() {
  210. if (this._false) {
  211. return this._false;
  212. }
  213. // False is a constant, immutable value in QuickJS.
  214. const ptr = this.ffi.QTS_GetFalse();
  215. return (this._false = new lifetime_1.StaticLifetime(ptr));
  216. }
  217. /**
  218. * [`global`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects).
  219. * A handle to the global object inside the interpreter.
  220. * You can set properties to create global variables.
  221. */
  222. get global() {
  223. if (this._global) {
  224. return this._global;
  225. }
  226. // The global is a JSValue, but since it's lifetime is as long as the VM's,
  227. // we should manage it.
  228. const ptr = this.ffi.QTS_GetGlobalObject(this.ctx.value);
  229. // Automatically clean up this reference when we dispose
  230. this.memory.manage(this.memory.heapValueHandle(ptr));
  231. // This isn't technically a static lifetime, but since it has the same
  232. // lifetime as the VM, it's okay to fake one since when the VM is
  233. // disposed, no other functions will accept the value.
  234. this._global = new lifetime_1.StaticLifetime(ptr, this.runtime);
  235. return this._global;
  236. }
  237. // New values ---------------------------------------------------------------
  238. /**
  239. * Converts a Javascript number into a QuickJS value.
  240. */
  241. newNumber(num) {
  242. return this.memory.heapValueHandle(this.ffi.QTS_NewFloat64(this.ctx.value, num));
  243. }
  244. /**
  245. * Create a QuickJS [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) value.
  246. */
  247. newString(str) {
  248. const ptr = this.memory
  249. .newHeapCharPointer(str)
  250. .consume((charHandle) => this.ffi.QTS_NewString(this.ctx.value, charHandle.value));
  251. return this.memory.heapValueHandle(ptr);
  252. }
  253. /**
  254. * Create a QuickJS [symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) value.
  255. * No two symbols created with this function will be the same value.
  256. */
  257. newUniqueSymbol(description) {
  258. const key = (typeof description === "symbol" ? description.description : description) ?? "";
  259. const ptr = this.memory
  260. .newHeapCharPointer(key)
  261. .consume((charHandle) => this.ffi.QTS_NewSymbol(this.ctx.value, charHandle.value, 0));
  262. return this.memory.heapValueHandle(ptr);
  263. }
  264. /**
  265. * Get a symbol from the [global registry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry) for the given key.
  266. * All symbols created with the same key will be the same value.
  267. */
  268. newSymbolFor(key) {
  269. const description = (typeof key === "symbol" ? key.description : key) ?? "";
  270. const ptr = this.memory
  271. .newHeapCharPointer(description)
  272. .consume((charHandle) => this.ffi.QTS_NewSymbol(this.ctx.value, charHandle.value, 1));
  273. return this.memory.heapValueHandle(ptr);
  274. }
  275. /**
  276. * Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) value.
  277. */
  278. newBigInt(num) {
  279. if (!this._BigInt) {
  280. const bigIntHandle = this.getProp(this.global, "BigInt");
  281. this.memory.manage(bigIntHandle);
  282. this._BigInt = new lifetime_1.StaticLifetime(bigIntHandle.value, this.runtime);
  283. }
  284. const bigIntHandle = this._BigInt;
  285. const asString = String(num);
  286. return this.newString(asString).consume((handle) => this.unwrapResult(this.callFunction(bigIntHandle, this.undefined, handle)));
  287. }
  288. /**
  289. * `{}`.
  290. * Create a new QuickJS [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer).
  291. *
  292. * @param prototype - Like [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create).
  293. */
  294. newObject(prototype) {
  295. if (prototype) {
  296. this.runtime.assertOwned(prototype);
  297. }
  298. const ptr = prototype
  299. ? this.ffi.QTS_NewObjectProto(this.ctx.value, prototype.value)
  300. : this.ffi.QTS_NewObject(this.ctx.value);
  301. return this.memory.heapValueHandle(ptr);
  302. }
  303. /**
  304. * `[]`.
  305. * Create a new QuickJS [array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
  306. */
  307. newArray() {
  308. const ptr = this.ffi.QTS_NewArray(this.ctx.value);
  309. return this.memory.heapValueHandle(ptr);
  310. }
  311. newPromise(value) {
  312. const deferredPromise = lifetime_1.Scope.withScope((scope) => {
  313. const mutablePointerArray = scope.manage(this.memory.newMutablePointerArray(2));
  314. const promisePtr = this.ffi.QTS_NewPromiseCapability(this.ctx.value, mutablePointerArray.value.ptr);
  315. const promiseHandle = this.memory.heapValueHandle(promisePtr);
  316. const [resolveHandle, rejectHandle] = Array.from(mutablePointerArray.value.typedArray).map((jsvaluePtr) => this.memory.heapValueHandle(jsvaluePtr));
  317. return new deferred_promise_1.QuickJSDeferredPromise({
  318. context: this,
  319. promiseHandle,
  320. resolveHandle,
  321. rejectHandle,
  322. });
  323. });
  324. if (value && typeof value === "function") {
  325. value = new Promise(value);
  326. }
  327. if (value) {
  328. Promise.resolve(value).then(deferredPromise.resolve, (error) => error instanceof lifetime_1.Lifetime
  329. ? deferredPromise.reject(error)
  330. : this.newError(error).consume(deferredPromise.reject));
  331. }
  332. return deferredPromise;
  333. }
  334. /**
  335. * Convert a Javascript function into a QuickJS function value.
  336. * See [[VmFunctionImplementation]] for more details.
  337. *
  338. * A [[VmFunctionImplementation]] should not free its arguments or its return
  339. * value. A VmFunctionImplementation should also not retain any references to
  340. * its return value.
  341. *
  342. * To implement an async function, create a promise with [[newPromise]], then
  343. * return the deferred promise handle from `deferred.handle` from your
  344. * function implementation:
  345. *
  346. * ```
  347. * const deferred = vm.newPromise()
  348. * someNativeAsyncFunction().then(deferred.resolve)
  349. * return deferred.handle
  350. * ```
  351. */
  352. newFunction(name, fn) {
  353. const fnId = ++this.fnNextId;
  354. this.setFunction(fnId, fn);
  355. return this.memory.heapValueHandle(this.ffi.QTS_NewFunction(this.ctx.value, fnId, name));
  356. }
  357. newError(error) {
  358. const errorHandle = this.memory.heapValueHandle(this.ffi.QTS_NewError(this.ctx.value));
  359. if (error && typeof error === "object") {
  360. if (error.name !== undefined) {
  361. this.newString(error.name).consume((handle) => this.setProp(errorHandle, "name", handle));
  362. }
  363. if (error.message !== undefined) {
  364. this.newString(error.message).consume((handle) => this.setProp(errorHandle, "message", handle));
  365. }
  366. }
  367. else if (typeof error === "string") {
  368. this.newString(error).consume((handle) => this.setProp(errorHandle, "message", handle));
  369. }
  370. else if (error !== undefined) {
  371. // This isn't supported in the type signature but maybe it will make life easier.
  372. this.newString(String(error)).consume((handle) => this.setProp(errorHandle, "message", handle));
  373. }
  374. return errorHandle;
  375. }
  376. // Read values --------------------------------------------------------------
  377. /**
  378. * `typeof` operator. **Not** [standards compliant](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof).
  379. *
  380. * @remarks
  381. * Does not support BigInt values correctly.
  382. */
  383. typeof(handle) {
  384. this.runtime.assertOwned(handle);
  385. return this.memory.consumeHeapCharPointer(this.ffi.QTS_Typeof(this.ctx.value, handle.value));
  386. }
  387. /**
  388. * Converts `handle` into a Javascript number.
  389. * @returns `NaN` on error, otherwise a `number`.
  390. */
  391. getNumber(handle) {
  392. this.runtime.assertOwned(handle);
  393. return this.ffi.QTS_GetFloat64(this.ctx.value, handle.value);
  394. }
  395. /**
  396. * Converts `handle` to a Javascript string.
  397. */
  398. getString(handle) {
  399. this.runtime.assertOwned(handle);
  400. return this.memory.consumeJSCharPointer(this.ffi.QTS_GetString(this.ctx.value, handle.value));
  401. }
  402. /**
  403. * Converts `handle` into a Javascript symbol. If the symbol is in the global
  404. * registry in the guest, it will be created with Symbol.for on the host.
  405. */
  406. getSymbol(handle) {
  407. this.runtime.assertOwned(handle);
  408. const key = this.memory.consumeJSCharPointer(this.ffi.QTS_GetSymbolDescriptionOrKey(this.ctx.value, handle.value));
  409. const isGlobal = this.ffi.QTS_IsGlobalSymbol(this.ctx.value, handle.value);
  410. return isGlobal ? Symbol.for(key) : Symbol(key);
  411. }
  412. /**
  413. * Converts `handle` to a Javascript bigint.
  414. */
  415. getBigInt(handle) {
  416. this.runtime.assertOwned(handle);
  417. const asString = this.getString(handle);
  418. return BigInt(asString);
  419. }
  420. /**
  421. * `Promise.resolve(value)`.
  422. * Convert a handle containing a Promise-like value inside the VM into an
  423. * actual promise on the host.
  424. *
  425. * @remarks
  426. * You may need to call [[executePendingJobs]] to ensure that the promise is resolved.
  427. *
  428. * @param promiseLikeHandle - A handle to a Promise-like value with a `.then(onSuccess, onError)` method.
  429. */
  430. resolvePromise(promiseLikeHandle) {
  431. this.runtime.assertOwned(promiseLikeHandle);
  432. const vmResolveResult = lifetime_1.Scope.withScope((scope) => {
  433. const vmPromise = scope.manage(this.getProp(this.global, "Promise"));
  434. const vmPromiseResolve = scope.manage(this.getProp(vmPromise, "resolve"));
  435. return this.callFunction(vmPromiseResolve, vmPromise, promiseLikeHandle);
  436. });
  437. if (vmResolveResult.error) {
  438. return Promise.resolve(vmResolveResult);
  439. }
  440. return new Promise((resolve) => {
  441. lifetime_1.Scope.withScope((scope) => {
  442. const resolveHandle = scope.manage(this.newFunction("resolve", (value) => {
  443. resolve({ value: value && value.dup() });
  444. }));
  445. const rejectHandle = scope.manage(this.newFunction("reject", (error) => {
  446. resolve({ error: error && error.dup() });
  447. }));
  448. const promiseHandle = scope.manage(vmResolveResult.value);
  449. const promiseThenHandle = scope.manage(this.getProp(promiseHandle, "then"));
  450. this.unwrapResult(this.callFunction(promiseThenHandle, promiseHandle, resolveHandle, rejectHandle)).dispose();
  451. });
  452. });
  453. }
  454. // Properties ---------------------------------------------------------------
  455. /**
  456. * `handle[key]`.
  457. * Get a property from a JSValue.
  458. *
  459. * @param key - The property may be specified as a JSValue handle, or as a
  460. * Javascript string (which will be converted automatically).
  461. */
  462. getProp(handle, key) {
  463. this.runtime.assertOwned(handle);
  464. const ptr = this.borrowPropertyKey(key).consume((quickJSKey) => this.ffi.QTS_GetProp(this.ctx.value, handle.value, quickJSKey.value));
  465. const result = this.memory.heapValueHandle(ptr);
  466. return result;
  467. }
  468. /**
  469. * `handle[key] = value`.
  470. * Set a property on a JSValue.
  471. *
  472. * @remarks
  473. * Note that the QuickJS authors recommend using [[defineProp]] to define new
  474. * properties.
  475. *
  476. * @param key - The property may be specified as a JSValue handle, or as a
  477. * Javascript string or number (which will be converted automatically to a JSValue).
  478. */
  479. setProp(handle, key, value) {
  480. this.runtime.assertOwned(handle);
  481. // free newly allocated value if key was a string or number. No-op if string was already
  482. // a QuickJS handle.
  483. this.borrowPropertyKey(key).consume((quickJSKey) => this.ffi.QTS_SetProp(this.ctx.value, handle.value, quickJSKey.value, value.value));
  484. }
  485. /**
  486. * [`Object.defineProperty(handle, key, descriptor)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
  487. *
  488. * @param key - The property may be specified as a JSValue handle, or as a
  489. * Javascript string or number (which will be converted automatically to a JSValue).
  490. */
  491. defineProp(handle, key, descriptor) {
  492. this.runtime.assertOwned(handle);
  493. lifetime_1.Scope.withScope((scope) => {
  494. const quickJSKey = scope.manage(this.borrowPropertyKey(key));
  495. const value = descriptor.value || this.undefined;
  496. const configurable = Boolean(descriptor.configurable);
  497. const enumerable = Boolean(descriptor.enumerable);
  498. const hasValue = Boolean(descriptor.value);
  499. const get = descriptor.get
  500. ? scope.manage(this.newFunction(descriptor.get.name, descriptor.get))
  501. : this.undefined;
  502. const set = descriptor.set
  503. ? scope.manage(this.newFunction(descriptor.set.name, descriptor.set))
  504. : this.undefined;
  505. this.ffi.QTS_DefineProp(this.ctx.value, handle.value, quickJSKey.value, value.value, get.value, set.value, configurable, enumerable, hasValue);
  506. });
  507. }
  508. // Evaluation ---------------------------------------------------------------
  509. /**
  510. * [`func.call(thisVal, ...args)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call).
  511. * Call a JSValue as a function.
  512. *
  513. * See [[unwrapResult]], which will throw if the function returned an error, or
  514. * return the result handle directly. If evaluation returned a handle containing
  515. * a promise, use [[resolvePromise]] to convert it to a native promise and
  516. * [[executePendingJobs]] to finish evaluating the promise.
  517. *
  518. * @returns A result. If the function threw synchronously, `result.error` be a
  519. * handle to the exception. Otherwise `result.value` will be a handle to the
  520. * value.
  521. */
  522. callFunction(func, thisVal, ...args) {
  523. this.runtime.assertOwned(func);
  524. const resultPtr = this.memory
  525. .toPointerArray(args)
  526. .consume((argsArrayPtr) => this.ffi.QTS_Call(this.ctx.value, func.value, thisVal.value, args.length, argsArrayPtr.value));
  527. const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr);
  528. if (errorPtr) {
  529. this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr);
  530. return { error: this.memory.heapValueHandle(errorPtr) };
  531. }
  532. return { value: this.memory.heapValueHandle(resultPtr) };
  533. }
  534. /**
  535. * Like [`eval(code)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Description).
  536. * Evaluates the Javascript source `code` in the global scope of this VM.
  537. * When working with async code, you many need to call [[executePendingJobs]]
  538. * to execute callbacks pending after synchronous evaluation returns.
  539. *
  540. * See [[unwrapResult]], which will throw if the function returned an error, or
  541. * return the result handle directly. If evaluation returned a handle containing
  542. * a promise, use [[resolvePromise]] to convert it to a native promise and
  543. * [[executePendingJobs]] to finish evaluating the promise.
  544. *
  545. * *Note*: to protect against infinite loops, provide an interrupt handler to
  546. * [[setInterruptHandler]]. You can use [[shouldInterruptAfterDeadline]] to
  547. * create a time-based deadline.
  548. *
  549. * @returns The last statement's value. If the code threw synchronously,
  550. * `result.error` will be a handle to the exception. If execution was
  551. * interrupted, the error will have name `InternalError` and message
  552. * `interrupted`.
  553. */
  554. evalCode(code, filename = "eval.js",
  555. /**
  556. * If no options are passed, a heuristic will be used to detect if `code` is
  557. * an ES module.
  558. *
  559. * See [[EvalFlags]] for number semantics.
  560. */
  561. options) {
  562. const detectModule = (options === undefined ? 1 : 0);
  563. const flags = (0, types_1.evalOptionsToFlags)(options);
  564. const resultPtr = this.memory
  565. .newHeapCharPointer(code)
  566. .consume((charHandle) => this.ffi.QTS_Eval(this.ctx.value, charHandle.value, filename, detectModule, flags));
  567. const errorPtr = this.ffi.QTS_ResolveException(this.ctx.value, resultPtr);
  568. if (errorPtr) {
  569. this.ffi.QTS_FreeValuePointer(this.ctx.value, resultPtr);
  570. return { error: this.memory.heapValueHandle(errorPtr) };
  571. }
  572. return { value: this.memory.heapValueHandle(resultPtr) };
  573. }
  574. /**
  575. * Throw an error in the VM, interrupted whatever current execution is in progress when execution resumes.
  576. * @experimental
  577. */
  578. throw(error) {
  579. return this.errorToHandle(error).consume((handle) => this.ffi.QTS_Throw(this.ctx.value, handle.value));
  580. }
  581. /**
  582. * @private
  583. */
  584. borrowPropertyKey(key) {
  585. if (typeof key === "number") {
  586. return this.newNumber(key);
  587. }
  588. if (typeof key === "string") {
  589. return this.newString(key);
  590. }
  591. // key is already a JSValue, but we're borrowing it. Return a static handle
  592. // for internal use only.
  593. return new lifetime_1.StaticLifetime(key.value, this.runtime);
  594. }
  595. /**
  596. * @private
  597. */
  598. getMemory(rt) {
  599. if (rt === this.rt.value) {
  600. return this.memory;
  601. }
  602. else {
  603. throw new Error("Private API. Cannot get memory from a different runtime");
  604. }
  605. }
  606. // Utilities ----------------------------------------------------------------
  607. /**
  608. * Dump a JSValue to Javascript in a best-effort fashion.
  609. * Returns `handle.toString()` if it cannot be serialized to JSON.
  610. */
  611. dump(handle) {
  612. this.runtime.assertOwned(handle);
  613. const type = this.typeof(handle);
  614. if (type === "string") {
  615. return this.getString(handle);
  616. }
  617. else if (type === "number") {
  618. return this.getNumber(handle);
  619. }
  620. else if (type === "bigint") {
  621. return this.getBigInt(handle);
  622. }
  623. else if (type === "undefined") {
  624. return undefined;
  625. }
  626. else if (type === "symbol") {
  627. return this.getSymbol(handle);
  628. }
  629. const str = this.memory.consumeJSCharPointer(this.ffi.QTS_Dump(this.ctx.value, handle.value));
  630. try {
  631. return JSON.parse(str);
  632. }
  633. catch (err) {
  634. return str;
  635. }
  636. }
  637. /**
  638. * Unwrap a SuccessOrFail result such as a [[VmCallResult]] or a
  639. * [[ExecutePendingJobsResult]], where the fail branch contains a handle to a QuickJS error value.
  640. * If the result is a success, returns the value.
  641. * If the result is an error, converts the error to a native object and throws the error.
  642. */
  643. unwrapResult(result) {
  644. if (result.error) {
  645. const context = "context" in result.error ? result.error.context : this;
  646. const cause = result.error.consume((error) => this.dump(error));
  647. if (cause && typeof cause === "object" && typeof cause.message === "string") {
  648. const { message, name, stack } = cause;
  649. const exception = new errors_1.QuickJSUnwrapError("");
  650. const hostStack = exception.stack;
  651. if (typeof name === "string") {
  652. exception.name = cause.name;
  653. }
  654. if (typeof stack === "string") {
  655. exception.stack = `${name}: ${message}\n${cause.stack}Host: ${hostStack}`;
  656. }
  657. Object.assign(exception, { cause, context, message });
  658. throw exception;
  659. }
  660. throw new errors_1.QuickJSUnwrapError(cause, context);
  661. }
  662. return result.value;
  663. }
  664. /** @private */
  665. getFunction(fn_id) {
  666. const map_id = fn_id >> 8;
  667. const fnMap = this.fnMaps.get(map_id);
  668. if (!fnMap) {
  669. return undefined;
  670. }
  671. return fnMap.get(fn_id);
  672. }
  673. /** @private */
  674. setFunction(fn_id, handle) {
  675. const map_id = fn_id >> 8;
  676. let fnMap = this.fnMaps.get(map_id);
  677. if (!fnMap) {
  678. fnMap = new Map();
  679. this.fnMaps.set(map_id, fnMap);
  680. }
  681. return fnMap.set(fn_id, handle);
  682. }
  683. errorToHandle(error) {
  684. if (error instanceof lifetime_1.Lifetime) {
  685. return error;
  686. }
  687. return this.newError(error);
  688. }
  689. }
  690. exports.QuickJSContext = QuickJSContext;
  691. //# sourceMappingURL=context.js.map