Improve Function.prototype.toString() to return source text for non-native functions#2394
Conversation
|
I don't mind having the feature, some seem to want it. Should this be behind engine option and default to false? |
c510a50 to
dfed0aa
Compare
|
If you ask me, I'd rather do it the opposite way: this is the expected behavior per spec, so it shouldn't really surprise anyone. We could offer an option to opt-out for those who want the original behavior for some reason. As a matter of fact, this option is kind of in place already as Performance doesn't seem like a concern either as the feature is implemented in the most unobtrusive way possible, it has practically zero overhead in execution time: Jint.Benchmark.EngineComparisonBenchmark
Numbers are a bit suprising here and there but probably that's because it was run using short jobs. The extra footprint resulting from referencing the original full source code also looks negligible compared to the total memory usage. That being said, either way is fine by me. I'll leave the decision up to you. |
|
Thanks for the analysis, you make a strong point so seems that new behavior being the default is the way to go 👍🏻 |
|
Cool, I'll finish this up soon! |
726c487 to
c34687e
Compare
|
Okay, I think it's ready now. I also made an improvement to There are special cases where the This is, however, a behavioral breaking change. Let me know if you don't agree with it. Otherwise, it would probably a good idea to mention it in the release notes. |
lahma
left a comment
There was a problem hiding this comment.
Awesome, thank you again for a top-quality contribution! The breaking change is for quite advanced API so I wouldn't be that concerned 👍🏻
… (#2562) Since v4.9.0 (#2394) every parsed function node pins the entire source string (Node.UserData = ctx.Input) so Function.prototype.toString() can return the original source, released only lazily on the first toString() call. Applications that cache many large prepared scripts and never call toString() therefore retain hundreds of MB of duplicated source strings (issue #2560: ~250 MB, the same ~500 KB string per engine). Add an opt-in RetainFunctionSourceText parsing option (default false) plus an engine-level Options.RetainFunctionSourceText toggle. When disabled (the default) the source text is not retained and toString() returns the "function name() { [native code] }" placeholder. - RetainFunctionSourceText on IParsingOptions + ScriptParsingOptions/ModuleParsingOptions - ApplyTo wires OnNode conditionally; prepared path passes null/ctx.Input in AstAnalyzer - Options.RetainFunctionSourceText + fluent extension; engine default parser honors it - Test262 harness and the #2394 source-asserting tests opt in to keep conformance green - Retained-memory regression test: 25 cached ~800 KB scripts retain 20.1 MB vs 1.8 MB Claude-Session: https://claude.ai/code/session_01Bf1LESD4X8dMNPwLKJCAh9 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
I took a stab at implementing
Function.prototype.toString()according to the spec. So this is another attempt at what #1726 tried to achieve, it's just takes the correct path instead of trying to convert the AST back to source code.However, this requires the full source text to be kept around as it would be a performance killer to materialize the source text for functions eagerly. So this is deferred until execution. Once the source text is created, the reference to the full code is released. This way GC can at least collect that once source text for every function is materialized.
It passes the relevant test262 tests (it's failing here because it requires a minor non-breaking change on the parser side, which hasn't been released to Nuget yet). According to my manual tests, it also produces correct results for crazy dynamic cases like
or
If the PR is welcome, I'll finalize it soon.