Skip to content

Feature/scalar methods#22329

Closed
kralmichal wants to merge 14 commits into
php:masterfrom
kralmichal:feature/scalar-methods
Closed

Feature/scalar methods#22329
kralmichal wants to merge 14 commits into
php:masterfrom
kralmichal:feature/scalar-methods

Conversation

@kralmichal

Copy link
Copy Markdown

No description provided.

…nment

Compile ZEND_AST_TYPED_VAR_DECL into a cv_types entry plus a new
ZEND_ASSIGN_TYPED opcode that verifies/coerces the RHS against the
declared scalar type using the same scalar path typed properties use,
honoring strict/weak mode. The RHS is copied into a separated tmp before
coercion so a CONST literal is never mutated in place. cv_types is kept
parallel to vars[] as new CVs are interned, and its synthesized name is
released on teardown.
…ls/$this/$GLOBALS

FIX A: zend_compile_assign() now emits ZEND_ASSIGN_TYPED (instead of plain
ZEND_ASSIGN) for a simple CV target that has a non-NULL cv_types[] entry, so
the declared type is enforced on every (re)assignment, not just the
initializer. Operand setup mirrors the initializer path in
zend_compile_typed_var_decl(); the CV index is computed via
EX_VAR_TO_NUM(var_node.u.op.var), matching the VM handler. Because cv_types[]
persists, this also covers `int $x; $x = ...;` and post-unset reassignment.
Only the simple-CV-target case changes; dim/prop/list/dynamic targets are
untouched (a non-CV var_node falls through to ZEND_ASSIGN unchanged).

FIX B (finding I1): zend_compile_typed_var_decl() now rejects auto-globals,
$this and $GLOBALS with E_COMPILE_ERROR, mirroring how the canonical CV path
(zend_try_compile_cv) handles these special names. The variable name is now
interned via zval_make_interned_string() so vars[] stays consistent with the
canonical path and opcache's interned-string accounting.
cv_types[] (the parallel array of zend_property_info* describing typed local
variables) was request-memory only and had no opcache handling: with opcache
enabled it leaked on compile, and a file-cached op_array reloaded in another
process carried a dangling cv_types pointer, causing a SIGSEGV when a typed
local was assigned (finding C1).

Mirror the existing op_array->vars handling in all three persistence layers:

- zend_persist_calc.c: account for the pointer array plus, per typed slot, the
  zend_property_info struct and its interned name.
- zend_persist.c: zend_shared_memdup the array and each zend_property_info into
  SHM and intern each name. Types are pure scalar masks, so zend_persist_type()
  is a no-op (no class/list pointers to relocate).
- zend_file_cache.c: SERIALIZE_PTR/UNSERIALIZE_PTR the array and each entry, plus
  SERIALIZE_STR/UNSERIALIZE_STR each name, in both the full walk and the shared-
  method (refcount) fast paths. serialize/unserialize_type are no-ops for masks.

destroy_op_array() needs no change: persisted op_arrays have refcount==NULL and
return early before the vars[]/cv_types[] efree, so the existing guard that
protects vars[] already protects cv_types[].

Verified: SHM run with report_memleaks shows no leak; file-cache write+reload
(including overwriting the source with garbage under validate_timestamps=0 to
force a pure cache load) prints correctly and does not segfault, for both plain
functions and class methods.
Add 13 .phpt tests covering the typed-locals feature: basic scalars,
weak-mode coercion on init and reassign, strict-mode TypeErrors,
uninitialized-then-assign, nullable types, non-numeric weak TypeError,
and compile errors for non-scalar type / redeclaration / $this / superglobals.
Includes an opcache_persist test guarding cv_types SHM/file-cache continuity.
…erence)

Close gap #2: references could bypass a typed local's declared type
(`int $x=1; $y=&$x; $y="s";` and `function m(&$r){$r="s";} m($x);`).

Reuse the typed-property reference machinery: when a typed CV is aliased
by a reference, attach its op_array-owned synthesized zend_property_info as
a type source on the resulting zend_reference, so writes through the
reference are checked by zend_verify_ref_assignable_zval(). The CV stays a
plain value until referenced (no read overhead).

ENFORCED reference-creation paths (source attached at ref creation):
- ZEND_ASSIGN_REF (`$y = &$x`): new helper zend_assign_to_typed_cv_reference()
  mirrors zend_assign_to_typed_property_reference() and handles either/both
  operands typed, the re-binding DEL-before-ADD, and the `$x = &$x` self-alias.
- by-ref argument passing (ZEND_SEND_REF, ZEND_SEND_VAR_EX) and ZEND_MAKE_REF:
  attach when a typed CV is freshly wrapped into a reference.

FORBIDDEN at compile time (E_COMPILE_ERROR; these ref paths are not enforced,
so they must not silently bypass the type):
- `global $x`, `static $x`, `foreach (... as &$x)` on a typed local.

Lifecycle: the source list stores only the pointer and never frees the
property_info; ZEND_REF_DEL_TYPE_SOURCE frees only the list allocation. The
synthesized info is owned by the op_array (freed in destroy_op_array), so the
matching DEL is performed when the CV slot is torn down or moved out of the
frame -- in i_free_compiled_variables() and zend_detach_symbol_table() (the
top-level/symbol-table path) -- before the CV drops its refcount. A frame uses
exactly one of those teardown paths, so ADD/DEL stay balanced one-per-CV-slot
and the reference can never outlive the op_array with a dangling source.

Reference type-error messages (zend_throw_ref_type_error_zval/_type,
zend_throw_conflicting_coercion_error, zend_verify_property_type_error) now
branch on prop->ce: synthesized local infos have ce == NULL, so they print
"local variable $x" instead of dereferencing ce->name (which would segfault).

Tests: 7 new .phpt under tests/lang/typed_locals (alias enforce/coerce, by-ref
param weak+strict, target-typed alias, and the three compile-error forbids).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant