Skip to content

Commit 2ef1930

Browse files
authored
Fix number of elements after packed hash filling (php#11022)
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 discovered this behaviour while fixing phpGH-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 f17cf2e commit 2ef1930

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
@@ -1517,8 +1517,8 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht,
15171517

15181518
#define ZEND_HASH_FILL_GROW() do { \
15191519
if (UNEXPECTED(__fill_idx >= __fill_ht->nTableSize)) { \
1520+
__fill_ht->nNumOfElements += __fill_idx - __fill_ht->nNumUsed; \
15201521
__fill_ht->nNumUsed = __fill_idx; \
1521-
__fill_ht->nNumOfElements = __fill_idx; \
15221522
__fill_ht->nNextFreeElement = __fill_idx; \
15231523
zend_hash_packed_grow(__fill_ht); \
15241524
__fill_val = __fill_ht->arPacked + __fill_idx; \
@@ -1557,8 +1557,8 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht,
15571557
} while (0)
15581558

15591559
#define ZEND_HASH_FILL_FINISH() do { \
1560+
__fill_ht->nNumOfElements += __fill_idx - __fill_ht->nNumUsed; \
15601561
__fill_ht->nNumUsed = __fill_idx; \
1561-
__fill_ht->nNumOfElements = __fill_idx; \
15621562
__fill_ht->nNextFreeElement = __fill_idx; \
15631563
__fill_ht->nInternalPointer = 0; \
15641564
} while (0)

ext/zend_test/test.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,29 @@ static ZEND_FUNCTION(zend_get_map_ptr_last)
533533
RETURN_LONG(CG(map_ptr_last));
534534
}
535535

536+
static ZEND_FUNCTION(zend_test_fill_packed_array)
537+
{
538+
HashTable *parameter;
539+
540+
ZEND_PARSE_PARAMETERS_START(1, 1)
541+
Z_PARAM_ARRAY_HT_EX(parameter, 0, 1)
542+
ZEND_PARSE_PARAMETERS_END();
543+
544+
if (!HT_IS_PACKED(parameter)) {
545+
zend_argument_value_error(1, "must be a packed array");
546+
RETURN_THROWS();
547+
}
548+
549+
zend_hash_extend(parameter, parameter->nNumUsed + 10, true);
550+
ZEND_HASH_FILL_PACKED(parameter) {
551+
for (int i = 0; i < 10; i++) {
552+
zval value;
553+
ZVAL_LONG(&value, i);
554+
ZEND_HASH_FILL_ADD(&value);
555+
}
556+
} ZEND_HASH_FILL_END();
557+
}
558+
536559
static zend_object *zend_test_class_new(zend_class_entry *class_type)
537560
{
538561
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
@@ -192,6 +192,8 @@ function zend_test_zend_call_stack_use_all(): int {}
192192
function zend_test_is_string_marked_as_valid_utf8(string $string): bool {}
193193

194194
function zend_get_map_ptr_last(): int {}
195+
196+
function zend_test_fill_packed_array(array &$array): void {}
195197
}
196198

197199
namespace ZendTestNS {

ext/zend_test/test_arginfo.h

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
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)