Skip to content

Commit 718dae4

Browse files
committed
Skip serialize object recording when array paths are rc1
Additionally, handle the common case of passing a CV-array with RC1 to serialize. This results in RC2 which we can safely treat as RC1 because no additional internal references are possible.
1 parent b2b7519 commit 718dae4

File tree

2 files changed

+74
-25
lines changed

2 files changed

+74
-25
lines changed

ext/standard/tests/serialize/serialization_objects_019.phpt

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,29 @@
22
Object serialization with references
33
--FILE--
44
<?php
5-
function gen() {
6-
$s = new stdClass;
7-
$r = new stdClass;
8-
$r->a = [$s];
9-
$r->b = $r->a;
10-
return $r;
5+
6+
function rcn() {
7+
$root = new stdClass;
8+
$end = new stdClass;
9+
$root->a = [$end];
10+
$root->b = $root->a;
11+
unset($end);
12+
var_dump(serialize($root));
1113
}
12-
var_dump(serialize(gen()));
13-
?>
14-
--EXPECTF--
15-
string(78) "O:8:"stdClass":2:{s:1:"a";a:1:{i:0;O:8:"stdClass":0:{}}s:1:"b";a:1:{i:0;r:3;}}"
1614

15+
function rcn_rc1() {
16+
$root = new stdClass;
17+
$end = new stdClass;
18+
$root->a = [[$end]];
19+
$root->b = $root->a;
20+
unset($end);
21+
var_dump(serialize($root));
22+
}
1723

24+
rcn();
25+
rcn_rc1();
26+
27+
?>
28+
--EXPECT--
29+
string(78) "O:8:"stdClass":2:{s:1:"a";a:1:{i:0;O:8:"stdClass":0:{}}s:1:"b";a:1:{i:0;r:3;}}"
30+
string(98) "O:8:"stdClass":2:{s:1:"a";a:1:{i:0;a:1:{i:0;O:8:"stdClass":0:{}}}s:1:"b";a:1:{i:0;a:1:{i:0;r:4;}}}"

ext/standard/var.c

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -652,9 +652,13 @@ PHP_FUNCTION(var_export)
652652
}
653653
/* }}} */
654654

655-
static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_data_t var_hash);
655+
static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_data_t var_hash, bool unique, bool subtract_rc);
656656

657-
static inline zend_long php_add_var_hash(php_serialize_data_t data, zval *var) /* {{{ */
657+
/**
658+
* @param bool unique Whether the element can only appear once in the serialized data. It can appear
659+
* multiple times if it is embedded in an array with RC > 1.
660+
*/
661+
static inline zend_long php_add_var_hash(php_serialize_data_t data, zval *var, bool unique) /* {{{ */
658662
{
659663
zval *zv;
660664
zend_ulong key;
@@ -666,6 +670,10 @@ static inline zend_long php_add_var_hash(php_serialize_data_t data, zval *var) /
666670
/* pass */
667671
} else if (Z_TYPE_P(var) != IS_OBJECT) {
668672
return 0;
673+
} else if (unique
674+
&& Z_REFCOUNT_P(var) == 1
675+
&& (Z_OBJ_P(var)->properties == NULL || GC_REFCOUNT(Z_OBJ_P(var)->properties) == 1)) {
676+
return 0;
669677
}
670678

671679
/* References to objects are treated as if the reference didn't exist */
@@ -921,7 +929,7 @@ static int php_var_serialize_get_sleep_props(
921929
}
922930
/* }}} */
923931

924-
static void php_var_serialize_nested_data(smart_str *buf, zval *struc, HashTable *ht, uint32_t count, bool incomplete_class, php_serialize_data_t var_hash) /* {{{ */
932+
static void php_var_serialize_nested_data(smart_str *buf, zval *struc, HashTable *ht, uint32_t count, bool incomplete_class, php_serialize_data_t var_hash, bool unique) /* {{{ */
925933
{
926934
smart_str_append_unsigned(buf, count);
927935
smart_str_appendl(buf, ":{", 2);
@@ -951,19 +959,19 @@ static void php_var_serialize_nested_data(smart_str *buf, zval *struc, HashTable
951959
if (Z_TYPE_P(data) == IS_ARRAY) {
952960
if (UNEXPECTED(Z_IS_RECURSIVE_P(data))
953961
|| UNEXPECTED(Z_TYPE_P(struc) == IS_ARRAY && Z_ARR_P(data) == Z_ARR_P(struc))) {
954-
php_add_var_hash(var_hash, struc);
962+
php_add_var_hash(var_hash, struc, unique);
955963
smart_str_appendl(buf, "N;", 2);
956964
} else {
957965
if (Z_REFCOUNTED_P(data)) {
958966
Z_PROTECT_RECURSION_P(data);
959967
}
960-
php_var_serialize_intern(buf, data, var_hash);
968+
php_var_serialize_intern(buf, data, var_hash, unique, false);
961969
if (Z_REFCOUNTED_P(data)) {
962970
Z_UNPROTECT_RECURSION_P(data);
963971
}
964972
}
965973
} else {
966-
php_var_serialize_intern(buf, data, var_hash);
974+
php_var_serialize_intern(buf, data, var_hash, unique, false);
967975
}
968976
} ZEND_HASH_FOREACH_END();
969977
}
@@ -978,13 +986,13 @@ static void php_var_serialize_class(smart_str *buf, zval *struc, HashTable *ht,
978986
if (php_var_serialize_get_sleep_props(&props, struc, ht) == SUCCESS) {
979987
php_var_serialize_class_name(buf, struc);
980988
php_var_serialize_nested_data(
981-
buf, struc, &props, zend_hash_num_elements(&props), /* incomplete_class */ 0, var_hash);
989+
buf, struc, &props, zend_hash_num_elements(&props), /* incomplete_class */ 0, var_hash, GC_REFCOUNT(&props) == 1);
982990
}
983991
zend_hash_destroy(&props);
984992
}
985993
/* }}} */
986994

987-
static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_data_t var_hash) /* {{{ */
995+
static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_data_t var_hash, bool unique, bool subtract_rc) /* {{{ */
988996
{
989997
zend_long var_already;
990998
HashTable *myht;
@@ -993,7 +1001,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
9931001
return;
9941002
}
9951003

996-
if (var_hash && (var_already = php_add_var_hash(var_hash, struc))) {
1004+
if (var_hash && (var_already = php_add_var_hash(var_hash, struc, unique))) {
9971005
if (var_already == -1) {
9981006
/* Reference to an object that failed to serialize, replace with null. */
9991007
smart_str_appendl(buf, "N;", 2);
@@ -1102,7 +1110,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
11021110
if (Z_ISREF_P(data) && Z_REFCOUNT_P(data) == 1) {
11031111
data = Z_REFVAL_P(data);
11041112
}
1105-
php_var_serialize_intern(buf, data, var_hash);
1113+
php_var_serialize_intern(buf, data, var_hash, Z_REFCOUNT_P(&retval) == 1, false);
11061114
} ZEND_HASH_FOREACH_END();
11071115
smart_str_appendc(buf, '}');
11081116

@@ -1224,7 +1232,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
12241232
prop = Z_REFVAL_P(prop);
12251233
}
12261234

1227-
php_var_serialize_intern(buf, prop, var_hash);
1235+
php_var_serialize_intern(buf, prop, var_hash, true, false);
12281236
}
12291237
smart_str_appendc(buf, '}');
12301238
} else {
@@ -1239,15 +1247,15 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
12391247
if (count > 0 && incomplete_class) {
12401248
--count;
12411249
}
1242-
php_var_serialize_nested_data(buf, struc, myht, count, incomplete_class, var_hash);
1250+
php_var_serialize_nested_data(buf, struc, myht, count, incomplete_class, var_hash, GC_REFCOUNT(myht) == 1);
12431251
zend_release_properties(myht);
12441252
return;
12451253
}
12461254
case IS_ARRAY:
12471255
smart_str_appendl(buf, "a:", 2);
12481256
myht = Z_ARRVAL_P(struc);
12491257
php_var_serialize_nested_data(
1250-
buf, struc, myht, zend_array_count(myht), /* incomplete_class */ 0, var_hash);
1258+
buf, struc, myht, zend_array_count(myht), /* incomplete_class */ 0, var_hash, unique && (GC_REFCOUNT(myht) - (subtract_rc ? 1 : 0)) == 1);
12511259
return;
12521260
case IS_REFERENCE:
12531261
struc = Z_REFVAL_P(struc);
@@ -1261,11 +1269,17 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
12611269

12621270
PHPAPI void php_var_serialize(smart_str *buf, zval *struc, php_serialize_data_t *data) /* {{{ */
12631271
{
1264-
php_var_serialize_intern(buf, struc, *data);
1272+
php_var_serialize_intern(buf, struc, *data, true, false);
12651273
smart_str_0(buf);
12661274
}
12671275
/* }}} */
12681276

1277+
static void php_var_serialize_cv(smart_str *buf, zval *struc, php_serialize_data_t *data)
1278+
{
1279+
php_var_serialize_intern(buf, struc, *data, true, true);
1280+
smart_str_0(buf);
1281+
}
1282+
12691283
PHPAPI php_serialize_data_t php_var_serialize_init(void) {
12701284
struct php_serialize_data *d;
12711285
/* fprintf(stderr, "SERIALIZE_INIT == lock: %u, level: %u\n", BG(serialize_lock), BG(serialize).level); */
@@ -1306,8 +1320,30 @@ PHP_FUNCTION(serialize)
13061320
Z_PARAM_ZVAL(struc)
13071321
ZEND_PARSE_PARAMETERS_END();
13081322

1323+
bool data_is_cv = false;
1324+
if (Z_TYPE_P(struc) == IS_ARRAY
1325+
&& !(GC_FLAGS(Z_ARRVAL_P(struc)) & IS_ARRAY_IMMUTABLE)
1326+
&& EG(current_execute_data)
1327+
&& EG(current_execute_data)->prev_execute_data) {
1328+
zend_execute_data *execute_data = EG(current_execute_data)->prev_execute_data;
1329+
if (execute_data->func && ZEND_USER_CODE(execute_data->func->type)) {
1330+
zend_op_array *func = &execute_data->func->op_array;
1331+
const zend_op *opline = execute_data->opline;
1332+
if (func->opcodes < opline) {
1333+
const zend_op *prev_opline = opline - 1;
1334+
if ((prev_opline->opcode == ZEND_SEND_VAR || prev_opline->opcode == ZEND_SEND_VAR_EX) && prev_opline->op1_type == IS_CV) {
1335+
data_is_cv = true;
1336+
}
1337+
}
1338+
}
1339+
}
1340+
13091341
PHP_VAR_SERIALIZE_INIT(var_hash);
1310-
php_var_serialize(&buf, struc, &var_hash);
1342+
if (data_is_cv) {
1343+
php_var_serialize_cv(&buf, struc, &var_hash);
1344+
} else {
1345+
php_var_serialize(&buf, struc, &var_hash);
1346+
}
13111347
PHP_VAR_SERIALIZE_DESTROY(var_hash);
13121348

13131349
if (EG(exception)) {

0 commit comments

Comments
 (0)