前言
无。
测试
就用之前的php7.4.33,测试用代码:
1 2 3 4 5 6 7 8
| <?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,该函数反序列化出键后会查看已有数据中是否存在该键:
1 2 3 4 5 6 7 8 9 10
| if (Z_TYPE(key) == IS_LONG) { idx = Z_LVAL(key); if (UNEXPECTED((old_data = zend_hash_index_find(ht, idx)) != NULL)) { 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函数:
1 2 3 4 5 6 7
| 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:
1 2
| (gdb) p rval.u1.v.type_flags $3 = 3 '\003
|
其在定义中可能的值如下:
1 2 3
| #define IS_TYPE_REFCOUNTED (1<<0) #define IS_TYPE_COLLECTABLE (1<<1)
|
简单来说就是判断是否需要引用计数。然后会调用var_tmp_var函数生成并返回一个临时变量,并将B对象保存了进去,这个临时变量会挂在var_hashx这个php_unserialize_data_t下面:
1
| return &var_hash->data[used_slots];
|
回到PHP的unserialize函数,即var.c的PHP_FUNCTION(unserialize),最后销毁多余变量:
1
| PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
|
一路找到var_destroy函数,里面会调用i_zval_ptr_dtor开始销毁变量:
1 2 3 4 5 6 7 8
| 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函数:
1 2
| ZEND_ASSERT(GC_TYPE(p) <= IS_CONSTANT_AST); zend_rc_dtor_func[GC_TYPE(p)](p);
|
找到类型对应的销毁函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| static const zend_rc_dtor_func_t zend_rc_dtor_func[] = { (zend_rc_dtor_func_t)zend_empty_destroy, (zend_rc_dtor_func_t)zend_empty_destroy, (zend_rc_dtor_func_t)zend_empty_destroy, (zend_rc_dtor_func_t)zend_empty_destroy, (zend_rc_dtor_func_t)zend_empty_destroy, (zend_rc_dtor_func_t)zend_empty_destroy, (zend_rc_dtor_func_t)zend_string_destroy, (zend_rc_dtor_func_t)zend_array_destroy, (zend_rc_dtor_func_t)zend_objects_store_del, (zend_rc_dtor_func_t)zend_list_free, (zend_rc_dtor_func_t)zend_reference_destroy, (zend_rc_dtor_func_t)zend_ast_ref_destroy };
|
这里是zend_objects_store_del,该函数会调用类对应的构析函数销毁对象:
1 2 3 4 5 6
| 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); }
|
使用场景
根据参考文章所说,可以用于立即触发构析函数而不需要等到代码执行完毕后统一销毁。
参考
浅析PHP GC垃圾回收机制及常见利用方式