Skip to content

Commit 1ac4c83

Browse files
committed
Update API after RFC discussion
1 parent f3cea21 commit 1ac4c83

23 files changed

+340
-361
lines changed

ext/spl/spl_vector.c

Lines changed: 50 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535

3636
#include <stdbool.h>
3737

38+
/* Though rare, it is possible to have 64-bit zend_longs and a 32-bit size_t. */
39+
#define MAX_ZVAL_COUNT ((SIZE_MAX / sizeof(zval)) - 1)
40+
#define MAX_VALID_OFFSET ((size_t)(MAX_ZVAL_COUNT > ZEND_LONG_MAX ? ZEND_LONG_MAX : MAX_ZVAL_COUNT))
41+
3842
/* Miscellaneous utility functions */
3943
static inline zval *spl_zval_copy_range(const zval *original, size_t n) {
4044
const size_t bytes = n * sizeof(zval);
@@ -120,6 +124,15 @@ typedef struct _spl_vector_it {
120124

121125
static void spl_vector_raise_capacity(spl_vector *intern, const size_t new_capacity);
122126

127+
/*
128+
* If a size this large is encountered, assume the allocation will likely fail or
129+
* future changes to the capacity will overflow.
130+
*/
131+
static ZEND_COLD void spl_error_noreturn_max_vector_capacity()
132+
{
133+
zend_error_noreturn(E_ERROR, "exceeded max valid Vector capacity");
134+
}
135+
123136
static spl_vector *spl_vector_from_object(zend_object *obj)
124137
{
125138
return (spl_vector*)((char*)(obj) - XtOffsetOf(spl_vector, std));
@@ -166,7 +179,6 @@ static bool spl_vector_entries_uninitialized(const spl_vector_entries *array)
166179

167180
static void spl_vector_raise_capacity(spl_vector *intern, const size_t new_capacity) {
168181
ZEND_ASSERT(new_capacity > intern->array.capacity);
169-
ZEND_ASSERT(new_capacity <= MAX_VALID_OFFSET + 1);
170182
if (intern->array.capacity == 0) {
171183
intern->array.entries = safe_emalloc(new_capacity, sizeof(zval), 0);
172184
} else {
@@ -205,82 +217,17 @@ static void spl_vector_entries_init_elems(spl_vector_entries *array, zend_long f
205217
}
206218
*/
207219

208-
static inline void spl_vector_entries_set_empty_list(spl_vector_entries *array) {
220+
static zend_always_inline void spl_vector_entries_set_empty_list(spl_vector_entries *array) {
209221
array->size = 0;
210222
array->capacity = 0;
211223
array->entries = (zval *)empty_entry_list;
212224
}
213225

214-
static void spl_vector_entries_init_from_array(spl_vector_entries *array, zend_array *values, bool preserve_keys)
215-
{
216-
const uint32_t num_elements = zend_hash_num_elements(values);
217-
if (num_elements <= 0) {
218-
spl_vector_entries_set_empty_list(array);
219-
return;
220-
}
221-
222-
zval *val;
223-
zval *entries;
224-
225-
array->size = 0; /* reset size in case emalloc() fails */
226-
if (preserve_keys) {
227-
zend_string *str_index;
228-
zend_ulong num_index, max_index = 0;
229-
230-
ZEND_HASH_FOREACH_KEY(values, num_index, str_index) {
231-
if (UNEXPECTED(str_index != NULL || (zend_long)num_index < 0)) {
232-
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "array must contain only positive integer keys");
233-
return;
234-
}
235-
236-
if (num_index > max_index) {
237-
max_index = num_index;
238-
}
239-
} ZEND_HASH_FOREACH_END();
240-
241-
if (UNEXPECTED(max_index > MAX_VALID_OFFSET)) {
242-
zend_error_noreturn("exceeded max valid offset of Vector");
243-
return;
244-
}
245-
const zend_ulong size = max_index + 1;
246-
ZEND_ASSERT(size > 0);
247-
array->entries = entries = safe_emalloc(size, sizeof(zval), 0);
248-
array->size = size;
249-
array->capacity = size;
250-
/* Optimization: Don't need to null remaining elements if all elements from 0..num_elements-1 are set. */
251-
ZEND_ASSERT(size >= num_elements);
252-
if (size > num_elements) {
253-
for (uint32_t i = 0; i < size; i++) {
254-
ZVAL_NULL(&entries[i]);
255-
}
256-
}
257-
258-
ZEND_HASH_FOREACH_KEY_VAL(values, num_index, str_index, val) {
259-
ZEND_ASSERT(num_index < size);
260-
ZEND_ASSERT(!str_index);
261-
/* should happen for non-corrupt array inputs */
262-
ZEND_ASSERT(size == num_elements || Z_TYPE(entries[num_index]) == IS_NULL);
263-
ZVAL_COPY_DEREF(&entries[num_index], val);
264-
} ZEND_HASH_FOREACH_END();
265-
return;
266-
}
267-
268-
size_t i = 0;
269-
array->entries = entries = safe_emalloc(num_elements, sizeof(zval), 0);
270-
array->size = num_elements;
271-
array->capacity = num_elements;
272-
ZEND_HASH_FOREACH_VAL(values, val) {
273-
ZEND_ASSERT(i < num_elements);
274-
ZVAL_COPY_DEREF(&entries[i], val);
275-
i++;
276-
} ZEND_HASH_FOREACH_END();
277-
}
278-
279226
static void spl_vector_entries_init_from_traversable(spl_vector_entries *array, zend_object *obj)
280227
{
281228
zend_class_entry *ce = obj->ce;
282229
zend_object_iterator *iter;
283-
zend_long size = 0, capacity = 0;
230+
size_t size = 0, capacity = 0;
284231
array->size = 0;
285232
array->entries = NULL;
286233
zval *entries = NULL;
@@ -317,6 +264,7 @@ static void spl_vector_entries_init_from_traversable(spl_vector_entries *array,
317264
if (size >= capacity) {
318265
/* Not using Countable::count(), that would potentially have side effects or throw UnsupportedOperationException or be slow to compute */
319266
if (entries) {
267+
/* The safe_erealloc macro emits its own fatal error on integer overflow */
320268
capacity *= 2;
321269
entries = safe_erealloc(entries, capacity, sizeof(zval), 0);
322270
} else {
@@ -520,7 +468,7 @@ PHP_METHOD(Vector, clear)
520468
efree(old_entries);
521469
}
522470

523-
/* Set size of this vector */
471+
/* Set size of this Vector */
524472
PHP_METHOD(Vector, setSize)
525473
{
526474
zend_long size;
@@ -535,7 +483,8 @@ PHP_METHOD(Vector, setSize)
535483
if (size < 0) {
536484
zend_argument_value_error(1, "must be greater than or equal to 0");
537485
} else {
538-
zend_error_noreturn("exceeded max valid Teds\\Vector offset");
486+
spl_error_noreturn_max_vector_capacity();
487+
ZEND_UNREACHABLE();
539488
}
540489
RETURN_THROWS();
541490
}
@@ -593,12 +542,10 @@ PHP_METHOD(Vector, __construct)
593542
{
594543
zval *object = ZEND_THIS;
595544
zval* iterable = NULL;
596-
bool preserve_keys = true;
597545

598-
ZEND_PARSE_PARAMETERS_START(0, 2)
546+
ZEND_PARSE_PARAMETERS_START(0, 1)
599547
Z_PARAM_OPTIONAL
600548
Z_PARAM_ITERABLE(iterable)
601-
Z_PARAM_BOOL(preserve_keys)
602549
ZEND_PARSE_PARAMETERS_END();
603550

604551
spl_vector *intern = Z_VECTOR_P(object);
@@ -608,17 +555,39 @@ PHP_METHOD(Vector, __construct)
608555
/* called __construct() twice, bail out */
609556
RETURN_THROWS();
610557
}
558+
uint32_t num_elements;
559+
HashTable *values;
611560
if (!iterable) {
561+
set_empty_list:
612562
spl_vector_entries_set_empty_list(&intern->array);
613563
return;
614564
}
615565

616566
switch (Z_TYPE_P(iterable)) {
617567
case IS_ARRAY:
618-
spl_vector_entries_init_from_array(&intern->array, Z_ARRVAL_P(iterable), preserve_keys);
568+
values = Z_ARRVAL_P(iterable);
569+
num_elements = zend_hash_num_elements(values);
570+
if (num_elements == 0) {
571+
goto set_empty_list;
572+
}
573+
574+
zval *val;
575+
zval *entries;
576+
spl_vector_entries *array = &intern->array;
577+
578+
array->size = 0; /* reset size in case emalloc() fails */
579+
size_t i = 0;
580+
array->entries = entries = safe_emalloc(num_elements, sizeof(zval), 0);
581+
array->size = num_elements;
582+
array->capacity = num_elements;
583+
ZEND_HASH_FOREACH_VAL(values, val) {
584+
ZEND_ASSERT(i < num_elements);
585+
ZVAL_COPY_DEREF(&entries[i], val);
586+
i++;
587+
} ZEND_HASH_FOREACH_END();
619588
return;
620589
case IS_OBJECT:
621-
spl_vector_entries_init_from_traversable(&intern->array, Z_OBJ_P(iterable), preserve_keys);
590+
spl_vector_entries_init_from_traversable(&intern->array, Z_OBJ_P(iterable));
622591
return;
623592
EMPTY_SWITCH_DEFAULT_CASE();
624593
}
@@ -776,9 +745,7 @@ static void spl_vector_entries_init_from_array_values(spl_vector_entries *array,
776745
{
777746
size_t num_entries = zend_hash_num_elements(raw_data);
778747
if (num_entries == 0) {
779-
array->size = 0;
780-
array->capacity = 0;
781-
array->entries = NULL;
748+
spl_vector_entries_set_empty_list(array);
782749
return;
783750
}
784751
zval *entries = safe_emalloc(num_entries, sizeof(zval), 0);
@@ -1184,7 +1151,7 @@ static zend_always_inline void spl_vector_push(spl_vector *intern, zval *value)
11841151

11851152
if (old_size >= old_capacity) {
11861153
ZEND_ASSERT(old_size == old_capacity);
1187-
spl_vector_raise_capacity(intern, old_size ? old_size * 2 : 4);
1154+
spl_vector_raise_capacity(intern, old_size > 2 ? old_size * 2 : 4);
11881155
}
11891156
ZVAL_COPY(&intern->array.entries[old_size], value);
11901157
intern->array.size++;
@@ -1208,18 +1175,13 @@ PHP_METHOD(Vector, push)
12081175
const size_t new_size = old_size + argc;
12091176
/* The compiler will type check but eliminate dead code on platforms where size_t is 32 bits (4 bytes) */
12101177
if (SIZEOF_SIZE_T < 8 && UNEXPECTED(new_size > MAX_VALID_OFFSET + 1 || new_size < old_size)) {
1211-
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "exceeded max valid offset");
1212-
RETURN_THROWS();
1178+
spl_error_noreturn_max_vector_capacity();
1179+
ZEND_UNREACHABLE();
12131180
}
12141181
const size_t old_capacity = intern->array.capacity;
12151182
if (new_size > old_capacity) {
1216-
size_t new_capacity = old_size ? old_size * 2 : 4;
1217-
if (UNEXPECTED(new_size > new_capacity)) {
1218-
new_capacity = new_size + (new_size >> 1);
1219-
}
1220-
if (SIZEOF_SIZE_T < 8 && UNEXPECTED(new_capacity > MAX_VALID_OFFSET + 1)) {
1221-
new_capacity = MAX_VALID_OFFSET + 1;
1222-
}
1183+
const size_t new_capacity = new_size >= 3 ? (new_size - 1) * 2 : 4;
1184+
ZEND_ASSERT(new_capacity >= new_size);
12231185
spl_vector_raise_capacity(intern, new_capacity);
12241186
}
12251187
zval *entries = intern->array.entries;

ext/spl/tests/Vector/Vector.phpt

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,25 @@ Vector constructed from array
33
--FILE--
44
<?php
55
// discards keys
6-
$it = new Vector(['first' => 'x', 'second' => new stdClass()], preserveKeys: false);
7-
foreach ($it as $key => $value) {
6+
$vec = new Vector(['first' => 'x', 'second' => new stdClass()]);
7+
foreach ($vec as $key => $value) {
88
printf("Key: %s\nValue: %s\n", var_export($key, true), var_export($value, true));
99
}
10-
var_dump($it);
11-
var_dump((array)$it);
10+
var_dump($vec);
11+
var_dump((array)$vec);
1212

13-
$it = new Vector([]);
14-
var_dump($it);
15-
var_dump((array)$it);
16-
foreach ($it as $key => $value) {
13+
$vec = new Vector([]);
14+
var_dump($vec);
15+
var_dump((array)$vec);
16+
foreach ($vec as $key => $value) {
1717
echo "Unreachable\n";
1818
}
1919

20-
// The default is to preserve keys
21-
$it = new Vector([2 => 'third', 0 => 'first']);
22-
var_dump($it);
20+
// Vector will always reindex keys in the order of iteration, like array_values() does.
21+
$vec = new Vector([2 => 'a', 0 => 'b']);
22+
var_dump($vec);
2323

24-
try {
25-
$it = new Vector([-1 => new stdClass()]);
26-
} catch (UnexpectedValueException $e) {
27-
echo "Caught: {$e->getMessage()}\n";
28-
}
29-
30-
try {
31-
$it = new Vector(['0a' => new stdClass()]);
32-
} catch (UnexpectedValueException $e) {
33-
echo "Caught: {$e->getMessage()}\n";
34-
}
24+
var_dump(new Vector([-1 => new stdClass()]));
3525
?>
3626
--EXPECT--
3727
Key: 0
@@ -57,13 +47,14 @@ object(Vector)#3 (0) {
5747
}
5848
array(0) {
5949
}
60-
object(Vector)#1 (3) {
50+
object(Vector)#1 (2) {
6151
[0]=>
62-
string(5) "first"
52+
string(1) "a"
6353
[1]=>
64-
NULL
65-
[2]=>
66-
string(5) "third"
54+
string(1) "b"
6755
}
68-
Caught: array must contain only positive integer keys
69-
Caught: array must contain only positive integer keys
56+
object(Vector)#3 (1) {
57+
[0]=>
58+
object(stdClass)#4 (0) {
59+
}
60+
}

ext/spl/tests/Vector/aggregate.phpt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ Vector is an IteratorAggregate
33
--FILE--
44
<?php
55

6-
$it = new Vector(['x', (object)['key' => 'value']]);
7-
foreach ($it as $k1 => $v1) {
8-
foreach ($it as $k2 => $v2) {
9-
printf("k1=%s k2=%s v1=%s v2=%s\n", json_encode($k1), json_encode($k2), json_encode($v1), json_encode($v2));
6+
$vector = new Vector(['x', (object)['key' => 'value']]);
7+
foreach ($vector as $k => $v) {
8+
foreach ($vector as $k2 => $v2) {
9+
printf("k=%s k2=%s v=%s v2=%s\n", json_encode($k), json_encode($k2), json_encode($v), json_encode($v2));
1010
}
1111
}
1212
?>
1313
--EXPECT--
14-
k1=0 k2=0 v1="x" v2="x"
15-
k1=0 k2=1 v1="x" v2={"key":"value"}
16-
k1=1 k2=0 v1={"key":"value"} v2="x"
17-
k1=1 k2=1 v1={"key":"value"} v2={"key":"value"}
14+
k=0 k2=0 v="x" v2="x"
15+
k=0 k2=1 v="x" v2={"key":"value"}
16+
k=1 k2=0 v={"key":"value"} v2="x"
17+
k=1 k2=1 v={"key":"value"} v2={"key":"value"}

ext/spl/tests/Vector/arrayCast.phpt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ Vector to array
33
--FILE--
44
<?php
55
// discards keys
6-
$it = new Vector([strtoupper('test')]);
7-
var_dump((array)$it);
8-
$it[0] = strtoupper('test2');
9-
var_dump((array)$it);
10-
var_dump($it);
11-
$it->pop();
12-
var_dump($it);
6+
$vec = new Vector([strtoupper('test')]);
7+
var_dump((array)$vec);
8+
$vec[0] = strtoupper('test2');
9+
var_dump((array)$vec);
10+
var_dump($vec);
11+
$vec->pop();
12+
var_dump($vec);
1313

1414

1515
?>

ext/spl/tests/Vector/clear.phpt

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ Vector clear
33
--FILE--
44
<?php
55

6-
$it = new Vector([new stdClass()]);
7-
var_dump($it->toArray());
8-
var_dump($it->count());
9-
var_dump($it->capacity());
10-
$it->clear();
11-
foreach ($it as $value) {
6+
$vec = new Vector([new stdClass()]);
7+
var_dump($vec->toArray());
8+
var_dump($vec->count());
9+
$vec->clear();
10+
foreach ($vec as $value) {
1211
echo "Not reached\n";
1312
}
14-
var_dump($it->toArray());
15-
var_dump($it->count());
16-
var_dump($it->capacity());
13+
var_dump($vec->toArray());
14+
var_dump($vec->count());
1715
?>
1816
--EXPECT--
1917
array(1) {
@@ -22,8 +20,6 @@ array(1) {
2220
}
2321
}
2422
int(1)
25-
int(1)
2623
array(0) {
2724
}
2825
int(0)
29-
int(0)

0 commit comments

Comments
 (0)