From ff635551436123022ba3980b39580d53973c80a2 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sat, 24 Jun 2023 06:02:16 -0700 Subject: Rewrite Bun's runtime CommonJS loader (#3379) * wip changes for CommonJS * this rewrite is almost complete * even more code * wip * Remove usages of `import.meta.require` from builtins * Remove usages of require * Regenerate * :scissors: builtin rewrite commonjs in printer * Use lazy custom getters for import.meta * fixups * Remove depd * ugh * still crashing * fixup undici * comment out import.meta.require.resolve temporarily not a real solution but it stops the crashes * Redo import.meta.primordials * Builtins now have a `builtin://` protocol in source origin * Seems to work? * Finsih getting rid of primordials * switcharoo * No more function * just one more bug * Update launch.json * Implement `require.main` * :scissors: * Bump WebKit * Fixup import cycles * Fixup improt cycles * export more things * Implement `createCommonJSModule` builtin * More exports * regenerate * i broke some stuff * some of these tests work now * We lost the encoding * Sort of fix zlib * Sort of fix util * Update events.js * bump * bump * bump * Fix missing export in fs * fix some bugs with builtin esm modules (stream, worker_threads, events). its not perfect yet. * fix some other internal module bugs * oops * fix some extra require default stuff * uncomment this file but it crsahes on my machine * tidy code here * fixup tls exports * make simdutf happier * Add hasPrefix binding * Add test for `require.main` * Fix CommonJS evaluation order race condition * Make node:http load faster * Add missing exports to tls.js * Use the getter * Regenerate builtins * Fix assertion failure in Bun.write() * revamp dotEnv parser (#3347) - fixes `strings.indexOfAny()` - fixes OOB array access fixes #411 fixes #2823 fixes #3042 * fix tests for `expect()` (#3384) - extend test job time-out for `darwin-aarch64` * `expect().resolves` and `expect().rejects` (#3318) * Move expect and snapshots to their own files * expect().resolves and expect().rejects * Fix promise being added to unhandled rejection list * Handle timeouts in expect() * wip merge * Fix merge issue --------- Co-authored-by: Jarred Sumner Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> * fixup min/memcopy (#3388) * Fix crash in builtins * Don't attempt to evaluate modules with no source code * Update WebCoreJSBuiltins.cpp * Update WebCoreJSBuiltins.cpp * Update WebCoreJSBuiltins.cpp * Fix crash * cleanup * Fix test cc @paperdave * Fixup Undici * Fix issue in node:http * Create util-deprecate.mjs * Fix several bugs * Use the identifier * Support error.code in `util.deprecate` * make the CJs loader slightly more resilient * Update WebCoreJSBuiltins.cpp * Fix macros --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: dave caruso Co-authored-by: Alex Lam S.L Co-authored-by: Ashcon Partovi Co-authored-by: Ciro Spaciari --- src/js/node/util.js | 126 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 52 deletions(-) (limited to 'src/js/node/util.js') diff --git a/src/js/node/util.js b/src/js/node/util.js index 282f4b371..346f88ffb 100644 --- a/src/js/node/util.js +++ b/src/js/node/util.js @@ -1,4 +1,11 @@ // Hardcoded module "node:util" +import * as types from "node:util/types"; +export { default as types } from "node:util/types"; + +var cjs_exports = {}; + +export default cjs_exports; + var __getOwnPropNames = Object.getOwnPropertyNames; var __commonJS = (cb, mod) => function __require() { @@ -17,6 +24,10 @@ export function isBuffer(value) { ); } +function isFunction(value) { + return typeof value === "function"; +} + // node_modules/inherits/inherits_browser.js var require_inherits_browser = __commonJS({ "node_modules/inherits/inherits_browser.js"(exports, module2) { @@ -35,9 +46,6 @@ var require_inherits_browser = __commonJS({ }); const deepEquals = Bun.deepEquals; const isDeepStrictEqual = (a, b) => deepEquals(a, b, true); -const exports = { - isDeepStrictEqual, -}; var getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors; var formatRegExp = /%[sdj%]/g; function format(f) { @@ -78,21 +86,19 @@ function format(f) { } return str; } -exports.format = format; -function deprecate(fn, msg) { - if (typeof process !== "undefined" && process.noDeprecation === true) { + +function deprecate(fn, msg, code) { + if (process.noDeprecation === true) { return fn; } - if (typeof process === "undefined") { - return function () { - return exports.deprecate(fn, msg).apply(this, arguments); - }; - } + var warned = false; function deprecated() { if (!warned) { if (process.throwDeprecation) { - throw new Error(msg); + var err = new Error(msg); + if (code) err.code = code; + throw err; } else if (process.traceDeprecation) { console.trace(msg); } else { @@ -104,7 +110,7 @@ function deprecate(fn, msg) { } return deprecated; } -exports.deprecate = deprecate; + var debugs = {}; var debugEnvRegex = /^$/; if (process.env.NODE_DEBUG) { @@ -123,7 +129,7 @@ function debuglog(set) { if (debugEnvRegex.test(set)) { var pid = process.pid; debugs[set] = function () { - var msg = exports.format.apply(exports, arguments); + var msg = format.apply(cjs_exports, arguments); console.error("%s %d: %s", set, pid, msg); }; } else { @@ -132,7 +138,6 @@ function debuglog(set) { } return debugs[set]; } -exports.debuglog = debuglog; function inspect(obj, opts) { var ctx = { seen: [], @@ -143,7 +148,7 @@ function inspect(obj, opts) { if (isBoolean(opts)) { ctx.showHidden = opts; } else if (opts) { - exports._extend(ctx, opts); + _extend(ctx, opts); } if (isUndefined(ctx.showHidden)) ctx.showHidden = false; if (isUndefined(ctx.depth)) ctx.depth = 2; @@ -152,7 +157,6 @@ function inspect(obj, opts) { if (ctx.colors) ctx.stylize = stylizeWithColor; return formatValue(ctx, obj, ctx.depth); } -exports.inspect = inspect; inspect.colors = { bold: [1, 22], italic: [3, 23], @@ -201,7 +205,7 @@ function formatValue(ctx, value, recurseTimes) { ctx.customInspect && value && isFunction(value.inspect) && - value.inspect !== exports.inspect && + value.inspect !== inspect && !(value.constructor && value.constructor.prototype === value) ) { var ret = value.inspect(recurseTimes, ctx); @@ -388,51 +392,42 @@ function reduceToSingleString(output, base, braces) { } return braces[0] + base + " " + output.join(", ") + " " + braces[1]; } -const types = import.meta.require("node:util/types"); -exports.types = types; + function isArray(ar) { return Array.isArray(ar); } -exports.isArray = isArray; + function isBoolean(arg) { return typeof arg === "boolean"; } -exports.isBoolean = isBoolean; + function isNull(arg) { return arg === null; } -exports.isNull = isNull; + function isNullOrUndefined(arg) { return arg == null; } -exports.isNullOrUndefined = isNullOrUndefined; + function isNumber(arg) { return typeof arg === "number"; } -exports.isNumber = isNumber; + function isString(arg) { return typeof arg === "string"; } -exports.isString = isString; function isSymbol(arg) { return typeof arg === "symbol"; } -exports.isSymbol = isSymbol; function isUndefined(arg) { return arg === void 0; } -exports.isUndefined = isUndefined; -var isRegExp = (exports.isRegExp = exports.types.isRegExp); +var isRegExp = types.isRegExp; function isObject(arg) { return typeof arg === "object" && arg !== null; } -exports.isObject = isObject; -var isDate = (exports.isDate = exports.types.isDate); -var isError = (exports.isError = exports.types.isNativeError); -function isFunction(arg) { - return typeof arg === "function"; -} -var isFunction = (exports.isFunction = isFunction); +var isDate = types.isDate; +var isError = types.isNativeError; function isPrimitive(arg) { return ( arg === null || @@ -443,8 +438,6 @@ function isPrimitive(arg) { typeof arg === "undefined" ); } -exports.isPrimitive = isPrimitive; -exports.isBuffer = isBuffer; function pad(n) { return n < 10 ? "0" + n.toString(10) : n.toString(10); } @@ -454,11 +447,11 @@ function timestamp() { var time = [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join(":"); return [d.getDate(), months[d.getMonth()], time].join(" "); } -var log = (exports.log = function () { - console.log("%s - %s", timestamp(), exports.format.apply(exports, arguments)); -}); -var inherits = (exports.inherits = require_inherits_browser()); -var _extend = (exports._extend = function (origin, add) { +var log = function log() { + console.log("%s - %s", timestamp(), format.apply(cjs_exports, arguments)); +}; +var inherits = (inherits = require_inherits_browser()); +var _extend = function (origin, add) { if (!add || !isObject(add)) return origin; var keys = Object.keys(add); var i = keys.length; @@ -466,12 +459,12 @@ var _extend = (exports._extend = function (origin, add) { origin[keys[i]] = add[keys[i]]; } return origin; -}); +}; function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } var kCustomPromisifiedSymbol = Symbol.for("util.promisify.custom"); -var promisify = (exports.promisify = function promisify(original) { +var promisify = function promisify(original) { if (typeof original !== "function") throw new TypeError('The "original" argument must be of type Function'); if (kCustomPromisifiedSymbol && original[kCustomPromisifiedSymbol]) { var fn = original[kCustomPromisifiedSymbol]; @@ -519,8 +512,8 @@ var promisify = (exports.promisify = function promisify(original) { configurable: true, }); return Object.defineProperties(fn, getOwnPropertyDescriptors(original)); -}); -exports.promisify.custom = kCustomPromisifiedSymbol; +}; +promisify.custom = kCustomPromisifiedSymbol; function callbackifyOnRejected(reason, cb) { if (!reason) { var newReason = new Error("Promise was rejected with a falsy value"); @@ -556,11 +549,40 @@ function callbackify(original) { Object.defineProperties(callbackified, getOwnPropertyDescriptors(original)); return callbackified; } -exports.callbackify = callbackify; -export var TextDecoder = (exports.TextDecoder = globalThis.TextDecoder); -export var TextEncoder = (exports.TextEncoder = globalThis.TextEncoder); -exports[Symbol.for("CommonJS")] = 0; -export default exports; +export var TextDecoder = globalThis.TextDecoder; +export var TextEncoder = globalThis.TextEncoder; + +Object.assign(cjs_exports, { + format, + deprecate, + debuglog, + _extend, + inspect, + types, + isArray, + isBoolean, + isNull, + isNullOrUndefined, + isNumber, + isString, + isSymbol, + isUndefined, + isRegExp, + isObject, + isDate, + isFunction, + isError, + isPrimitive, + isBuffer, + log, + inherits, + promisify, + callbackify, + isDeepStrictEqual, + TextDecoder, + TextEncoder, + [Symbol.for("CommonJS")]: 0, +}); export { format, -- cgit v1.2.3 From 1c46d887286350ca102708c7fb8ae090ad88bf31 Mon Sep 17 00:00:00 2001 From: Ai Hoshino Date: Tue, 27 Jun 2023 23:23:25 +0800 Subject: Fix `node:util.callbackify` (#3428) * remove the incorrect parameters Close: https://github.com/oven-sh/bun/issues/3424 * fix error code * add callbackify tests * fix function type * ensure `done` is called when error occurred --- src/js/node/util.js | 5 +- test/js/node/util/util-callbackify.test.js | 323 +++++++++++++++++++++++++++++ 2 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 test/js/node/util/util-callbackify.test.js (limited to 'src/js/node/util.js') diff --git a/src/js/node/util.js b/src/js/node/util.js index 346f88ffb..8f20a4bc1 100644 --- a/src/js/node/util.js +++ b/src/js/node/util.js @@ -518,6 +518,7 @@ function callbackifyOnRejected(reason, cb) { if (!reason) { var newReason = new Error("Promise was rejected with a falsy value"); newReason.reason = reason; + newReason.code = "ERR_FALSY_VALUE_REJECTION"; reason = newReason; } return cb(reason); @@ -538,10 +539,10 @@ function callbackify(original) { }; original.apply(this, args).then( function (ret) { - process.nextTick(cb, null, null, ret); + process.nextTick(cb, null, ret); }, function (rej) { - process.nextTick(callbackifyOnRejected, null, rej, cb); + process.nextTick(callbackifyOnRejected, rej, cb); }, ); } diff --git a/test/js/node/util/util-callbackify.test.js b/test/js/node/util/util-callbackify.test.js new file mode 100644 index 000000000..38c20f5c0 --- /dev/null +++ b/test/js/node/util/util-callbackify.test.js @@ -0,0 +1,323 @@ +import { callbackify } from "util"; +import { createTest } from "node-harness"; + +const { describe, expect, it, createCallCheckCtx } = createTest(import.meta.path); + +const values = [ + "hello world", + null, + undefined, + false, + 0, + {}, + { key: "value" }, + Symbol("I am a symbol"), + function ok() {}, + ["array", "with", 4, "values"], + new Error("boo"), +]; + +describe("util.callbackify", () => { + describe("rejection reason", () => { + for (const value of values) { + it(`callback is async function, value is ${String(value)}`, done => { + const { mustCall } = createCallCheckCtx(done); + async function asyncFn() { + return Promise.reject(value); + } + + const cbAsyncFn = callbackify(asyncFn); + cbAsyncFn( + mustCall((err, ret) => { + try { + expect(ret).toBeUndefined(); + if (err instanceof Error) { + if ("reason" in err) { + expect(!value).toBeTrue(); + expect(err.code).toStrictEqual("ERR_FALSY_VALUE_REJECTION"); + expect(err.reason).toStrictEqual(value); + } else { + expect(String(value)).toEndWith(err.message); + } + } else { + expect(err).toStrictEqual(value); + } + + done(); + } catch (error) { + done(error); + } + }), + ); + }); + + it(`callback is promise, value is ${String(value)}`, done => { + const { mustCall } = createCallCheckCtx(done); + function promiseFn() { + return Promise.reject(value); + } + const obj = {}; + Object.defineProperty(promiseFn, "name", { + value: obj, + writable: false, + enumerable: false, + configurable: true, + }); + + const cbPromiseFn = callbackify(promiseFn); + try { + expect(promiseFn.name).toStrictEqual(obj); + } catch (error) { + done(error); + } + + cbPromiseFn( + mustCall((err, ret) => { + try { + expect(ret).toBeUndefined(); + if (err instanceof Error) { + if ("reason" in err) { + expect(!value).toBeTrue(); + expect(err.code).toStrictEqual("ERR_FALSY_VALUE_REJECTION"); + expect(err.reason).toStrictEqual(value); + } else { + expect(String(value)).toEndWith(err.message); + } + } else { + expect(err).toStrictEqual(value); + } + + done(); + } catch (error) { + done(error); + } + }), + ); + }); + + it(`callback is thenable, value is ${String(value)}`, done => { + const { mustCall } = createCallCheckCtx(done); + function thenableFn() { + return { + then(onRes, onRej) { + onRej(value); + }, + }; + } + + const cbThenableFn = callbackify(thenableFn); + cbThenableFn( + mustCall((err, ret) => { + try { + expect(ret).toBeUndefined(); + if (err instanceof Error) { + if ("reason" in err) { + expect(!value).toBeTrue(); + expect(err.code).toStrictEqual("ERR_FALSY_VALUE_REJECTION"); + expect(err.reason).toStrictEqual(value); + } else { + expect(String(value)).toEndWith(err.message); + } + } else { + expect(err).toStrictEqual(value); + } + + done(); + } catch (error) { + done(error); + } + }), + ); + }); + } + }); + + describe("return value", () => { + for (const value of values) { + it(`callback is async function, value is ${String(value)}`, done => { + const { mustSucceed } = createCallCheckCtx(done); + async function asyncFn() { + return value; + } + + const cbAsyncFn = callbackify(asyncFn); + cbAsyncFn( + mustSucceed(ret => { + try { + expect(ret).toStrictEqual(value); + expect(ret).toStrictEqual(value); + + done(); + } catch (error) { + done(error); + } + }), + ); + }); + + it(`callback is promise, value is ${String(value)}`, done => { + const { mustSucceed } = createCallCheckCtx(done); + function promiseFn() { + return Promise.resolve(value); + } + + const cbPromiseFn = callbackify(promiseFn); + cbPromiseFn( + mustSucceed(ret => { + try { + expect(ret).toStrictEqual(value); + done(); + } catch (error) { + done(error); + } + }), + ); + }); + + it(`callback is thenable, value is ${String(value)}`, done => { + const { mustSucceed } = createCallCheckCtx(done); + function thenableFn() { + return { + then(onRes, onRej) { + onRes(value); + }, + }; + } + + const cbThenableFn = callbackify(thenableFn); + cbThenableFn( + mustSucceed(ret => { + try { + expect(ret).toStrictEqual(value); + done(); + } catch (error) { + done(error); + } + }), + ); + }); + } + }); + + describe("arguments", () => { + for (const value of values) { + it(`callback is async function, value is ${String(value)}`, done => { + const { mustSucceed } = createCallCheckCtx(done); + async function asyncFn(arg) { + try { + expect(arg).toStrictEqual(value); + } catch (error) { + done(error); + } + return arg; + } + + const cbAsyncFn = callbackify(asyncFn); + cbAsyncFn( + value, + mustSucceed(ret => { + try { + expect(ret).toStrictEqual(value); + done(); + } catch (error) { + done(error); + } + }), + ); + }); + + it(`callback is promise, value is ${String(value)}`, done => { + const { mustSucceed } = createCallCheckCtx(done); + function promiseFn(arg) { + try { + expect(arg).toStrictEqual(value); + } catch (error) { + done(error); + } + + return Promise.resolve(arg); + } + const obj = {}; + Object.defineProperty(promiseFn, "length", { + value: obj, + writable: false, + enumerable: false, + configurable: true, + }); + const cbPromiseFn = callbackify(promiseFn); + try { + expect(promiseFn.length).toStrictEqual(obj); + } catch (error) { + done(error); + } + + cbPromiseFn( + value, + mustSucceed(ret => { + try { + expect(ret).toStrictEqual(value); + done(); + } catch (error) { + done(error); + } + }), + ); + }); + } + }); + + describe("this binding", () => { + const value = "hello world"; + it("callback is sync function", done => { + // TODO: + // const { mustSucceed } = createCallCheckCtx(done); + const iAmThis = { + fn(arg) { + try { + expect(this).toStrictEqual(iAmThis); + } catch (error) { + done(error); + } + return Promise.resolve(arg); + }, + }; + + iAmThis.cbFn = callbackify(iAmThis.fn); + iAmThis.cbFn(value, function (rej, ret) { + try { + expect(ret).toStrictEqual(value); + expect(this).toStrictEqual(iAmThis); + + done(); + } catch (error) { + done(error); + } + }); + }); + + it("callback is async function", done => { + const iAmThis = { + async fn(arg) { + try { + expect(this).toStrictEqual(iAmThis); + } catch (error) { + done(error); + } + return Promise.resolve(arg); + }, + }; + + iAmThis.cbFn = callbackify(iAmThis.fn); + iAmThis.cbFn(value, function (rej, ret) { + try { + expect(ret).toStrictEqual(value); + expect(this).toStrictEqual(iAmThis); + + done(); + } catch (error) { + done(error); + } + }); + }); + }); +}); -- cgit v1.2.3 From 4d2c86fd5cb29ed0c76e5763de7f935e15ea0414 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:08:01 -0700 Subject: Add util.toUSVString --- src/js/node/util.js | 5 +++++ src/js/out/modules/node/util.js | 6 +++++- test/js/node/util/util.test.js | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) (limited to 'src/js/node/util.js') diff --git a/src/js/node/util.js b/src/js/node/util.js index 8f20a4bc1..2ec4aadb9 100644 --- a/src/js/node/util.js +++ b/src/js/node/util.js @@ -552,6 +552,9 @@ function callbackify(original) { } export var TextDecoder = globalThis.TextDecoder; export var TextEncoder = globalThis.TextEncoder; +var toUSVString = input => { + return (input + "").toWellFormed(); +}; Object.assign(cjs_exports, { format, @@ -577,6 +580,7 @@ Object.assign(cjs_exports, { isBuffer, log, inherits, + toUSVString, promisify, callbackify, isDeepStrictEqual, @@ -609,4 +613,5 @@ export { promisify, callbackify, isDeepStrictEqual, + toUSVString, }; diff --git a/src/js/out/modules/node/util.js b/src/js/out/modules/node/util.js index 48b22871f..eade9e0d9 100644 --- a/src/js/out/modules/node/util.js +++ b/src/js/out/modules/node/util.js @@ -386,7 +386,9 @@ var isRegExp2 = types.isRegExp, isDate2 = types.isDate, isError = types.isNative return Object.defineProperties(fn, getOwnPropertyDescriptors(original)); }; promisify.custom = kCustomPromisifiedSymbol; -var { TextDecoder, TextEncoder } = globalThis; +var { TextDecoder, TextEncoder } = globalThis, toUSVString = (input) => { + return (input + "").toWellFormed(); +}; Object.assign(cjs_exports, { format, deprecate, @@ -411,6 +413,7 @@ Object.assign(cjs_exports, { isBuffer, log, inherits, + toUSVString, promisify, callbackify, isDeepStrictEqual, @@ -420,6 +423,7 @@ Object.assign(cjs_exports, { }); export { default2 as types, + toUSVString, promisify, log, isUndefined, diff --git a/test/js/node/util/util.test.js b/test/js/node/util/util.test.js index 45ecffda8..741b27d19 100644 --- a/test/js/node/util/util.test.js +++ b/test/js/node/util/util.test.js @@ -36,6 +36,23 @@ const deepStrictEqual = (...args) => { // Tests adapted from https://github.com/nodejs/node/blob/main/test/parallel/test-util.js describe("util", () => { + it("toUSVString", () => { + const strings = [ + // Lone high surrogate + "ab\uD800", + "ab\uD800c", + // Lone low surrogate + "\uDFFFab", + "c\uDFFFab", + // Well-formed + "abc", + "ab\uD83D\uDE04c", + ]; + const outputs = ["ab�", "ab�c", "�ab", "c�ab", "abc", "ab😄c"]; + for (let i = 0; i < strings.length; i++) { + expect(util.toUSVString(strings[i])).toBe(outputs[i]); + } + }); describe("isArray", () => { it("all cases", () => { strictEqual(util.isArray([]), true); -- cgit v1.2.3