Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions ext/opcache/jit/zend_jit_ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -14554,8 +14554,21 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
prop_ref = ir_ADD_OFFSET(obj_ref, prop_info->offset);
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
if (opline->opcode == ZEND_FETCH_OBJ_W || !(res_info & MAY_BE_GUARD) || !JIT_G(current_frame)) {
/* perform IS_UNDEF check only after result type guard (during deoptimization) */
/* Where this check is skipped, IS_UNDEF is caught only by the result type
* guard that follows (during deoptimization). That guard stands in for it
* only where it admits IS_UNDEF, which zend_jit_guard_fetch_result_type()
* does for ZEND_FETCH_OBJ_IS with a NULL result. An unset() declared
* property served by __isset()/__get() traces to the type those return, so
* IS_UNDEF fails the guard, and the deoptimization resumes at the next
* opline with the empty slot copied into the result: isset() answers false
* and the magic handler never runs. */
bool undef_needs_vm = opline->opcode == ZEND_FETCH_OBJ_IS
&& concrete_type(res_info) != IS_NULL;

if (opline->opcode == ZEND_FETCH_OBJ_W
|| !(res_info & MAY_BE_GUARD)
|| !JIT_G(current_frame)
|| undef_needs_vm) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);

Expand Down
64 changes: 64 additions & 0 deletions ext/opcache/tests/jit/fetch_obj_is_unset_prop.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--TEST--
FETCH_OBJ_IS on a declared property removed by unset() must reach __isset()/__get()
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=tracing
opcache.jit_buffer_size=16M
opcache.jit_hot_func=2
--FILE--
<?php

class Store {
public array $marks = [];
}

class Holder {
public static Store $store;
public array $marks = [];

public function __construct() {
unset($this->marks);
}

public function &__get(string $name) {
return self::$store->$name;
}

public function __isset(string $name): bool {
return isset(self::$store->$name);
}

public function mark(string $key): void {
$this->marks[$key] = true;
}

public function has(string $key): bool {
return isset($this->marks[$key]);
}
}

Holder::$store = new Store();
$holder = new Holder();

for ($n = 0; $n < 10; $n++) {
$key = "k{$n}";
$holder->mark($key);
var_dump($holder->has($key));
}

?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
Loading