Skip to content

Commit

Permalink
Introduce get_properties_for() handler
Browse files Browse the repository at this point in the history
This handler allows getting the object properties for a particular
purpose, such as array casting, serialization, etc.
  • Loading branch information
nikic committed Oct 10, 2018
1 parent 77c85b3 commit 7ec8087
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 106 deletions.
9 changes: 3 additions & 6 deletions Zend/zend.c
Expand Up @@ -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));
Expand All @@ -449,18 +448,16 @@ 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;
}

Z_PROTECT_RECURSION_P(expr);
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:
Expand Down
37 changes: 37 additions & 0 deletions Zend/zend_object_handlers.c
Expand Up @@ -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 */

Expand Down Expand Up @@ -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 */
};

/*
Expand Down
35 changes: 35 additions & 0 deletions Zend/zend_object_handlers.h
Expand Up @@ -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(). */

This comment has been minimized.

Copy link
@bukka

bukka Oct 21, 2018

Member

@nikic Does engine need to have a special case for json_encode? It seems identical to export so maybe something a bit more generic would be a bit cleaner.

This comment has been minimized.

Copy link
@nikic

nikic Oct 21, 2018

Author Member

Generally it is expected that many of these (serialize, var_export, json_encode) will have the same implementation -- it's mainly a question of which ones should be overloaded and which not. It's best not to overload any of these, but if you do, overload as little as possible. JSON is necessary for BC in some cases, but otherwise it is better to implement JsonSerializable instead.

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.
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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; \
Expand Down
36 changes: 12 additions & 24 deletions Zend/zend_operators.c
Expand Up @@ -610,32 +610,20 @@ ZEND_API void ZEND_FASTCALL convert_to_array(zval *op) /* {{{ */
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:
Expand Down
3 changes: 0 additions & 3 deletions Zend/zend_types.h
Expand Up @@ -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)

Expand Down
14 changes: 5 additions & 9 deletions Zend/zend_vm_def.h
Expand Up @@ -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));
Expand Down
56 changes: 20 additions & 36 deletions Zend/zend_vm_execute.h
Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down
9 changes: 7 additions & 2 deletions ext/json/json_encoder.c
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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();
Expand All @@ -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;
}
}
Expand All @@ -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;
}
/* }}} */
Expand Down

0 comments on commit 7ec8087

Please sign in to comment.