summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Zend/zend_hash.h27
-rw-r--r--ext/json/json_encoder.c25
-rw-r--r--ext/opcache/Optimizer/sccp.c1
-rwxr-xr-xext/standard/basic_functions.stub.php2
-rw-r--r--ext/standard/basic_functions_arginfo.h8
-rw-r--r--ext/standard/tests/general_functions/array_is_list.phpt98
-rw-r--r--ext/standard/type.c13
7 files changed, 151 insertions, 23 deletions
diff --git a/Zend/zend_hash.h b/Zend/zend_hash.h
index e339345a32..64a9220573 100644
--- a/Zend/zend_hash.h
+++ b/Zend/zend_hash.h
@@ -1188,6 +1188,33 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht,
ZEND_HASH_FILL_FINISH(); \
} while (0)
+/* Check if an array is a list */
+static zend_always_inline zend_bool zend_array_is_list(zend_array *array)
+{
+ zend_long expected_idx = 0;
+ zend_long num_idx;
+ zend_string* str_idx;
+ /* Empty arrays are lists */
+ if (zend_hash_num_elements(array) == 0) {
+ return 1;
+ }
+
+ /* Packed arrays are lists */
+ if (HT_IS_PACKED(array) && HT_IS_WITHOUT_HOLES(array)) {
+ return 1;
+ }
+
+ /* Check if the list could theoretically be repacked */
+ ZEND_HASH_FOREACH_KEY(array, num_idx, str_idx) {
+ if (str_idx != NULL || num_idx != expected_idx++) {
+ return 0;
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ return 1;
+}
+
+
static zend_always_inline zval *_zend_hash_append_ex(HashTable *ht, zend_string *key, zval *zv, bool interned)
{
uint32_t idx = ht->nNumUsed++;
diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c
index 92e4a10933..f608936349 100644
--- a/ext/json/json_encoder.c
+++ b/ext/json/json_encoder.c
@@ -36,29 +36,10 @@ static int php_json_escape_string(
static int php_json_determine_array_type(zval *val) /* {{{ */
{
- int i;
- HashTable *myht = Z_ARRVAL_P(val);
-
- i = myht ? zend_hash_num_elements(myht) : 0;
- if (i > 0) {
- zend_string *key;
- zend_ulong index, idx;
+ zend_array *myht = Z_ARRVAL_P(val);
- if (HT_IS_PACKED(myht) && HT_IS_WITHOUT_HOLES(myht)) {
- return PHP_JSON_OUTPUT_ARRAY;
- }
-
- idx = 0;
- ZEND_HASH_FOREACH_KEY(myht, index, key) {
- if (key) {
- return PHP_JSON_OUTPUT_OBJECT;
- } else {
- if (index != idx) {
- return PHP_JSON_OUTPUT_OBJECT;
- }
- }
- idx++;
- } ZEND_HASH_FOREACH_END();
+ if (myht) {
+ return zend_array_is_list(myht) ? PHP_JSON_OUTPUT_ARRAY : PHP_JSON_OUTPUT_OBJECT;
}
return PHP_JSON_OUTPUT_ARRAY;
diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c
index 7f9bdbdbb5..bc9dc38680 100644
--- a/ext/opcache/Optimizer/sccp.c
+++ b/ext/opcache/Optimizer/sccp.c
@@ -788,6 +788,7 @@ static bool can_ct_eval_func_call(zend_string *name, uint32_t num_args, zval **a
|| zend_string_equals_literal(name, "array_diff")
|| zend_string_equals_literal(name, "array_diff_assoc")
|| zend_string_equals_literal(name, "array_diff_key")
+ || zend_string_equals_literal(name, "array_is_list")
|| zend_string_equals_literal(name, "array_key_exists")
|| zend_string_equals_literal(name, "array_keys")
|| zend_string_equals_literal(name, "array_merge")
diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php
index b6597d262e..99d0d3c63d 100755
--- a/ext/standard/basic_functions.stub.php
+++ b/ext/standard/basic_functions.stub.php
@@ -248,6 +248,8 @@ function array_chunk(array $array, int $length, bool $preserve_keys = false): ar
function array_combine(array $keys, array $values): array {}
+function array_is_list(array $array): bool {}
+
/* base64.c */
function base64_encode(string $string): string {}
diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h
index e416fc5e27..5eafa59b50 100644
--- a/ext/standard/basic_functions_arginfo.h
+++ b/ext/standard/basic_functions_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 4e471966d507762dd6fdd2fc4200c8430fac97f4 */
+ * Stub hash: 7540039937587f05584660bc1a1a8a80aa5ccbd1 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
@@ -360,6 +360,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_combine, 0, 2, IS_ARRAY, 0
ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_is_list, 0, 1, _IS_BOOL, 0)
+ ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0)
+ZEND_END_ARG_INFO()
+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_base64_encode, 0, 1, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0)
ZEND_END_ARG_INFO()
@@ -2309,6 +2313,7 @@ ZEND_FUNCTION(array_map);
ZEND_FUNCTION(array_key_exists);
ZEND_FUNCTION(array_chunk);
ZEND_FUNCTION(array_combine);
+ZEND_FUNCTION(array_is_list);
ZEND_FUNCTION(base64_encode);
ZEND_FUNCTION(base64_decode);
ZEND_FUNCTION(constant);
@@ -2933,6 +2938,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FALIAS(key_exists, array_key_exists, arginfo_key_exists)
ZEND_FE(array_chunk, arginfo_array_chunk)
ZEND_FE(array_combine, arginfo_array_combine)
+ ZEND_FE(array_is_list, arginfo_array_is_list)
ZEND_FE(base64_encode, arginfo_base64_encode)
ZEND_FE(base64_decode, arginfo_base64_decode)
ZEND_FE(constant, arginfo_constant)
diff --git a/ext/standard/tests/general_functions/array_is_list.phpt b/ext/standard/tests/general_functions/array_is_list.phpt
new file mode 100644
index 0000000000..1cc2886f96
--- /dev/null
+++ b/ext/standard/tests/general_functions/array_is_list.phpt
@@ -0,0 +1,98 @@
+--TEST--
+Test array_is_list() function
+--FILE--
+<?php
+
+function test_is_list(string $desc, $val) : void {
+ try {
+ printf("%s: %s\n", $desc, json_encode(array_is_list($val)));
+ } catch (TypeError $e) {
+ printf("%s: threw %s\n", $desc, $e->getMessage());
+ }
+}
+
+test_is_list("empty", []);
+test_is_list("one", [1]);
+test_is_list("two", [1,2]);
+test_is_list("three", [1,2,3]);
+test_is_list("four", [1,2,3,4]);
+test_is_list("ten", range(0, 10));
+
+test_is_list("null", null);
+test_is_list("int", 123);
+test_is_list("float", 1.23);
+test_is_list("string", "string");
+test_is_list("object", new stdClass());
+test_is_list("true", true);
+test_is_list("false", false);
+
+test_is_list("string key", ["a" => 1]);
+test_is_list("mixed keys", [0 => 0, "a" => 1]);
+test_is_list("ordered keys", [0 => 0, 1 => 1]);
+test_is_list("shuffled keys", [1 => 0, 0 => 1]);
+test_is_list("skipped keys", [0 => 0, 2 => 2]);
+
+$arr = [1, 2, 3];
+unset($arr[0]);
+test_is_list("unset first", $arr);
+
+$arr = [1, 2, 3];
+unset($arr[1]);
+test_is_list("unset middle", $arr);
+
+$arr = [1, 2, 3];
+unset($arr[2]);
+test_is_list("unset end", $arr);
+
+$arr = [1, "a" => "a", 2];
+unset($arr["a"]);
+test_is_list("unset string key", $arr);
+
+$arr = [1 => 1, 0 => 0];
+unset($arr[1]);
+test_is_list("unset into order", $arr);
+
+$arr = ["a" => 1];
+unset($arr["a"]);
+test_is_list("unset to empty", $arr);
+
+$arr = [1, 2, 3];
+$arr[] = 4;
+test_is_list("append implicit", $arr);
+
+$arr = [1, 2, 3];
+$arr[3] = 4;
+test_is_list("append explicit", $arr);
+
+$arr = [1, 2, 3];
+$arr[4] = 5;
+test_is_list("append with gap", $arr);
+
+--EXPECT--
+empty: true
+one: true
+two: true
+three: true
+four: true
+ten: true
+null: threw array_is_list(): Argument #1 ($array) must be of type array, null given
+int: threw array_is_list(): Argument #1 ($array) must be of type array, int given
+float: threw array_is_list(): Argument #1 ($array) must be of type array, float given
+string: threw array_is_list(): Argument #1 ($array) must be of type array, string given
+object: threw array_is_list(): Argument #1 ($array) must be of type array, stdClass given
+true: threw array_is_list(): Argument #1 ($array) must be of type array, bool given
+false: threw array_is_list(): Argument #1 ($array) must be of type array, bool given
+string key: false
+mixed keys: false
+ordered keys: true
+shuffled keys: false
+skipped keys: false
+unset first: false
+unset middle: false
+unset end: true
+unset string key: true
+unset into order: true
+unset to empty: true
+append implicit: true
+append explicit: true
+append with gap: false \ No newline at end of file
diff --git a/ext/standard/type.c b/ext/standard/type.c
index afe0e7afc2..1036dd7d06 100644
--- a/ext/standard/type.c
+++ b/ext/standard/type.c
@@ -321,6 +321,19 @@ PHP_FUNCTION(is_array)
}
/* }}} */
+/* {{{ Returns true if $array is an array whose keys are all numeric, sequential, and start at 0 */
+PHP_FUNCTION(array_is_list)
+{
+ HashTable *array;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ARRAY_HT(array)
+ ZEND_PARSE_PARAMETERS_END();
+
+ RETURN_BOOL(zend_array_is_list(array));
+}
+/* }}} */
+
/* {{{ Returns true if variable is an object
Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_object)