前言
无。
测试
就用之前的php7.4.33,测试用代码:
<?php
class B {
function __destruct() {
echo "Destruct";
}
}
unserialize('a:2:{i:0;O:1:"B":0:{}i:0;i:0;}');
throw new Exception('Error');
运行会发现,由于数组键的冲突,反序列化的过程中就触发了B类的构析函数。
源码
找到反序列化源码文件var_unserializer.c,找到处理类和数组反序列化的函数process_nested_data,该函数反序列化出键后会查看已有数据中是否存在该键:
if (Z_TYPE(key) == IS_LONG) {
idx = Z_LVAL(key);
if (UNEXPECTED((old_data = zend_hash_index_find(ht, idx)) != NULL)) {
//??? update hash
var_push_dtor(var_hash, old_data);
data = zend_hash_index_update(ht, idx, &d);
} else {
data = zend_hash_index_add_new(ht, idx, &d);
}
}
如果发生了键冲突,则会调用var_push_dtor函数:
if (Z_REFCOUNTED_P(rval)) {
zval *tmp_var = var_tmp_var(var_hashx);
if (!tmp_var) {
return;
}
ZVAL_COPY(tmp_var, rval);
}
Z_REFCOUNTED_P宏判断的是变量的type_flags标志位不为0,此时B对象的type_flags值为3:
(gdb) p rval.u1.v.type_flags
$3 = 3 '\003
其在定义中可能的值如下:
/* zval.u1.v.type_flags */
#define IS_TYPE_REFCOUNTED (1<<0)
#define IS_TYPE_COLLECTABLE (1<<1)
简单来说就是判断是否需要引用计数。然后会调用var_tmp_var函数生成并返回一个临时变量,并将B对象保存了进去,这个临时变量会挂在var_hashx这个php_unserialize_data_t下面:
return &var_hash->data[used_slots];
回到PHP的unserialize函数,即var.c的PHP_FUNCTION(unserialize),最后销毁多余变量:
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
一路找到var_destroy函数,里面会调用i_zval_ptr_dtor开始销毁变量:
if (Z_REFCOUNTED_P(zval_ptr)) {
zend_refcounted *ref = Z_COUNTED_P(zval_ptr);
if (!GC_DELREF(ref)) {
rc_dtor_func(ref);
} else {
gc_check_possible_root(ref);
}
}
再到rc_dtor_func函数:
ZEND_ASSERT(GC_TYPE(p) <= IS_CONSTANT_AST);
zend_rc_dtor_func[GC_TYPE(p)](p);
找到类型对应的销毁函数:
static const zend_rc_dtor_func_t zend_rc_dtor_func[] = {
/* IS_UNDEF */ (zend_rc_dtor_func_t)zend_empty_destroy,
/* IS_NULL */ (zend_rc_dtor_func_t)zend_empty_destroy,
/* IS_FALSE */ (zend_rc_dtor_func_t)zend_empty_destroy,
/* IS_TRUE */ (zend_rc_dtor_func_t)zend_empty_destroy,
/* IS_LONG */ (zend_rc_dtor_func_t)zend_empty_destroy,
/* IS_DOUBLE */ (zend_rc_dtor_func_t)zend_empty_destroy,
/* IS_STRING */ (zend_rc_dtor_func_t)zend_string_destroy,
/* IS_ARRAY */ (zend_rc_dtor_func_t)zend_array_destroy,
/* IS_OBJECT */ (zend_rc_dtor_func_t)zend_objects_store_del,
/* IS_RESOURCE */ (zend_rc_dtor_func_t)zend_list_free,
/* IS_REFERENCE */ (zend_rc_dtor_func_t)zend_reference_destroy,
/* IS_CONSTANT_AST */ (zend_rc_dtor_func_t)zend_ast_ref_destroy
};
这里是zend_objects_store_del,该函数会调用类对应的构析函数销毁对象:
if (object->handlers->dtor_obj != zend_objects_destroy_object
|| object->ce->destructor) {
GC_SET_REFCOUNT(object, 1);
object->handlers->dtor_obj(object);
GC_DELREF(object);
}
使用场景
根据参考文章所说,可以用于立即触发构析函数而不需要等到代码执行完毕后统一销毁。
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!