From 7ca6a2ea61bf0dd102dbf5361f70bfe977fccb74 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:29:36 +0200 Subject: [PATCH] Fix escape analysis for objects implementing ArrayAccess Objects that implement ArrayAccess can run arbitrayr code on isset(), dimensions accesses etc. SCCP can then run on stale property values that were manipulated by such ArrayAccess handlers. We also take into accuont that we don't know the interfaces yet of unlinked classes. --- Zend/Optimizer/escape_analysis.c | 3 ++ .../opt/escape_analysis_array_access.phpt | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 ext/opcache/tests/opt/escape_analysis_array_access.phpt diff --git a/Zend/Optimizer/escape_analysis.c b/Zend/Optimizer/escape_analysis.c index 3c2864d14fb4..46597ddc5e9e 100644 --- a/Zend/Optimizer/escape_analysis.c +++ b/Zend/Optimizer/escape_analysis.c @@ -175,6 +175,9 @@ static bool is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, i && !ce->__set && !ce->__isset && !ce->num_hooked_props + /* ArrayAccess methods receive the object, interfaces of unlinked classes are unknown */ + && !ce->arrayaccess_funcs_ptr + && ((ce->ce_flags & ZEND_ACC_LINKED) || !ce->num_interfaces) && !(ce->ce_flags & forbidden_flags) && (ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { return 1; diff --git a/ext/opcache/tests/opt/escape_analysis_array_access.phpt b/ext/opcache/tests/opt/escape_analysis_array_access.phpt new file mode 100644 index 000000000000..2864d3e4b2f5 --- /dev/null +++ b/ext/opcache/tests/opt/escape_analysis_array_access.phpt @@ -0,0 +1,51 @@ +--TEST-- +Escape analysis: objects implementing ArrayAccess escape through dimension accesses +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--EXTENSIONS-- +opcache +--FILE-- +x = 3; return true; } + function offsetGet($o): mixed { $this->x = 2; return 42; } + function offsetSet($o, $v): void { $this->x = 4; } + function offsetUnset($o): void {} + + static function fetch() { + $o = new self; + $o->x = 1; + $o[0]; + return $o->x; + } + + static function isset() { + $o = new self; + $o->x = 1; + return [isset($o[0]), $o->x]; + } + + static function assign() { + $o = new self; + $o->x = 1; + $o[0] = 1; + return $o->x; + } +} + +var_dump(AA::fetch()); +var_dump(AA::isset()); +var_dump(AA::assign()); +?> +--EXPECT-- +int(2) +array(2) { + [0]=> + bool(true) + [1]=> + int(3) +} +int(4)