Repository navigation
Conversation
…ses undefined as this A sloppy function with a simple parameter list that mentions `arguments` and captures no parameter keeps its parameters in a DirectArguments object. emitResolveScope() returned the register that holds that object as the scope of such a parameter. FunctionCallResolveNode and TaggedTemplateNode moved it into the this register of the call, and DeleteResolveNode used it as the base of del_by_id. So a callee got the caller's arguments object as |this|, and `delete parameter` deleted the property of that name from the arguments object. emitResolveScope() now returns null for VarKind::DirectArgument, as it does for a local. emitGetFromScope() and emitPutToScope() name the arguments register themselves, and argumentsRegister() is removed, so no emitter can use that register as a value. FunctionCallResolveNode and TaggedTemplateNode load undefined into the this register for a mapped parameter, and `delete parameter` is false. EvalFunctionCallNode asserts that its callee is not a mapped parameter: a function that calls eval captures its parameters.
Collaborator
Author
|
Status: draft on purpose. This is an upstream candidate, not a request to merge into the fork (see Route in the notes of the description). How to reproduce ( function victim(callback, secret) { if (arguments[5] === 1) return; callback(); return secret; }
print(victim(function () { if (this !== undefined && this !== globalThis) this[1] = "overwritten"; }, "the caller's own parameter"));
function deleteIt(length) { return [delete length, arguments[0], arguments.length].join(); }
print(deleteIt(7));
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft on purpose: this patch is for WebKit/WebKit first, not for a merge into the fork. Found in the review of #784, not by a user.
Problem
this. Soexpect(new Counter().set).toThrowError(TypeError)of jasmine-core fails on Bun and passes on Node.delete pon a mapped parameter deletes the propertypofarguments.emitResolveScope(bytecompiler/BytecodeGenerator.cpp:2742) returns the arguments register as the scope of a mapped parameter. The call emitters (NodesCodegen.cpp:1525) move it intothis.Fix
emitResolveScopereturns null for a mapped parameter.emitGetFromScopeandemitPutToScopename the arguments register themselves.argumentsRegister()is removed.FunctionCallResolveNodeandTaggedTemplateNodeloadundefinedinto the this register.delete pisfalse. ECMA-262 requires both.JSTests/stress/direct-arguments-parameter-call-receiver.js(new, fails on main).run-javascriptcore-tests, main against the branch: no new failure.Background
argumentsand captures no parameter keeps them in aDirectArgumentsobject: mapped parameters.this, forwith.op_to_thisandJSValue::toThisturn a scope intoundefined, but not an arguments object.emitResolveScopekeep the register.Downsides
thisnow getsundefinedorglobalThis, as in V8.jsc.text: +256 bytes. Bytecode of other code blocks: 0 of 13055 differ (JetStream2).Notes
Route. This PR does not ask for a merge into the fork as it stands.
return argumentsRegister();is atBytecodeGenerator.cpp:2716there). The patch has noUSE(BUN_JSC_ADDITIONS)guard. It applies to WebKit/WebKit main withpatch -p1: 9 hunks, one with fuzz, because the fork hasemitTDZCheck(target, variable)where upstream hasemitTDZCheck(target). The test is a new file.(a?.b)(x)outside the optional chain WebKit/WebKit#74773, [JSC] Tagged template with a parenthesized optional chain tag should keepthisWebKit/WebKit#75047 and [JSC] Tagged template with a super property tag should be called with the currentthisWebKit/WebKit#75355. They are receivers of calls in the same emitters. I cannot file the bugs.webkit.org entry or the WebKit/WebKit PR.NodesCodegen.cppare enough for the behavior, withgenerator.emitResolveScope(nullptr, var)in place ofnullptr. TheBytecodeGeneratorpart (the null return, the removed accessor, theRELEASE_ASSERTchange) is hardening on top of them.thisvalue when calling functions WebKit/WebKit#35527, has nodeletepart.main. Or it becomes a cherry-pick of that commit, as [JSC] Array profiles should not turn ordinary arrays intoArrayStorage(cherry-pick WebKit/WebKit#75376) #785 was. [JSC](a?.b)(): a nullishathrows a TypeError, and a class field initializer makes the call #650 closed that way.What changes. The caller is sloppy, mentions
arguments, has a simple parameter list and captures no parameter.mainis 87ee10d, the base of the branch. Line numbers in this description are those of that commit. Node v26.3.0 gives the right column.cb(),(cb)(),cb?.(),cb(...xs), strict callee[object Arguments]undefined[object Arguments]globalThiscb`x`[object Arguments]undefinedorglobalThiscb()withcb = Object.prototype.valueOfTypeErrorcb()with a Proxy,thisArgumentof theapplytrapundefineddelete ptrue, andargumentsloses its propertyp, for examplelengthfalsethis[1]changes the second parameter of the caller.awaitis affected too. Not affected:o.m(),cb.call(x),arguments[0](), a caller that does not mentionarguments, a strict caller, a captured parameter, a parameter namedeval, code inwith.refEnv.WithBaseObject(), which isundefinedfor a function environment. DeleteBinding returnsfalsefor a binding that cannot be deleted.In Bun (1.4.3,
.cjsfile). Node gives the second value.arguments;. A repro for Bun must use the object, asarguments[5]does above.toThrowError(jasmine.js:6885to6894):comparereadsargumentsand callsactual(). A method that writesthis.n, passed without its object, does not throw on Bun. Node throwsTypeError, and the expectation passes.mock.contexts[0]ofbun:testis the arguments object of the caller for such a call."use strict"directive in a function body of a.cjsfile. So a caller that is strict only by that directive is affected in Bun too.Bytecode at the changed sites (
jsc --dumpGeneratedBytecodes=true).tag`x`: 9 instructions and 29 bytes become 8 and 26, +1 constant.delete p: 5 instructions and 13 bytes (del_by_idon the arguments register) become 5 and 11 (mov False), +1 constant.Measurements. Two Release builds of
jscwith the same flags, main 87ee10d and this branch.cli.js,testIterationCount=1, about 42600 code blocks generated, 13058 distinct). A control of main against main gives 0. The 3 blocks that differ arecall,eachandmapof recast. Each has one bare call of a mapped parameter.JSTests/stress/*arguments*.js: 0 of 2207 distinct code blocks differ.jsc.text: 28562478 to 28562734 bytes (size -A). The bytecompiler object: +259 bytes.VarKindcompare at each call, tag ordeleteof a name that is not a local. That is 25245 sites in the JetStream2 run. Instructions added to emitted code: 0.Callnode, so the arguments elimination of the FTL can remove it when the callee is not inlined. Heap bytes per call for that shape: 64.0 before, 0.0 after ($vm.heapTotalBytesAllocated()over 5000000 calls). With--useFTLJIT=0it is 64.0 on both builds.run-jsc-stress-tests --report-execution-time). On main it fails withbareCall with a strict callee: expected undefined but got [object Arguments].perf,valgrindandbloatyare not installed on this machine. There is no instruction count.A consumer that this PR missed would crash, not leak. The new test compiles the 4 places that a mapped parameter can reach. A build with one of them removed crashes on the test: the
RELEASE_ASSERTchange (exit 134), the arm inFunctionCallResolveNode, the arm inTaggedTemplateNode, the condition inDeleteResolveNode(exit 139 each).EvalFunctionCallNodehas the same shape but only gets anASSERT: a function that calls a name spelledevalcaptures all of its parameters, so that name is never a mapped parameter.The 44 callers of
emitResolveScope, by what they do with the result.FunctionCallResolveNode,TaggedTemplateNodethisof the call. A mapped parameter now takes a separate arm.EvalFunctionCallNodethisof the call. Not reachable with a mapped parameter, see above.DeleteResolveNodedel_by_id. A mapped parameter now returnsfalsebefore it.ResolveNode,PostfixNode::emitResolve,PrefixNode::emitResolve,TypeOfResolveNode,ReadModifyResolveNode,ShortCircuitReadModifyResolveNode,ForInNode::emitLoopHeader,ForOfNode,EmptyVarExpression(type profiler only)emitGetFromScopeandemitPutToScope, which do not use it for a mapped parameterAssignResolveNode,BindingNode::bindValue,AssignmentElementNode::bindValueemitTDZCheckIfNecessaryletbinding, a non-local name, a parameter of a non-simple list, a function declarationemitTDZCheckIfNecessaryis reached with a null scope when a mapped parameter has the name of aletof an enclosing scope that is in its dead zone. The check is by name. TheRELEASE_ASSERTthere now accepts a mapped parameter.Suites.
Tools/Scripts/run-javascriptcore-testsas CI runs it (JSTests, LayoutTests/js, PerformanceTests, 124139 runs), on a build of main and a build of the branch: 0 runs fail only with the branch. 2326 runs in 218 files fail on both builds on that machine. Most of them are Intl tests: its ICU data cannot format a number.*arguments*.jscomparison and the timing of the new test are from the revision before the last one. The last revision replaced an arm inEvalFunctionCallNodethat cannot run by theASSERT, and changed comments. It changed no emitted bytecode: 0 of the 13058 JetStream2 blocks differ between the two revisions.ENABLE_ASSERTS=ON. That build also ran the 7635 runs of the tests whose name hasarguments,eval,tagged-template,delete,spread,call-varargs,tail-call,to-this,this-valueoroptional-chain: no assertion fails.--useTypeProfiler=true,--useControlFlowProfiler=true,--forceDebuggerBytecodeGeneration=trueand--useJIT=false, and in V8 with the message text of one assertion changed.Self-review, not taken.
undefinedalso for a callee in a closure variable. Not taken here: it is a separate change, at about 2400 call sites of the JetStream2 run against 3.emitResolveScope(nullptr, var). Not taken: it is only needed without theBytecodeGeneratorpart, see Route....and super[key]...with the method's this #438 and TaggedTemplateNode: call (a?.b)...with |this| = a, and super.tag...with the method's this #534, and open an issue for the other differences below. Not taken: outside this PR.Open PR #516 rewrites the arm below each new arm in the call emitters.
git merge-treeof #516 gives 8 conflict hunks against main and the same 8 against this branch (BytecodeGenerator.h1,DFGByteCodeParser.cpp5,JITPropertyAccess.cpp2).NodesCodegen.cppmerges with no conflict.Not changed here.
this. A JS callee that usesthisconverts it. A native must calltoThis([JSC] A native called with no receiver must not work on the caller's scope #784).arguments[0]. V8 shows the function there.caller.arguments, as in V8.WEBKIT_VERSIONthat brings the fix: the jasmine case andmock.contexts.