Introduce get_properties_for() handler

This handler allows getting the object properties for a particular
purpose, such as array casting, serialization, etc.
This commit is contained in:
Nikita Popov 2018-10-04 13:58:35 +02:00
parent 77c85b3119
commit 7ec8087f80
10 changed files with 136 additions and 106 deletions

View File

@ -438,7 +438,6 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
case IS_OBJECT:
{
HashTable *properties;
int is_temp;
zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(Z_OBJ_P(expr));
smart_str_appends(buf, ZSTR_VAL(class_name));
@ -449,7 +448,8 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
smart_str_appends(buf, " *RECURSION*");
return;
}
if ((properties = Z_OBJDEBUG_P(expr, is_temp)) == NULL) {
if ((properties = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_DEBUG)) == NULL) {
break;
}
@ -457,10 +457,7 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
print_hash(buf, properties, indent, 1);
Z_UNPROTECT_RECURSION_P(expr);
if (is_temp) {
zend_hash_destroy(properties);
FREE_HASHTABLE(properties);
}
zend_release_properties(properties);
break;
}
case IS_LONG:

View File

@ -1737,6 +1737,42 @@ ZEND_API int zend_std_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_fun
}
/* }}} */
ZEND_API HashTable *zend_std_get_properties_for(zval *obj, zend_prop_purpose purpose) {
HashTable *ht;
switch (purpose) {
case ZEND_PROP_PURPOSE_DEBUG:
if (Z_OBJ_HT_P(obj)->get_debug_info) {
int is_temp;
ht = Z_OBJ_HT_P(obj)->get_debug_info(obj, &is_temp);
if (ht && !is_temp && !(GC_FLAGS(ht) & GC_IMMUTABLE)) {
GC_ADDREF(ht);
}
return ht;
}
/* break missing intentionally */
case ZEND_PROP_PURPOSE_ARRAY_CAST:
case ZEND_PROP_PURPOSE_SERIALIZE:
case ZEND_PROP_PURPOSE_VAR_EXPORT:
case ZEND_PROP_PURPOSE_JSON:
ht = Z_OBJ_HT_P(obj)->get_properties(obj);
if (ht && !(GC_FLAGS(ht) & GC_IMMUTABLE)) {
GC_ADDREF(ht);
}
return ht;
default:
ZEND_ASSERT(0);
return NULL;
}
}
ZEND_API HashTable *zend_get_properties_for(zval *obj, zend_prop_purpose purpose) {
if (Z_OBJ_HT_P(obj)->get_properties_for) {
return Z_OBJ_HT_P(obj)->get_properties_for(obj, purpose);
}
return zend_std_get_properties_for(obj, purpose);
}
ZEND_API const zend_object_handlers std_object_handlers = {
0, /* offset */
@ -1768,6 +1804,7 @@ ZEND_API const zend_object_handlers std_object_handlers = {
zend_std_get_gc, /* get_gc */
NULL, /* do_operation */
NULL, /* compare */
NULL, /* get_properties_for */
};
/*

View File

@ -93,6 +93,26 @@ typedef HashTable *(*zend_object_get_properties_t)(zval *object);
typedef HashTable *(*zend_object_get_debug_info_t)(zval *object, int *is_temp);
typedef enum _zend_prop_purpose {
/* Used for debugging. Supersedes get_debug_info handler. */
ZEND_PROP_PURPOSE_DEBUG,
/* Used for (array) casts. */
ZEND_PROP_PURPOSE_ARRAY_CAST,
/* Used for serialization using the "O" scheme.
* Unserialization will use __wakeup(). */
ZEND_PROP_PURPOSE_SERIALIZE,
/* Used for var_export().
* The data will be passed to __set_state() when evaluated. */
ZEND_PROP_PURPOSE_VAR_EXPORT,
/* Used for json_encode(). */
ZEND_PROP_PURPOSE_JSON,
/* Dummy member to ensure that "default" is specified. */
_ZEND_PROP_PURPOSE_NON_EXHAUSTIVE_ENUM
} zend_prop_purpose;
/* The return value must be released using zend_release_properties(). */
typedef zend_array *(*zend_object_get_properties_for_t)(zval *object, zend_prop_purpose purpose);
/* Used to call methods */
/* args on stack! */
/* Andi - EX(fbc) (function being called) needs to be initialized already in the INIT fcall opcode so that the parameters can be parsed the right way. We need to add another callback for this.
@ -160,6 +180,7 @@ struct _zend_object_handlers {
zend_object_get_gc_t get_gc;
zend_object_do_operation_t do_operation;
zend_object_compare_zvals_t compare;
zend_object_get_properties_for_t get_properties_for; /* optional */
};
BEGIN_EXTERN_C()
@ -207,6 +228,20 @@ ZEND_API zend_function *zend_get_call_trampoline_func(zend_class_entry *ce, zend
ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member);
/* Default behavior for get_properties_for. For use as a fallback in custom
* get_properties_for implementations. */
ZEND_API HashTable *zend_std_get_properties_for(zval *obj, zend_prop_purpose purpose);
/* Will call get_properties_for handler or use default behavior. For use by
* consumers of the get_properties_for API. */
ZEND_API HashTable *zend_get_properties_for(zval *obj, zend_prop_purpose purpose);
#define zend_release_properties(ht) do { \
if ((ht) && !(GC_FLAGS(ht) & GC_IMMUTABLE) && !GC_DELREF(ht)) { \
zend_array_destroy(ht); \
} \
} while (0)
#define zend_free_trampoline(func) do { \
if ((func) == &EG(trampoline)) { \
EG(trampoline).common.function_name = NULL; \

View File

@ -610,32 +610,20 @@ try_again:
if (Z_OBJCE_P(op) == zend_ce_closure) {
convert_scalar_to_array(op);
} else {
if (Z_OBJ_HT_P(op)->get_properties) {
HashTable *obj_ht = Z_OBJ_HT_P(op)->get_properties(op);
if (obj_ht) {
/* fast copy */
obj_ht = zend_proptable_to_symtable(obj_ht,
(Z_OBJCE_P(op)->default_properties_count ||
Z_OBJ_P(op)->handlers != &std_object_handlers ||
GC_IS_RECURSIVE(obj_ht)));
zval_ptr_dtor(op);
ZVAL_ARR(op, obj_ht);
return;
}
HashTable *obj_ht = zend_get_properties_for(op, ZEND_PROP_PURPOSE_ARRAY_CAST);
if (obj_ht) {
HashTable *new_obj_ht = zend_proptable_to_symtable(obj_ht,
(Z_OBJCE_P(op)->default_properties_count ||
Z_OBJ_P(op)->handlers != &std_object_handlers ||
GC_IS_RECURSIVE(obj_ht)));
zval_ptr_dtor(op);
ZVAL_ARR(op, new_obj_ht);
zend_release_properties(obj_ht);
} else {
zval dst;
convert_object_to_type(op, &dst, IS_ARRAY, convert_to_array);
if (Z_TYPE(dst) == IS_ARRAY) {
zval_ptr_dtor(op);
ZVAL_COPY_VALUE(op, &dst);
return;
}
zval_ptr_dtor(op);
/*ZVAL_EMPTY_ARRAY(op);*/
array_init(op);
}
zval_ptr_dtor(op);
/*ZVAL_EMPTY_ARRAY(op);*/
array_init(op);
}
break;
case IS_NULL:

View File

@ -670,9 +670,6 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) {
#define Z_OBJPROP(zval) Z_OBJ_HT((zval))->get_properties(&(zval))
#define Z_OBJPROP_P(zval_p) Z_OBJPROP(*(zval_p))
#define Z_OBJDEBUG(zval,tmp) (Z_OBJ_HANDLER((zval),get_debug_info)?Z_OBJ_HANDLER((zval),get_debug_info)(&(zval),&tmp):(tmp=0,Z_OBJPROP(zval)))
#define Z_OBJDEBUG_P(zval_p,tmp) Z_OBJDEBUG(*(zval_p), tmp)
#define Z_RES(zval) (zval).value.res
#define Z_RES_P(zval_p) Z_RES(*zval_p)

View File

@ -5307,22 +5307,18 @@ ZEND_VM_COLD_CONST_HANDLER(21, ZEND_CAST, CONST|TMP|VAR|CV, ANY, TYPE)
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else if (Z_OBJ_HT_P(expr)->get_properties) {
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
} else {
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
if (obj_ht) {
/* fast copy */
obj_ht = zend_proptable_to_symtable(obj_ht,
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
(Z_OBJCE_P(expr)->default_properties_count ||
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
GC_IS_RECURSIVE(obj_ht)));
ZVAL_ARR(result, obj_ht);
GC_IS_RECURSIVE(obj_ht))));
zend_release_properties(obj_ht);
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else {
ZVAL_COPY_VALUE(result, expr);
Z_ADDREF_P(result);
convert_to_array(result);
}
} else {
ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def));

View File

@ -3121,22 +3121,18 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CONST_H
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else if (Z_OBJ_HT_P(expr)->get_properties) {
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
} else {
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
if (obj_ht) {
/* fast copy */
obj_ht = zend_proptable_to_symtable(obj_ht,
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
(Z_OBJCE_P(expr)->default_properties_count ||
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
GC_IS_RECURSIVE(obj_ht)));
ZVAL_ARR(result, obj_ht);
GC_IS_RECURSIVE(obj_ht))));
zend_release_properties(obj_ht);
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else {
ZVAL_COPY_VALUE(result, expr);
Z_ADDREF_P(result);
convert_to_array(result);
}
} else {
ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def));
@ -18092,22 +18088,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_TMP_HANDLER(ZEND_OPC
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else if (Z_OBJ_HT_P(expr)->get_properties) {
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
} else {
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
if (obj_ht) {
/* fast copy */
obj_ht = zend_proptable_to_symtable(obj_ht,
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
(Z_OBJCE_P(expr)->default_properties_count ||
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
GC_IS_RECURSIVE(obj_ht)));
ZVAL_ARR(result, obj_ht);
GC_IS_RECURSIVE(obj_ht))));
zend_release_properties(obj_ht);
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else {
ZVAL_COPY_VALUE(result, expr);
Z_ADDREF_P(result);
convert_to_array(result);
}
} else {
ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def));
@ -21100,22 +21092,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_VAR_HANDLER(ZEND_OPC
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else if (Z_OBJ_HT_P(expr)->get_properties) {
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
} else {
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
if (obj_ht) {
/* fast copy */
obj_ht = zend_proptable_to_symtable(obj_ht,
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
(Z_OBJCE_P(expr)->default_properties_count ||
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
GC_IS_RECURSIVE(obj_ht)));
ZVAL_ARR(result, obj_ht);
GC_IS_RECURSIVE(obj_ht))));
zend_release_properties(obj_ht);
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else {
ZVAL_COPY_VALUE(result, expr);
Z_ADDREF_P(result);
convert_to_array(result);
}
} else {
ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def));
@ -37467,22 +37455,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CV_HANDLER(ZEND_OPCO
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else if (Z_OBJ_HT_P(expr)->get_properties) {
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
} else {
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
if (obj_ht) {
/* fast copy */
obj_ht = zend_proptable_to_symtable(obj_ht,
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
(Z_OBJCE_P(expr)->default_properties_count ||
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
GC_IS_RECURSIVE(obj_ht)));
ZVAL_ARR(result, obj_ht);
GC_IS_RECURSIVE(obj_ht))));
zend_release_properties(obj_ht);
} else {
ZVAL_EMPTY_ARRAY(result);
}
} else {
ZVAL_COPY_VALUE(result, expr);
Z_ADDREF_P(result);
convert_to_array(result);
}
} else {
ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def));

View File

@ -130,19 +130,21 @@ static inline void php_json_encode_double(smart_str *buf, double d, int options)
static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
{
int i, r, need_comma = 0;
HashTable *myht;
HashTable *myht, *prop_ht;
if (Z_TYPE_P(val) == IS_ARRAY) {
myht = Z_ARRVAL_P(val);
prop_ht = NULL;
r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val);
} else {
myht = Z_OBJPROP_P(val);
prop_ht = myht = zend_get_properties_for(val, ZEND_PROP_PURPOSE_JSON);
r = PHP_JSON_OUTPUT_OBJECT;
}
if (myht && GC_IS_RECURSIVE(myht)) {
encoder->error_code = PHP_JSON_ERROR_RECURSION;
smart_str_appendl(buf, "null", 4);
zend_release_properties(prop_ht);
return FAILURE;
}
@ -218,6 +220,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso
if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
zend_release_properties(prop_ht);
return FAILURE;
}
} ZEND_HASH_FOREACH_END();
@ -228,6 +231,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso
if (encoder->depth > encoder->max_depth) {
encoder->error_code = PHP_JSON_ERROR_DEPTH;
if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
zend_release_properties(prop_ht);
return FAILURE;
}
}
@ -245,6 +249,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso
smart_str_appendc(buf, '}');
}
zend_release_properties(prop_ht);
return SUCCESS;
}
/* }}} */

View File

@ -82,7 +82,6 @@ PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */
{
HashTable *myht;
zend_string *class_name;
int is_temp;
int is_ref = 0;
zend_ulong num;
zend_string *key;
@ -145,7 +144,7 @@ again:
}
Z_PROTECT_RECURSION_P(struc);
myht = Z_OBJDEBUG_P(struc, is_temp);
myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG);
class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc));
php_printf("%sobject(%s)#%d (%d) {\n", COMMON, ZSTR_VAL(class_name), Z_OBJ_HANDLE_P(struc), myht ? zend_array_count(myht) : 0);
zend_string_release_ex(class_name, 0);
@ -158,10 +157,7 @@ again:
ZEND_HASH_FOREACH_KEY_VAL_IND(myht, num, key, val) {
php_object_property_dump(val, num, key, level);
} ZEND_HASH_FOREACH_END();
if (is_temp) {
zend_hash_destroy(myht);
efree(myht);
}
zend_release_properties(myht);
}
if (level > 1) {
php_printf("%*c", level-1, ' ');
@ -249,7 +245,6 @@ PHPAPI void php_debug_zval_dump(zval *struc, int level) /* {{{ */
{
HashTable *myht = NULL;
zend_string *class_name;
int is_temp = 0;
int is_ref = 0;
zend_ulong index;
zend_string *key;
@ -299,20 +294,17 @@ again:
if (level > 1 && !(GC_FLAGS(myht) & GC_IMMUTABLE)) {
GC_UNPROTECT_RECURSION(myht);
}
if (is_temp) {
zend_hash_destroy(myht);
efree(myht);
}
if (level > 1) {
php_printf("%*c", level - 1, ' ');
}
PUTS("}\n");
break;
case IS_OBJECT:
myht = Z_OBJDEBUG_P(struc, is_temp);
myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG);
if (myht) {
if (GC_IS_RECURSIVE(myht)) {
PUTS("*RECURSION*\n");
zend_release_properties(myht);
return;
}
GC_PROTECT_RECURSION(myht);
@ -325,10 +317,7 @@ again:
zval_object_property_dump(val, index, key, level);
} ZEND_HASH_FOREACH_END();
GC_UNPROTECT_RECURSION(myht);
if (is_temp) {
zend_hash_destroy(myht);
efree(myht);
}
zend_release_properties(myht);
}
if (level > 1) {
php_printf("%*c", level - 1, ' ');
@ -510,11 +499,12 @@ again:
break;
case IS_OBJECT:
myht = Z_OBJPROP_P(struc);
myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_VAR_EXPORT);
if (myht) {
if (GC_IS_RECURSIVE(myht)) {
smart_str_appendl(buf, "NULL", 4);
zend_error(E_WARNING, "var_export does not handle circular references");
zend_release_properties(myht);
return;
} else {
GC_PROTECT_RECURSION(myht);
@ -538,6 +528,7 @@ again:
php_object_element_export(val, index, key, level, buf);
} ZEND_HASH_FOREACH_END();
GC_UNPROTECT_RECURSION(myht);
zend_release_properties(myht);
}
if (level > 1) {
buffer_append_spaces(buf, level - 1);
@ -743,7 +734,7 @@ static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_pt
smart_str_appendl(buf, ":{", 2);
ZVAL_NULL(&nval);
propers = Z_OBJPROP_P(struc);
propers = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_SERIALIZE);
ZEND_HASH_FOREACH_STR_KEY(&names, name) {
zend_string *prot_name, *priv_name;
@ -809,6 +800,7 @@ undef_prop:
smart_str_appendc(buf, '}');
zend_hash_destroy(&names);
zend_release_properties(propers);
}
/* }}} */
@ -925,7 +917,7 @@ again:
i = zend_array_count(myht);
} else {
incomplete_class = php_var_serialize_class_name(buf, struc);
myht = Z_OBJPROP_P(struc);
myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_SERIALIZE);
/* count after serializing name, since php_var_serialize_class_name
* changes the count if the variable is incomplete class */
i = zend_array_count(myht);
@ -978,6 +970,9 @@ again:
} ZEND_HASH_FOREACH_END();
}
smart_str_appendc(buf, '}');
if (Z_TYPE_P(struc) == IS_OBJECT) {
zend_release_properties(myht);
}
return;
}
case IS_REFERENCE:

View File

@ -660,8 +660,6 @@ PHPDBG_API void phpdbg_xml_var_dump(zval *zv) {
int (*element_dump_func)(zval *zv, zend_string *key, zend_ulong num);
zend_bool is_ref = 0;
int is_temp;
phpdbg_try_access {
is_ref = Z_ISREF_P(zv) && GC_REFCOUNT(Z_COUNTED_P(zv)) > 1;
ZVAL_DEREF(zv);
@ -696,10 +694,9 @@ PHPDBG_API void phpdbg_xml_var_dump(zval *zv) {
}
phpdbg_xml("<array refstatus=\"%s\" num=\"%d\">", COMMON, zend_hash_num_elements(myht));
element_dump_func = phpdbg_xml_array_element_dump;
is_temp = 0;
goto head_done;
case IS_OBJECT:
myht = Z_OBJDEBUG_P(zv, is_temp);
myht = zend_get_properties_for(zv, ZEND_PROP_PURPOSE_DEBUG);
if (myht && GC_IS_RECURSIVE(myht)) {
phpdbg_xml("<recursion />");
break;
@ -717,9 +714,8 @@ head_done:
} ZEND_HASH_FOREACH_END();
zend_hash_apply_with_arguments(myht, (apply_func_args_t) element_dump_func, 0);
GC_UNPROTECT_RECURSION(myht);
if (is_temp) {
zend_hash_destroy(myht);
efree(myht);
if (Z_TYPE_P(zv) == IS_OBJECT) {
zend_release_properties(myht);
}
}
if (Z_TYPE_P(zv) == IS_ARRAY) {