Skip to content

Commit c6c69b3

Browse files
committed
Introduce get_properties_for() handler
This handler allows getting the object properties for a particular purpose, such as array casting, serialization, etc.
1 parent c2c2021 commit c6c69b3

10 files changed

+140
-106
lines changed

Zend/zend.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,6 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
438438
case IS_OBJECT:
439439
{
440440
HashTable *properties;
441-
int is_temp;
442441

443442
zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(Z_OBJ_P(expr));
444443
smart_str_appends(buf, ZSTR_VAL(class_name));
@@ -449,18 +448,16 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
449448
smart_str_appends(buf, " *RECURSION*");
450449
return;
451450
}
452-
if ((properties = Z_OBJDEBUG_P(expr, is_temp)) == NULL) {
451+
452+
if ((properties = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_DEBUG)) == NULL) {
453453
break;
454454
}
455455

456456
Z_PROTECT_RECURSION_P(expr);
457457
print_hash(buf, properties, indent, 1);
458458
Z_UNPROTECT_RECURSION_P(expr);
459459

460-
if (is_temp) {
461-
zend_hash_destroy(properties);
462-
FREE_HASHTABLE(properties);
463-
}
460+
zend_release_properties(properties);
464461
break;
465462
}
466463
case IS_LONG:

Zend/zend_object_handlers.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,6 +1737,45 @@ ZEND_API int zend_std_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_fun
17371737
}
17381738
/* }}} */
17391739

1740+
ZEND_API HashTable *zend_std_get_properties_for(zval *obj, zend_prop_purpose purpose) {
1741+
HashTable *ht;
1742+
switch (purpose) {
1743+
case ZEND_PROP_PURPOSE_DEBUG:
1744+
if (Z_OBJ_HT_P(obj)->get_debug_info) {
1745+
int is_temp;
1746+
ht = Z_OBJ_HT_P(obj)->get_debug_info(obj, &is_temp);
1747+
if (ht && !is_temp && !(GC_FLAGS(ht) & GC_IMMUTABLE)) {
1748+
GC_ADDREF(ht);
1749+
}
1750+
return ht;
1751+
}
1752+
/* break missing intentionally */
1753+
case ZEND_PROP_PURPOSE_ARRAY_CAST:
1754+
case ZEND_PROP_PURPOSE_SERIALIZE:
1755+
case ZEND_PROP_PURPOSE_VAR_EXPORT:
1756+
case ZEND_PROP_PURPOSE_JSON:
1757+
ht = Z_OBJ_HT_P(obj)->get_properties(obj);
1758+
if (ht && !(GC_FLAGS(ht) & GC_IMMUTABLE)) {
1759+
GC_ADDREF(ht);
1760+
}
1761+
return ht;
1762+
default:
1763+
return NULL;
1764+
}
1765+
}
1766+
1767+
ZEND_API HashTable *zend_get_properties_for(zval *obj, zend_prop_purpose purpose) {
1768+
HashTable *ht;
1769+
if (Z_OBJ_HT_P(obj)->get_properties_for) {
1770+
ht = Z_OBJ_HT_P(obj)->get_properties_for(obj, purpose);
1771+
if (ht) {
1772+
return ht;
1773+
}
1774+
}
1775+
1776+
return zend_std_get_properties_for(obj, purpose);
1777+
}
1778+
17401779
ZEND_API const zend_object_handlers std_object_handlers = {
17411780
0, /* offset */
17421781

@@ -1768,6 +1807,7 @@ ZEND_API const zend_object_handlers std_object_handlers = {
17681807
zend_std_get_gc, /* get_gc */
17691808
NULL, /* do_operation */
17701809
NULL, /* compare */
1810+
NULL, /* get_properties_for */
17711811
};
17721812

17731813
/*

Zend/zend_object_handlers.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,27 @@ typedef HashTable *(*zend_object_get_properties_t)(zval *object);
9393

9494
typedef HashTable *(*zend_object_get_debug_info_t)(zval *object, int *is_temp);
9595

96+
typedef enum _zend_prop_purpose {
97+
/* Used for debugging. Supersedes get_debug_info handler. */
98+
ZEND_PROP_PURPOSE_DEBUG,
99+
/* Used for (array) casts. */
100+
ZEND_PROP_PURPOSE_ARRAY_CAST,
101+
/* Used for serialization using the "O" scheme.
102+
* Unserialization will use __wakeup(). */
103+
ZEND_PROP_PURPOSE_SERIALIZE,
104+
/* Used for var_export().
105+
* The data will be passed to __set_state() when evaluated. */
106+
ZEND_PROP_PURPOSE_VAR_EXPORT,
107+
/* Used for json_encode(). */
108+
ZEND_PROP_PURPOSE_JSON,
109+
/* Dummy member to ensure that "default" is specified. */
110+
_ZEND_PROP_PURPOSE_NON_EXHAUSTIVE_ENUM
111+
} zend_prop_purpose;
112+
113+
/* The return value may be NULL. A non-NULL return value must be released using
114+
* zend_release_properties(). Releasing NULL is also safe. */
115+
typedef zend_array *(*zend_object_get_properties_for_t)(zval *object, zend_prop_purpose purpose);
116+
96117
/* Used to call methods */
97118
/* args on stack! */
98119
/* 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 +181,7 @@ struct _zend_object_handlers {
160181
zend_object_get_gc_t get_gc;
161182
zend_object_do_operation_t do_operation;
162183
zend_object_compare_zvals_t compare;
184+
zend_object_get_properties_for_t get_properties_for; /* optional */
163185
};
164186

165187
BEGIN_EXTERN_C()
@@ -207,6 +229,20 @@ ZEND_API zend_function *zend_get_call_trampoline_func(zend_class_entry *ce, zend
207229

208230
ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member);
209231

232+
/* Default behavior for get_properties_for. For use as a fallback in custom
233+
* get_properties_for implementations. */
234+
ZEND_API HashTable *zend_std_get_properties_for(zval *obj, zend_prop_purpose purpose);
235+
236+
/* Will call get_properties_for handler or use default behavior. For use by
237+
* consumers of the get_properties_for API. */
238+
ZEND_API HashTable *zend_get_properties_for(zval *obj, zend_prop_purpose purpose);
239+
240+
#define zend_release_properties(ht) do { \
241+
if ((ht) && !(GC_FLAGS(ht) & GC_IMMUTABLE) && !GC_DELREF(ht)) { \
242+
zend_array_destroy(ht); \
243+
} \
244+
} while (0)
245+
210246
#define zend_free_trampoline(func) do { \
211247
if ((func) == &EG(trampoline)) { \
212248
EG(trampoline).common.function_name = NULL; \

Zend/zend_operators.c

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -610,32 +610,20 @@ ZEND_API void ZEND_FASTCALL convert_to_array(zval *op) /* {{{ */
610610
if (Z_OBJCE_P(op) == zend_ce_closure) {
611611
convert_scalar_to_array(op);
612612
} else {
613-
if (Z_OBJ_HT_P(op)->get_properties) {
614-
HashTable *obj_ht = Z_OBJ_HT_P(op)->get_properties(op);
615-
if (obj_ht) {
616-
/* fast copy */
617-
obj_ht = zend_proptable_to_symtable(obj_ht,
618-
(Z_OBJCE_P(op)->default_properties_count ||
619-
Z_OBJ_P(op)->handlers != &std_object_handlers ||
620-
GC_IS_RECURSIVE(obj_ht)));
621-
zval_ptr_dtor(op);
622-
ZVAL_ARR(op, obj_ht);
623-
return;
624-
}
613+
HashTable *obj_ht = zend_get_properties_for(op, ZEND_PROP_PURPOSE_ARRAY_CAST);
614+
if (obj_ht) {
615+
HashTable *new_obj_ht = zend_proptable_to_symtable(obj_ht,
616+
(Z_OBJCE_P(op)->default_properties_count ||
617+
Z_OBJ_P(op)->handlers != &std_object_handlers ||
618+
GC_IS_RECURSIVE(obj_ht)));
619+
zval_ptr_dtor(op);
620+
ZVAL_ARR(op, new_obj_ht);
621+
zend_release_properties(obj_ht);
625622
} else {
626-
zval dst;
627-
convert_object_to_type(op, &dst, IS_ARRAY, convert_to_array);
628-
629-
if (Z_TYPE(dst) == IS_ARRAY) {
630-
zval_ptr_dtor(op);
631-
ZVAL_COPY_VALUE(op, &dst);
632-
return;
633-
}
623+
zval_ptr_dtor(op);
624+
/*ZVAL_EMPTY_ARRAY(op);*/
625+
array_init(op);
634626
}
635-
636-
zval_ptr_dtor(op);
637-
/*ZVAL_EMPTY_ARRAY(op);*/
638-
array_init(op);
639627
}
640628
break;
641629
case IS_NULL:

Zend/zend_types.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -670,9 +670,6 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) {
670670
#define Z_OBJPROP(zval) Z_OBJ_HT((zval))->get_properties(&(zval))
671671
#define Z_OBJPROP_P(zval_p) Z_OBJPROP(*(zval_p))
672672

673-
#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)))
674-
#define Z_OBJDEBUG_P(zval_p,tmp) Z_OBJDEBUG(*(zval_p), tmp)
675-
676673
#define Z_RES(zval) (zval).value.res
677674
#define Z_RES_P(zval_p) Z_RES(*zval_p)
678675

Zend/zend_vm_def.h

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5307,22 +5307,18 @@ ZEND_VM_COLD_CONST_HANDLER(21, ZEND_CAST, CONST|TMP|VAR|CV, ANY, TYPE)
53075307
} else {
53085308
ZVAL_EMPTY_ARRAY(result);
53095309
}
5310-
} else if (Z_OBJ_HT_P(expr)->get_properties) {
5311-
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
5310+
} else {
5311+
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
53125312
if (obj_ht) {
53135313
/* fast copy */
5314-
obj_ht = zend_proptable_to_symtable(obj_ht,
5314+
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
53155315
(Z_OBJCE_P(expr)->default_properties_count ||
53165316
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
5317-
GC_IS_RECURSIVE(obj_ht)));
5318-
ZVAL_ARR(result, obj_ht);
5317+
GC_IS_RECURSIVE(obj_ht))));
5318+
zend_release_properties(obj_ht);
53195319
} else {
53205320
ZVAL_EMPTY_ARRAY(result);
53215321
}
5322-
} else {
5323-
ZVAL_COPY_VALUE(result, expr);
5324-
Z_ADDREF_P(result);
5325-
convert_to_array(result);
53265322
}
53275323
} else {
53285324
ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def));

Zend/zend_vm_execute.h

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3121,22 +3121,18 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CONST_H
31213121
} else {
31223122
ZVAL_EMPTY_ARRAY(result);
31233123
}
3124-
} else if (Z_OBJ_HT_P(expr)->get_properties) {
3125-
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
3124+
} else {
3125+
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
31263126
if (obj_ht) {
31273127
/* fast copy */
3128-
obj_ht = zend_proptable_to_symtable(obj_ht,
3128+
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
31293129
(Z_OBJCE_P(expr)->default_properties_count ||
31303130
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
3131-
GC_IS_RECURSIVE(obj_ht)));
3132-
ZVAL_ARR(result, obj_ht);
3131+
GC_IS_RECURSIVE(obj_ht))));
3132+
zend_release_properties(obj_ht);
31333133
} else {
31343134
ZVAL_EMPTY_ARRAY(result);
31353135
}
3136-
} else {
3137-
ZVAL_COPY_VALUE(result, expr);
3138-
Z_ADDREF_P(result);
3139-
convert_to_array(result);
31403136
}
31413137
} else {
31423138
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
1809218088
} else {
1809318089
ZVAL_EMPTY_ARRAY(result);
1809418090
}
18095-
} else if (Z_OBJ_HT_P(expr)->get_properties) {
18096-
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
18091+
} else {
18092+
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
1809718093
if (obj_ht) {
1809818094
/* fast copy */
18099-
obj_ht = zend_proptable_to_symtable(obj_ht,
18095+
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
1810018096
(Z_OBJCE_P(expr)->default_properties_count ||
1810118097
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
18102-
GC_IS_RECURSIVE(obj_ht)));
18103-
ZVAL_ARR(result, obj_ht);
18098+
GC_IS_RECURSIVE(obj_ht))));
18099+
zend_release_properties(obj_ht);
1810418100
} else {
1810518101
ZVAL_EMPTY_ARRAY(result);
1810618102
}
18107-
} else {
18108-
ZVAL_COPY_VALUE(result, expr);
18109-
Z_ADDREF_P(result);
18110-
convert_to_array(result);
1811118103
}
1811218104
} else {
1811318105
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
2110021092
} else {
2110121093
ZVAL_EMPTY_ARRAY(result);
2110221094
}
21103-
} else if (Z_OBJ_HT_P(expr)->get_properties) {
21104-
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
21095+
} else {
21096+
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
2110521097
if (obj_ht) {
2110621098
/* fast copy */
21107-
obj_ht = zend_proptable_to_symtable(obj_ht,
21099+
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
2110821100
(Z_OBJCE_P(expr)->default_properties_count ||
2110921101
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
21110-
GC_IS_RECURSIVE(obj_ht)));
21111-
ZVAL_ARR(result, obj_ht);
21102+
GC_IS_RECURSIVE(obj_ht))));
21103+
zend_release_properties(obj_ht);
2111221104
} else {
2111321105
ZVAL_EMPTY_ARRAY(result);
2111421106
}
21115-
} else {
21116-
ZVAL_COPY_VALUE(result, expr);
21117-
Z_ADDREF_P(result);
21118-
convert_to_array(result);
2111921107
}
2112021108
} else {
2112121109
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
3746737455
} else {
3746837456
ZVAL_EMPTY_ARRAY(result);
3746937457
}
37470-
} else if (Z_OBJ_HT_P(expr)->get_properties) {
37471-
HashTable *obj_ht = Z_OBJ_HT_P(expr)->get_properties(expr);
37458+
} else {
37459+
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
3747237460
if (obj_ht) {
3747337461
/* fast copy */
37474-
obj_ht = zend_proptable_to_symtable(obj_ht,
37462+
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
3747537463
(Z_OBJCE_P(expr)->default_properties_count ||
3747637464
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
37477-
GC_IS_RECURSIVE(obj_ht)));
37478-
ZVAL_ARR(result, obj_ht);
37465+
GC_IS_RECURSIVE(obj_ht))));
37466+
zend_release_properties(obj_ht);
3747937467
} else {
3748037468
ZVAL_EMPTY_ARRAY(result);
3748137469
}
37482-
} else {
37483-
ZVAL_COPY_VALUE(result, expr);
37484-
Z_ADDREF_P(result);
37485-
convert_to_array(result);
3748637470
}
3748737471
} else {
3748837472
ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def));

ext/json/json_encoder.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,21 @@ static inline void php_json_encode_double(smart_str *buf, double d, int options)
130130
static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
131131
{
132132
int i, r, need_comma = 0;
133-
HashTable *myht;
133+
HashTable *myht, *prop_ht;
134134

135135
if (Z_TYPE_P(val) == IS_ARRAY) {
136136
myht = Z_ARRVAL_P(val);
137+
prop_ht = NULL;
137138
r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val);
138139
} else {
139-
myht = Z_OBJPROP_P(val);
140+
prop_ht = myht = zend_get_properties_for(val, ZEND_PROP_PURPOSE_JSON);
140141
r = PHP_JSON_OUTPUT_OBJECT;
141142
}
142143

143144
if (myht && GC_IS_RECURSIVE(myht)) {
144145
encoder->error_code = PHP_JSON_ERROR_RECURSION;
145146
smart_str_appendl(buf, "null", 4);
147+
zend_release_properties(prop_ht);
146148
return FAILURE;
147149
}
148150

@@ -218,6 +220,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso
218220
if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
219221
!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
220222
PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
223+
zend_release_properties(prop_ht);
221224
return FAILURE;
222225
}
223226
} ZEND_HASH_FOREACH_END();
@@ -228,6 +231,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso
228231
if (encoder->depth > encoder->max_depth) {
229232
encoder->error_code = PHP_JSON_ERROR_DEPTH;
230233
if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
234+
zend_release_properties(prop_ht);
231235
return FAILURE;
232236
}
233237
}
@@ -245,6 +249,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso
245249
smart_str_appendc(buf, '}');
246250
}
247251

252+
zend_release_properties(prop_ht);
248253
return SUCCESS;
249254
}
250255
/* }}} */

0 commit comments

Comments
 (0)