Commit e08201b
committed
[DeepClone] Support 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 are fully described by their declaration site. deepclone_to_array()
now encodes them under the new mask marker 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). deepclone_from_array() re-evaluates the addressed constant
expression through reflection, picks the Nth closure of 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.
Unlike the extension, which matches literals by op_array identity, the
polyfill identifies them by closure name, file, line span and signature
over a per-class index of every const-expr site. The closure name
encodes nesting and method context, and a token-level count of closure
literals per source line guards the remaining aliasing case, so a
closure created at runtime never silently resolves to a const-expr
literal: declaration sites the polyfill cannot tell apart are refused.
When that count requires reading a file that is not there (deployments
shipping only precompiled opcodes), the refusal says so and points to
the extension, which needs no sources. The same literal reached through
several surfaces (an attribute argument referencing a class constant, a
trait alias, a promoted default applied by a constructor) resolves to
the first site that exposes it; promoted properties are addressed
through their constructor parameter.
The payload carries no code, only names and indices. 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. Closures created at runtime keep throwing
NotInstantiableException.1 parent e85dd72 commit e08201b
3 files changed
Lines changed: 1046 additions & 17 deletions
0 commit comments