Repository navigation
Conversation
…truct data An embedder can have a function whose `new f()` must evaluate to a primitive. bun:ffi `CString` is one: `new CString(ptr)` is a string. Such a function cannot be a constructor: every algorithm that constructs its argument trusts [[Construct]] to return an object. Array.from writes into the result with put_by_val_direct, and Interpreter::executeConstruct casts it with asObject. `new f()` and `super()` on an InternalFunction do not read construct data. setUpCall, linkFor and virtualFor jump to the InternalFunction trampoline, which loads m_functionForConstruct from the cell. Everything else asks getConstructData first. So a new type-info flag, ReportsNoConstructData, makes InternalFunction::getConstructData report CallData::Type::None while m_functionForConstruct stays installed. isConstructor() is false for such a function. Array.of and Array.from make a plain array, and a species lookup, Reflect.construct, bind, Proxy and `extends` throw a TypeError. `new f()` keeps its result on every tier. No function sets the flag here. $vm.createInternalFunctionWithoutConstructData() makes one for the test. * JSTests/stress/internal-function-new-without-construct-data.js: Added. * Source/JavaScriptCore/runtime/InternalFunction.cpp: (JSC::InternalFunction::getConstructData): * Source/JavaScriptCore/runtime/JSTypeInfo.h: (JSC::TypeInfo::reportsNoConstructData const): * Source/JavaScriptCore/tools/JSDollarVM.cpp: (JSC::JSDollarVM::finishCreation):
|
Preview build of 42d31a6: |
Collaborator
|
@robobun wake up!! |
Collaborator
Author
|
Status:
One decision is needed from a maintainer. After the bun change,
If those forms must keep their result, this change is the wrong tool, because Edited: the first version of this comment said that all of these forms fail an assertion on a debug build. The literal |
…t a type-info flag The flag ReportsNoConstructData added a branch to InternalFunction::getConstructData, which every native constructor runs, and it used one of the four free type-info bits. The method table says the same with neither cost. Everything that reads the construct data of an InternalFunction goes through the method table, so a subclass that declares `using JSCell::getConstructData;` reports no construct data. m_functionForConstruct stays installed for `new f()`. The constructor of InternalFunction asserted that no subclass changes that slot, because compiler threads call it. The assertion now accepts JSCell::getConstructData, which returns an empty CallData and reads nothing. * JSTests/stress/internal-function-new-without-construct-data.js: * Source/JavaScriptCore/runtime/InternalFunction.cpp: (JSC::InternalFunction::InternalFunction): (JSC::InternalFunction::getConstructData): * Source/JavaScriptCore/runtime/JSCellInlines.h: (JSC::JSCell::isConstructorWithConcurrency): * Source/JavaScriptCore/runtime/JSTypeInfo.h: (JSC::TypeInfo::reportsNoConstructData const): Deleted. * Source/JavaScriptCore/tools/JSDollarVM.cpp:
new handler and report no construct datanew handler and report no construct data
robobun
added a commit
to oven-sh/bun
that referenced
this pull request
Sep 29, 2026
In that build a subclass of InternalFunction may take JSCell::getConstructData. No class in bun does it at this commit.
This was referenced Sep 29, 2026
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. The bun side is oven-sh/bun#44226, pinned to the preview build here.
Problem
CStringmust return a string primitive fromnew CString(ptr)(bun:ffi: use the engine-native FFI when available bun#35246). As an InternalFunction it is a constructor to every engine algorithm, and those trust[[Construct]]to return an object.Array.from.call(CString, [1, 2, 3])aborts a release build:RELEASE_ASSERT(baseValue.isObject())inllint_slow_path_put_by_val_direct.Fix
InternalFunctionmay putJSCell::getConstructDatain its method table (using JSCell::getConstructData;). It then reports no construct data, andm_functionForConstructstays installed.InternalFunctionasserted that no subclass changes that slot. The assertion now accepts this one function.new f()andsuper()read no construct data on an InternalFunction:setUpCall,linkForandvirtualForjump to a trampoline that loadsm_functionForConstruct. Every other construct asks the method table first.JSTests/stress/internal-function-new-without-construct-data.js, debug ASANjsc, 7 tier configurations.Background
isConstructor(),construct(),Reflect.construct,bind,Proxy,Array.of,Array.from,extendsand species read construct data through the method table.JSCell::getConstructDatareturns an emptyCallDataand reads nothing.getConstructDatafor every native constructor, and one of 4 free type-info bits.Downsides
InternalFunction::getConstructDatahas the same 13 instructions before and after, and the release text of bun has the same size (see Notes).super()of a class whose[[Prototype]]is such a function still runs the handler. The handler must checknew.target.Notes
Why not an
isObjectcheck inInterpreter::executeConstruct. It runs after the handler has read the length as a pointer, andArray.fromis a JS builtin that never gets there.The test helper.
$vm.createInternalFunctionWithoutConstructData()makes a function of a class that takesJSCell::getConstructData. Its call handler returns"call:<argument count>"and its construct handler returns"new:<argument count>", or throws a TypeError whennew.targetis not the function.JSDollarVMis compiled into the library only when assertions are on, so a release build of bun does not have it.Tier configurations run (local build of this branch,
jsc, Debug with ASAN, Linux x64), all with--validateExceptionChecks=true: default with FTL,--useLLInt=false,--useJIT=false,--useFTLJIT=false, DFG eager with--useConcurrentJIT=falseand graph validation, FTL eager with--useConcurrentJIT=falseand graph validation,--forceOSRExitToLLInt=true.Without the change to the assertion a build with assertions stops when the helper makes its function:
ASSERTION FAILED: methodTable()->getConstructData == InternalFunction::info()->methodTable.getConstructData.Existing tests run on the same build, unchanged, all pass:
is-constructor.js,is-constructor-should-be-correct-in-ai.js,internal-function-construct.js,internal-function-call.js,reflect-construct.js,proxy-construct.js,array-of-non-array-constructor.js,array-species-functions.js,array-from-with-iterable.js,class-subclassing-function.js.Release builds, measured (Linux x64).
nm -Sandobjdump -don the ltojscoff20ce774and of42d31a6b:InternalFunction::getConstructDatais 41 bytes and the same 13 instructions in both. A release build of bun has 80,692,188 bytes of text with each of the two packages, and noJSDollarVMsymbol. Thejscshell has$vmin every build, so its lto text grows from 47,724,559 to 47,726,911 bytes.The flag version, measured. gdb single-stepped one call of
InternalFunction::getConstructDatain the ltojscoff20ce774and of the first commit of this PR: 13 to 18 instructions for a function that is a constructor, 41 to 61 bytes.What this does not cover. A native function made with
JSFunction::createdoes not read its construct data from the method table (getConstructDataInlineinJSObjectInlines.hgoes toJSFunction::getConstructDatafor aJSFunctionTypecell). The functions of oven-sh/bun#34575 are of that kind.