Skip to content

Commit d4beb94

Browse files
Round-trip closures declared in constant expressions
PHP 8.5 allows anonymous closures in constant expressions: attribute arguments, class constants, property and parameter defaults, property hooks. They are compile-time checked to be static and capture-free, so they carry no state and are fully described by their declaration site. deepclone_to_array() now encodes them under a new mask marker (LONG 1) as [class, site, attrIndex|null, closureIndex, startLine], where site is "" (the class), "NAME" (constant or enum case), "$name" (property), "name()" (method), "name()#N" (parameter) or "$name::get()" / "$name::set()#N" (property hooks). Identification is exact: a const-expr closure shares its opcodes with the op_array embedded in the declaring AST, so the scope class's sites are scanned in declaration order for a pointer match. Promoted properties are addressed through their constructor parameter, the canonical surface. deepclone_from_array() re-evaluates the addressed constant expression, picks the Nth closure from a depth-first walk (arrays in order, objects through their array-cast properties) and verifies the declaration line still matches, so payloads that outlived a code change fail loudly ("stale payload") instead of resolving to a moved closure. The payload carries no code, only names and indices; the resolved code is whatever the loaded class declares. allowed_classes gates both directions: "Closure" must be allowed before any constant expression is evaluated on the to_array side, and the payload-named class must be allow-listed before it is autoloaded on the from_array side, keeping unserialization free of payload-driven constructor execution. Closures created at runtime (and any closure the scanner cannot match) keep throwing NotInstantiableException as before.
1 parent f0c7f81 commit d4beb94

3 files changed

Lines changed: 986 additions & 17 deletions

File tree

0 commit comments

Comments
 (0)