Commit 4c3d44638eb for php

commit 4c3d44638eb7eaaea1526452e5d8e7176b243ead
Author: RV7PR <17615589+RV7PR@users.noreply.github.com>
Date:   Tue Oct 13 07:48:25 2026 +0200

    Fix GH-24270: JIT loses called scope of self::/parent:: in closures

    zend_jit_init_static_method_call() computes the called scope for a
    self:: or parent:: call from a non-static closure as a PHI of
    Z_OBJCE(EX(This)) and Z_CE(EX(This)), but discarded the PHI instead of
    assigning it to scope_ref. The callee then ran with the resolved class
    as called scope, so static::class and new static() returned Base
    instead of the forwarded Child (or failed with "Cannot instantiate
    abstract class"). Affects function and tracing JIT.

    Introduced by 972d6b6664e (Fix JIT for INIT_STATIC_METHOD_CALL in a
    closure).

    Closes GH-24287.

diff --git a/NEWS b/NEWS
index a8b32cd0d65..95fff33d289 100644
--- a/NEWS
+++ b/NEWS
@@ -41,6 +41,8 @@ PHP                                                                        NEWS
   . Fix too wide type inference for ASSIGN_DIM_OP. (ndossche)
   . Fix OSS-Fuzz #568005340 (FETCH_DIM_FUNC_ARG partial conversion). (ndossche)
   . Fix exception checks due to NAN warnings and object empty()'s. (ndossche)
+  . Fixed bug GH-24270 (JIT loses the called scope of self:: and parent:: calls
+    in closures). (RV7PR)

 - PDO:
   . Fixed a duplicate warning when ATTR_STATEMENT_CLASS is rejected on a
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 471cf55c654..08704742f8b 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -9344,7 +9344,7 @@ static int zend_jit_init_static_method_call(zend_jit_ctx         *jit,
 					ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(This.value.ref)), offsetof(zend_object, ce))));
 				ir_IF_FALSE(if_object);
 				ir_END_PHI_list(values, ir_LOAD_A(jit_EX(This.value.ref)));
-				ir_PHI_list(values);
+				scope_ref = ir_PHI_list(values);
 			} else {
 				scope_ref = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(This.value.ref)), offsetof(zend_object, ce)));
 			}
diff --git a/ext/opcache/tests/jit/gh24270.inc b/ext/opcache/tests/jit/gh24270.inc
new file mode 100644
index 00000000000..badcee80a4b
--- /dev/null
+++ b/ext/opcache/tests/jit/gh24270.inc
@@ -0,0 +1,74 @@
+<?php
+class Base {
+	public static function staticSelf(): Closure {
+		return fn() => self::make();
+	}
+	public function boundSelf(): Closure {
+		return fn() => self::make();
+	}
+	private static function make() {
+		return new static();
+	}
+}
+class Mid extends Base {
+	protected static function name() {
+		return static::class;
+	}
+}
+class Child extends Mid {
+	public function boundParent(): Closure {
+		return fn() => parent::name();
+	}
+}
+class GrandChild extends Child {}
+
+class ParentA {
+	public static function name() {
+		return static::class;
+	}
+}
+class ParentB {
+	public static function name() {
+		return static::class;
+	}
+}
+trait T {
+	public static function traitSelf(): Closure {
+		return fn() => self::make();
+	}
+	public function traitParent(): Closure {
+		return fn() => parent::name();
+	}
+	public static function make() {
+		return new static();
+	}
+}
+class A extends ParentA {
+	use T;
+}
+class ChildA extends A {}
+class B extends ParentB {
+	use T;
+}
+class ChildB extends B {}
+
+$closures = [
+	'self from static method' => Child::staticSelf(),
+	'bound self' => (new Child)->boundSelf(),
+	'bound parent' => (new GrandChild)->boundParent(),
+	'trait self A' => ChildA::traitSelf(),
+	'trait self B' => ChildB::traitSelf(),
+	'trait parent A' => (new ChildA)->traitParent(),
+	'trait parent B' => (new ChildB)->traitParent(),
+];
+
+$results = [];
+for ($i = 0; $i < 100; $i++) {
+	foreach ($closures as $name => $closure) {
+		$result = $closure();
+		$results[$name][is_object($result) ? $result::class : $result] = true;
+	}
+}
+foreach ($results as $name => $classes) {
+	echo $name, ': ', implode(', ', array_keys($classes)), "\n";
+}
diff --git a/ext/opcache/tests/jit/gh24270_001.phpt b/ext/opcache/tests/jit/gh24270_001.phpt
new file mode 100644
index 00000000000..dcc1b8700d5
--- /dev/null
+++ b/ext/opcache/tests/jit/gh24270_001.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-24270 001: Function JIT loses the called scope of self:: and parent:: calls in closures
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit=function
+opcache.jit_buffer_size=64M
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+require __DIR__ . '/gh24270.inc';
+?>
+--EXPECT--
+self from static method: Child
+bound self: Child
+bound parent: GrandChild
+trait self A: ChildA
+trait self B: ChildB
+trait parent A: ChildA
+trait parent B: ChildB
diff --git a/ext/opcache/tests/jit/gh24270_002.phpt b/ext/opcache/tests/jit/gh24270_002.phpt
new file mode 100644
index 00000000000..8425d412270
--- /dev/null
+++ b/ext/opcache/tests/jit/gh24270_002.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GH-24270 002: Tracing JIT loses the called scope of self:: and parent:: calls in closures
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit=tracing
+opcache.jit_buffer_size=64M
+opcache.jit_hot_func=1
+opcache.jit_hot_loop=1
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+require __DIR__ . '/gh24270.inc';
+?>
+--EXPECT--
+self from static method: Child
+bound self: Child
+bound parent: GrandChild
+trait self A: ChildA
+trait self B: ChildB
+trait parent A: ChildA
+trait parent B: ChildB