-
Notifications
You must be signed in to change notification settings - Fork 55
Print every native function's source on one line #545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| // Every kind of native function prints the same single-line NativeFunction | ||
| // source: `function name() { [native code] }`. This is the shape V8 uses. | ||
| // | ||
| // Before this, InternalFunction subclasses (Map, Set, WeakMap, DataView, ...) | ||
| // printed one line while host functions (NativeExecutable), builtin functions | ||
| // (FunctionExecutable), bound functions and remote functions printed | ||
| // function name() {\n [native code]\n} | ||
| // lodash's isNative() builds a RegExp from Object.prototype.hasOwnProperty's | ||
| // source and tests Map against it, so the two shapes made lodash believe Map | ||
| // was not native and fall back to a linear-scan cache in cloneDeep/memoize. | ||
|
|
||
| function shouldBe(actual, expected) { | ||
| if (actual !== expected) | ||
| throw new Error(`bad value: ${JSON.stringify(actual)}, expected ${JSON.stringify(expected)}`); | ||
| } | ||
|
|
||
| const toString = Function.prototype.toString; | ||
| const source = (fn) => toString.call(fn); | ||
| const nativeFunction = /^function [^(]*\(\) \{ \[native code\] \}$/; | ||
|
|
||
| // Host functions (NativeExecutable). | ||
| shouldBe(source(Object.prototype.hasOwnProperty), "function hasOwnProperty() { [native code] }"); | ||
| shouldBe(source(Array.prototype.push), "function push() { [native code] }"); | ||
| shouldBe(source(Object.create), "function create() { [native code] }"); | ||
| shouldBe(source(Math.max), "function max() { [native code] }"); | ||
| shouldBe(source(Symbol.for), "function for() { [native code] }"); | ||
|
|
||
| // Constructors that are InternalFunction subclasses. | ||
| shouldBe(source(Map), "function Map() { [native code] }"); | ||
| shouldBe(source(Set), "function Set() { [native code] }"); | ||
| shouldBe(source(WeakMap), "function WeakMap() { [native code] }"); | ||
| shouldBe(source(DataView), "function DataView() { [native code] }"); | ||
| shouldBe(source(Array), "function Array() { [native code] }"); | ||
| shouldBe(source(Function), "function Function() { [native code] }"); | ||
|
|
||
| // Constructors that are JSFunction subclasses with a NativeExecutable. | ||
| shouldBe(source(Promise), "function Promise() { [native code] }"); | ||
| shouldBe(source(Number), "function Number() { [native code] }"); | ||
| shouldBe(source(String), "function String() { [native code] }"); | ||
| shouldBe(source(Boolean), "function Boolean() { [native code] }"); | ||
|
|
||
| // Builtin functions written in JavaScript (FunctionExecutable). | ||
| shouldBe(source(Array.prototype.map), "function map() { [native code] }"); | ||
| shouldBe(source(Array.from), "function from() { [native code] }"); | ||
| shouldBe(source(Promise.prototype.then), "function then() { [native code] }"); | ||
| shouldBe(source(Function.prototype.call), "function call() { [native code] }"); | ||
|
|
||
| // Native accessors keep the `get `/`set ` prefix in the name. | ||
| shouldBe(source(Object.getOwnPropertyDescriptor(Map.prototype, "size").get), "function get size() { [native code] }"); | ||
| shouldBe(source(Object.getOwnPropertyDescriptor(RegExp.prototype, "flags").get), "function get flags() { [native code] }"); | ||
|
|
||
| // Bound functions. | ||
| shouldBe(source(function foo() {}.bind(null)), "function foo() { [native code] }"); | ||
| shouldBe(source(Map.bind(null)), "function Map() { [native code] }"); | ||
| shouldBe(source(Object.prototype.hasOwnProperty.bind({})), "function hasOwnProperty() { [native code] }"); | ||
|
|
||
| // Callable objects that are neither JSFunction nor InternalFunction. | ||
| shouldBe(nativeFunction.test(source(new Proxy(function () {}, {}))), true); | ||
| shouldBe(nativeFunction.test(source(new Proxy(Map, {}))), true); | ||
|
|
||
| // The result is cached per executable. The cached string has the same shape. | ||
| shouldBe(source(Object.prototype.hasOwnProperty), source(Object.prototype.hasOwnProperty)); | ||
| shouldBe(source(Array.prototype.map), source(Array.prototype.map)); | ||
|
|
||
| // lodash's isNative(): a RegExp built from hasOwnProperty's source must match | ||
| // every other native function. | ||
| const reRegExpChar = /[\\^$.*+?()[\]{}|]/g; | ||
| const reIsNative = RegExp("^" + source(Object.prototype.hasOwnProperty).replace(reRegExpChar, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$"); | ||
| for (const fn of [Map, Set, WeakMap, WeakRef, DataView, Promise, Symbol, Object.create, Array.prototype.map, Function.prototype.bind, Map.prototype.get, Object.prototype.hasOwnProperty.bind({})]) { | ||
| if (!reIsNative.test(source(fn))) | ||
| throw new Error(`${fn.name} did not pass the lodash isNative RegExp: ${JSON.stringify(source(fn))}`); | ||
| } | ||
|
|
||
| // Everything reachable from the global object that is a native function prints one line. | ||
| const seen = new Set(); | ||
| function visit(object, path, depth) { | ||
| if (object === null || (typeof object !== "object" && typeof object !== "function") || seen.has(object) || depth > 3) | ||
| return; | ||
| seen.add(object); | ||
| if (typeof object === "function") { | ||
| const text = source(object); | ||
| if (text.includes("[native code]") && !nativeFunction.test(text)) | ||
| throw new Error(`${path} prints a native function on more than one line: ${JSON.stringify(text)}`); | ||
| } | ||
| let keys; | ||
| try { | ||
| keys = Reflect.ownKeys(object); | ||
| } catch { | ||
| return; | ||
| } | ||
| for (const key of keys) { | ||
| let descriptor; | ||
| try { | ||
| descriptor = Reflect.getOwnPropertyDescriptor(object, key); | ||
| } catch { | ||
| continue; | ||
| } | ||
| if (!descriptor) | ||
| continue; | ||
| const name = typeof key === "symbol" ? `[${key.description}]` : key; | ||
| if ("value" in descriptor) | ||
| visit(descriptor.value, `${path}.${name}`, depth + 1); | ||
| if (descriptor.get) | ||
| visit(descriptor.get, `${path}.${name}[get]`, depth + 1); | ||
| if (descriptor.set) | ||
| visit(descriptor.set, `${path}.${name}[set]`, depth + 1); | ||
| } | ||
| } | ||
| visit(globalThis, "globalThis", 0); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,7 +108,7 @@ JSString* NativeExecutable::toStringSlow(JSGlobalObject *globalObject) | |
|
|
||
| auto throwScope = DECLARE_THROW_SCOPE(vm); | ||
|
|
||
| JSValue value = jsMakeNontrivialString(globalObject, "function "_s, name(), "() {\n [native code]\n}"_s); | ||
| JSValue value = jsMakeNontrivialString(globalObject, "function "_s, name(), "() { [native code] }"_s); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Switching host-function toString to one line breaks several enabled ChakraCore baseline tests that pass on the base branch (they print only NativeExecutable-backed functions, so #155 did not touch them): Function/toString.js, strict/05.arguments_sm.js, Array/protoLookup.js, es6/letconst_global_shadow_builtins.js. run-javascriptcore-tests will report new failures after merging. Fix: rebaseline every Extended reasoning...On the base commit d71031a, Verification: normal — The change at Source/JavaScriptCore/runtime/NativeExecutable.cpp:111 switches host-function toString from |
||
|
|
||
| RETURN_IF_EXCEPTION(throwScope, nullptr); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 (optional) The injected-script native sniff was updated to
"[native code] }"but the frontend counterpartisFunctionStringNativeCodein Source/WebInspectorUI/UserInterface/Base/Utilities.js:1690 still checksendsWith("{\n [native code]\n}"); after this change no native function's toString ends that way, so the Web Inspector object tree stops recognizing any native function (previously host/builtin/bound functions still matched). Fix: updateisFunctionStringNativeCodeto test the single-line suffix (e.g.endsWith("{ [native code] }")) so both inspector halves agree. Pre-existing for InternalFunction only; this diff extends the breakage to every native function.Extended reasoning...
Before this diff,
Object.prototype.hasOwnProperty.toString()and other NativeExecutable/FunctionExecutable builtins producedfunction name() {\n [native code]\n}, soisFunctionStringNativeCode(WebInspectorUI/UserInterface/Base/Utilities.js:1688-1691) returned true for them and ObjectTreePropertyTreeElement.js:299/312 and ObjectTreeBaseTreeElement.js:222 rendered them as native (simplifiedfunction, no jump-to-source context menu). After this diff every native path emitsfunction name() { [native code] }, which never ends with{\n [native code]\n}, soisFunctionStringNativeCodereturns false for all functions and the inspector frontend treats every native function as user code. The PR updated the backend sniff in InjectedScriptSource.js but not the matching frontend helper. Note: the JSCOnly port Bun builds does not ship WebInspectorUI, so this only affects consumers of this fork that build the full inspector UI.Verification: nit — The mismatch is real.
Source/WebInspectorUI/UserInterface/Base/Utilities.js:1688-1691still readsreturn str.endsWith("{\n [native code]\n}");, and its callers atSource/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js:299,312andSource/WebInspectorUI/UserInterface/Views/ObjectTreeBaseTreeElement.js:222gate the native-parameter-string display and the…