Commit a480965c90b for php.net
commit a480965c90b4f4948f211c8b139788371e031838
Author: Weilin Du <weilindu@php.net>
Date: Wed Jun 17 04:48:17 2026 +0800
ext/opcache: unwrap reference wrappers in typed by-value returns (#21973)
diff --git a/NEWS b/NEWS
index 05bdd1b35ff..afb45fca45c 100644
--- a/NEWS
+++ b/NEWS
@@ -93,6 +93,8 @@ PHP NEWS
. Fix persistent free of non-persistent connect_attr key (David Carlier).
- Opcache:
+ . Fixed bug GH-21972 (Corrupted variable type when a typed by-value return
+ contains a reference wrapper). (Weilin Du)
. Fixed tracing JIT crash when a VM interrupt is handled during an observed
user function call. (Levi Morrison)
. Fixed bug GH-22004 (Assertion failure at ext/opcache/jit/zend_jit_trace.c).
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 34906e1bfcc..c46b1710154 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -4387,7 +4387,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
ZVAL_DEREF(retval_ptr);
}
- if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) {
+ if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ref)))) {
ZEND_VM_NEXT_OPCODE();
}
@@ -4417,6 +4417,9 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
}
retval_ptr = retval_ref;
}
+ if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) {
+ ZEND_VM_NEXT_OPCODE();
+ }
}
SAVE_OPLINE();
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index d4b2aff1907..f4f35ce2a8a 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
diff --git a/ext/opcache/tests/opt/gh21972.phpt b/ext/opcache/tests/opt/gh21972.phpt
new file mode 100644
index 00000000000..f34f9b45ccf
--- /dev/null
+++ b/ext/opcache/tests/opt/gh21972.phpt
@@ -0,0 +1,39 @@
+--TEST--
+GH-21972: Typed by-value return must not leak reference wrapper
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+declare(strict_types=1);
+
+enum ValueType {
+ case BOOL;
+ case MIXED;
+}
+
+function applyDefinition(
+ bool &$lazy = false,
+ ValueType &$type = ValueType::MIXED,
+ int &$flags = 0,
+ ?string &$default = null,
+): void {
+}
+
+function getTypedValue(string $default, bool $lazy, ValueType $type): string {
+ applyDefinition($lazy, $type, default: $default);
+ return $default;
+}
+
+$value = getTypedValue('false', false, ValueType::BOOL);
+var_dump(gettype($value));
+var_dump(strtolower($value));
+var_dump(strtolower(getTypedValue('FALSE', false, ValueType::BOOL)));
+?>
+--EXPECT--
+string(6) "string"
+string(5) "false"
+string(5) "false"