Skip to content

Commit b6dd6fd

Browse files
committed
Fix number of elements after packed hash filling
After a hash filling routine the number of elements are set to the fill index. However, if the fill index is larger than the number of elements, the number of elements are no longer correct. This is observable at least via count() and var_dump(). E.g. the attached test case would incorrectly show int(17) instead of int(11). Solve this by only increasing the number of elements by the actual number that got added. Instead of adding a variable that increments per iteration, I wanted to save some cycles in the iteration and simply compute the number of added elements at the end. I removed the assignment in ZEND_HASH_FILL_GROW() because the number of elements is set anyway at the end of the hash function and this avoids an unnecessary computation. I discovered this behaviour while fixing GH-11016, where this filling routine is easily exposed to userland via a specialised VM path [1]. Since this seems to be more a general problem with the macros, and may be triggered outside of the VM handlers, I fixed it in the macros instead of modifying the VM to fixup the number of elements. [1] https://github.com/php/php-src/blob/b2c5acbb010f4bbc7ea9b53ba9bc81d672dd0f34/Zend/zend_vm_def.h#L6132-L6141
1 parent 0579beb commit b6dd6fd

File tree

5 files changed

+78
-3
lines changed

5 files changed

+78
-3
lines changed

Zend/zend_hash.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,12 +1175,12 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht,
11751175
HashTable *__fill_ht = (ht); \
11761176
Bucket *__fill_bkt = __fill_ht->arData + __fill_ht->nNumUsed; \
11771177
uint32_t __fill_idx = __fill_ht->nNumUsed; \
1178+
uint32_t __fill_old_used = __fill_idx; \
11781179
ZEND_ASSERT(HT_FLAGS(__fill_ht) & HASH_FLAG_PACKED);
11791180

11801181
#define ZEND_HASH_FILL_GROW() do { \
11811182
if (UNEXPECTED(__fill_idx >= __fill_ht->nTableSize)) { \
11821183
__fill_ht->nNumUsed = __fill_idx; \
1183-
__fill_ht->nNumOfElements = __fill_idx; \
11841184
__fill_ht->nNextFreeElement = __fill_idx; \
11851185
zend_hash_packed_grow(__fill_ht); \
11861186
__fill_bkt = __fill_ht->arData + __fill_idx; \
@@ -1222,7 +1222,7 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht,
12221222

12231223
#define ZEND_HASH_FILL_FINISH() do { \
12241224
__fill_ht->nNumUsed = __fill_idx; \
1225-
__fill_ht->nNumOfElements = __fill_idx; \
1225+
__fill_ht->nNumOfElements += __fill_idx - __fill_old_used; \
12261226
__fill_ht->nNextFreeElement = __fill_idx; \
12271227
__fill_ht->nInternalPointer = 0; \
12281228
} while (0)

ext/zend_test/test.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,29 @@ static ZEND_FUNCTION(zend_get_map_ptr_last)
327327
RETURN_LONG(CG(map_ptr_last));
328328
}
329329

330+
static ZEND_FUNCTION(zend_test_fill_packed_array)
331+
{
332+
HashTable *parameter;
333+
334+
ZEND_PARSE_PARAMETERS_START(1, 1)
335+
Z_PARAM_ARRAY_HT_EX(parameter, 0, 1)
336+
ZEND_PARSE_PARAMETERS_END();
337+
338+
if (!HT_IS_PACKED(parameter)) {
339+
zend_argument_value_error(1, "must be a packed array");
340+
RETURN_THROWS();
341+
}
342+
343+
zend_hash_extend(parameter, parameter->nNumUsed + 10, true);
344+
ZEND_HASH_FILL_PACKED(parameter) {
345+
for (int i = 0; i < 10; i++) {
346+
zval value;
347+
ZVAL_LONG(&value, i);
348+
ZEND_HASH_FILL_ADD(&value);
349+
}
350+
} ZEND_HASH_FILL_END();
351+
}
352+
330353
static zend_object *zend_test_class_new(zend_class_entry *class_type)
331354
{
332355
zend_object *obj = zend_objects_new(class_type);

ext/zend_test/test.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ function zend_get_current_func_name(): string {}
119119
function zend_call_method(string $class, string $method, mixed $arg1 = UNKNOWN, mixed $arg2 = UNKNOWN): mixed {}
120120

121121
function zend_get_map_ptr_last(): int {}
122+
123+
function zend_test_fill_packed_array(array &$array): void {}
122124
}
123125

124126
namespace ZendTestNS {

ext/zend_test/test_arginfo.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 614310958c6e2acde46c9b7932ba894caf72d6df */
2+
* Stub hash: 7e9e4384787822de32a284d45cf9d1355391e848 */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
55
ZEND_END_ARG_INFO()
@@ -82,6 +82,10 @@ ZEND_END_ARG_INFO()
8282
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_get_map_ptr_last, 0, 0, IS_LONG, 0)
8383
ZEND_END_ARG_INFO()
8484

85+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_fill_packed_array, 0, 1, IS_VOID, 0)
86+
ZEND_ARG_TYPE_INFO(1, array, IS_ARRAY, 0)
87+
ZEND_END_ARG_INFO()
88+
8589
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_ZendSubNS_namespaced_func, 0, 0, _IS_BOOL, 0)
8690
ZEND_END_ARG_INFO()
8791

@@ -143,6 +147,7 @@ static ZEND_FUNCTION(zend_test_parameter_with_attribute);
143147
static ZEND_FUNCTION(zend_get_current_func_name);
144148
static ZEND_FUNCTION(zend_call_method);
145149
static ZEND_FUNCTION(zend_get_map_ptr_last);
150+
static ZEND_FUNCTION(zend_test_fill_packed_array);
146151
static ZEND_FUNCTION(namespaced_func);
147152
static ZEND_METHOD(_ZendTestClass, is_object);
148153
static ZEND_METHOD(_ZendTestClass, __toString);
@@ -182,6 +187,7 @@ static const zend_function_entry ext_functions[] = {
182187
ZEND_FE(zend_get_current_func_name, arginfo_zend_get_current_func_name)
183188
ZEND_FE(zend_call_method, arginfo_zend_call_method)
184189
ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last)
190+
ZEND_FE(zend_test_fill_packed_array, arginfo_zend_test_fill_packed_array)
185191
ZEND_NS_FE("ZendTestNS2\\ZendSubNS", namespaced_func, arginfo_ZendTestNS2_ZendSubNS_namespaced_func)
186192
ZEND_FE_END
187193
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
Test hash packed fill number of elements
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
function number() {
9+
return 6;
10+
}
11+
12+
$my_array = [number() => 0];
13+
zend_test_fill_packed_array($my_array);
14+
15+
var_dump($my_array);
16+
var_dump(count($my_array));
17+
18+
?>
19+
--EXPECT--
20+
array(11) {
21+
[6]=>
22+
int(0)
23+
[7]=>
24+
int(0)
25+
[8]=>
26+
int(1)
27+
[9]=>
28+
int(2)
29+
[10]=>
30+
int(3)
31+
[11]=>
32+
int(4)
33+
[12]=>
34+
int(5)
35+
[13]=>
36+
int(6)
37+
[14]=>
38+
int(7)
39+
[15]=>
40+
int(8)
41+
[16]=>
42+
int(9)
43+
}
44+
int(11)

0 commit comments

Comments
 (0)