diff --git a/CMakeLists.txt b/CMakeLists.txt index f81432f458f..f4d17a56f13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,6 +137,8 @@ if (DEFINED verbose) add_definitions(-DSW_VERBOSE) endif() +set(php_dir "" CACHE STRING "Set the root directory of PHP") + if (DEFINED php_dir) set(PHP_CONFIG "${php_dir}/bin/php-config") else () diff --git a/config.m4 b/config.m4 index aeb9b521d1a..1583cb81c81 100644 --- a/config.m4 +++ b/config.m4 @@ -116,6 +116,11 @@ PHP_ARG_ENABLE([swoole-thread], [AS_HELP_STRING([--enable-swoole-thread], [Enable swoole thread support])], [no], [no]) +PHP_ARG_ENABLE([swoole-stdext], + [whether to enable swoole stdext support], + [AS_HELP_STRING([--enable-swoole-stdext], + [Enable swoole stdext support([Experimental] This module is only used for swoole-cli. If you are unsure which feature you need, keep it disabled)])], [no], [no]) + PHP_ARG_ENABLE([swoole-coro-time], [whether to enable coroutine execution time ], [AS_HELP_STRING([--enable-swoole-coro-time], @@ -965,6 +970,10 @@ EOF AC_DEFINE(SW_THREAD, 1, [enable swoole thread support]) fi + if test "$PHP_SWOOLE_STDEXT" != "no"; then + AC_DEFINE(SW_STDEXT, 1, [enable swoole stdext support]) + fi + if test "$PHP_SOCKETS" = "yes"; then AC_MSG_CHECKING([for php_sockets.h]) diff --git a/examples/stdext/array.php b/examples/stdext/array.php new file mode 100644 index 00000000000..46e10999587 --- /dev/null +++ b/examples/stdext/array.php @@ -0,0 +1,76 @@ +slice(1, 2); +var_dump($arr); + +$array1 = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5]; +$array2 = [6, 7, 8, 9, 10, 11, 12]; + +var_dump($array2->count()); + +// var_dump($array2->all(function ($value) { +// return $value > 10; +// })); + +$input_array = array("FirSt" => 1, "SecOnd" => 4); +print_r($input_array->changeKeyCase(CASE_UPPER)); + +$array4 = array(0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red'); +$key = $array4->search('green'); +var_dump($key); + +echo "==================================[contains]===================================\n"; +$os = array("Mac", "Windows", "Linux"); +var_dump($os->contains("Windows")); +var_dump($os->contains("Unix")); + +echo "==================================[isList]===================================\n"; +var_dump($array1->isList()); +var_dump($array2->isList()); + +var_dump($array1->keys()); +var_dump($array2->values()); + +echo "==================================[join]===================================\n"; +var_dump(['a', 'b', 'c']->join(',')); + +echo "==================================[method not exists]===================================\n"; +try { + $array->notExists(); +} catch (throwable $e) { + echo "Caught exception: ", $e->getMessage(), "\n"; +} + +function odd($var) +{ + // returns whether the input integer is odd + return $var & 1; +} + +function even($var) +{ + // returns whether the input integer is even + return !($var & 1); +} + +echo "Odd :\n"; +print_r($array1->filter("odd")); + +echo "Even:\n"; +print_r($array2->filter("even")); + + +echo "==================================[array_map]===================================\n"; + +$a = [1, 2, 3, 4, 5]; +$b = $a->map(function ($n) { + return ($n * $n * $n); +}); +var_dump($b); + +echo "==================================[array_key_exists]===================================\n"; +$searchArray = ['first' => null, 'second' => 4]; + +var_dump(isset($searchArray['first'])); +var_dump($searchArray->keyExists('first')); diff --git a/examples/stdext/foreach.php b/examples/stdext/foreach.php new file mode 100644 index 00000000000..27472984e9a --- /dev/null +++ b/examples/stdext/foreach.php @@ -0,0 +1,7 @@ +'); + +$arr[] = 1; +// $arr[0] += 10; +// assert($arr[0] == 11); +$arr[0] .= "hello world"; \ No newline at end of file diff --git a/examples/stdext/ref.php b/examples/stdext/ref.php new file mode 100644 index 00000000000..731f353960b --- /dev/null +++ b/examples/stdext/ref.php @@ -0,0 +1,24 @@ + $val) { + echo "fruits[" . $key . "] = " . $val . "\n"; +} + +$b = &$fruits; +$b->sort(SORT_NATURAL | SORT_FLAG_CASE); + +echo "After sorting:\n"; +foreach ($fruits as $key => $val) { + echo "fruits[" . $key . "] = " . $val . "\n"; +} + +$stack = array("orange", "banana", "apple", "raspberry"); + +$ref = &$stack; +$fruit = $ref->shift(); +var_dump($stack); + +$ref->unshift("kiwi"); +var_dump($stack); diff --git a/examples/stdext/string.php b/examples/stdext/string.php new file mode 100644 index 00000000000..73d3295d68b --- /dev/null +++ b/examples/stdext/string.php @@ -0,0 +1,36 @@ +toUpper()); + +var_dump($str->split(' ')->search('world')); +var_dump($str->length()); +var_dump('test'->length()); + +var_dump($str->indexOf('world')); +var_dump($str->substr(1, 4)); + +var_dump($str->startsWith('hello')); +var_dump($str->endsWith('world')); +var_dump($str->endsWith('.php')); + +var_dump($str->md5(), $str->sha1(), $str->crc32()); +var_dump($str->hash('sha256')); +echo "==============================hash=====================\n"; +var_dump($str->md5() === $str->hash('md5')); + +$str = 'first=value&arr[]=foo+bar&arr[]=baz'; +$output = $str->parseStr(); +echo $output['first']; // value +echo $output['arr'][0]; // foo bar +echo $output['arr'][1]; // baz + +var_dump($str->urlEncode()); diff --git a/examples/stdext/typed_array.php b/examples/stdext/typed_array.php new file mode 100644 index 00000000000..30f4b3f4a9c --- /dev/null +++ b/examples/stdext/typed_array.php @@ -0,0 +1,7 @@ +'); + +$list[] = 123; +$list[] = 345; +$list[] = 'hello'; // 异常 + diff --git a/examples/stdext/typed_array_map.php b/examples/stdext/typed_array_map.php new file mode 100644 index 00000000000..0df81adbbb1 --- /dev/null +++ b/examples/stdext/typed_array_map.php @@ -0,0 +1,6 @@ +'); + +$list["hello"] = 123; +$list[] = 345; +$list["hello"] = 'hello'; diff --git a/ext-src/php_swoole.cc b/ext-src/php_swoole.cc index d7ba8f99d29..1fc1c875b99 100644 --- a/ext-src/php_swoole.cc +++ b/ext-src/php_swoole.cc @@ -28,6 +28,9 @@ BEGIN_EXTERN_C() #include "stubs/php_swoole_arginfo.h" #include "stubs/php_swoole_ex_arginfo.h" +#ifdef SW_STDEXT +#include "stubs/php_swoole_stdext_arginfo.h" +#endif END_EXTERN_C() #include "swoole_mime_type.h" @@ -102,6 +105,10 @@ static PHP_FUNCTION(swoole_internal_call_user_shutdown_begin); static PHP_FUNCTION(swoole_implicit_fn); SW_EXTERN_C_END +#ifdef SW_STDEXT +#include "php_swoole_stdext.h" +#endif + // clang-format off const zend_function_entry swoole_functions[] = { PHP_FE(swoole_version, arginfo_swoole_version) @@ -148,6 +155,32 @@ const zend_function_entry swoole_functions[] = { ZEND_FE(swoole_name_resolver_lookup, arginfo_swoole_name_resolver_lookup) ZEND_FE(swoole_name_resolver_add, arginfo_swoole_name_resolver_add) ZEND_FE(swoole_name_resolver_remove, arginfo_swoole_name_resolver_remove) + // for stdext +#ifdef SW_STDEXT + ZEND_FE(swoole_call_array_method, arginfo_swoole_call_array_method) + ZEND_FE(swoole_call_string_method, arginfo_swoole_call_string_method) + ZEND_FE(swoole_call_stream_method, arginfo_swoole_call_stream_method) + ZEND_FE(swoole_array_search, arginfo_swoole_array_search) + ZEND_FE(swoole_array_contains, arginfo_swoole_array_contains) + ZEND_FE(swoole_array_join, arginfo_swoole_array_join) + ZEND_FE(swoole_array_key_exists, arginfo_swoole_array_key_exists) + ZEND_FE(swoole_array_map, arginfo_swoole_array_map) + ZEND_FE(swoole_str_split, arginfo_swoole_str_split) + ZEND_FE(swoole_parse_str, arginfo_swoole_parse_str) + ZEND_FE(swoole_hash, arginfo_swoole_hash) + ZEND_FE(swoole_typed_array, arginfo_swoole_typed_array) + ZEND_FE(swoole_array_is_typed, arginfo_swoole_array_is_typed) + ZEND_FE(swoole_str_is_empty, arginfo_swoole_str_is_empty) + ZEND_FE(swoole_array_is_empty, arginfo_swoole_array_is_empty) + ZEND_FE(swoole_str_match, arginfo_swoole_str_match) + ZEND_FE(swoole_str_match_all, arginfo_swoole_str_match_all) + ZEND_FE(swoole_str_json_decode, arginfo_swoole_str_json_decode) + ZEND_FE(swoole_str_json_decode_to_object, arginfo_swoole_str_json_decode_to_object) + ZEND_FE(swoole_str_replace, arginfo_swoole_str_replace) + ZEND_FE(swoole_str_ireplace, arginfo_swoole_str_ireplace) + ZEND_FE(swoole_array_replace_str, arginfo_swoole_array_replace_str) + ZEND_FE(swoole_array_ireplace_str, arginfo_swoole_array_ireplace_str) +#endif PHP_FE_END /* Must be the last line in swoole_functions[] */ }; @@ -430,7 +463,7 @@ SW_API bool php_swoole_unserialize(zend_string *data, zval *zv) { PHP_VAR_UNSERIALIZE_DESTROY(var_hash); if (!unserialized) { swoole_warning("unserialize() failed, Error at offset " ZEND_LONG_FMT " of %zd bytes", - (zend_long)((char *) p - ZSTR_VAL(data)), + (zend_long) ((char *) p - ZSTR_VAL(data)), l); } return unserialized; @@ -807,6 +840,10 @@ PHP_MINIT_FUNCTION(swoole) { CG(function_table), "swoole_coroutine_create", CG(function_table), "go", arginfo_swoole_coroutine_create); SW_FUNCTION_ALIAS( CG(function_table), "swoole_coroutine_defer", CG(function_table), "defer", arginfo_swoole_coroutine_defer); +#ifdef SW_STDEXT + SW_FUNCTION_ALIAS( + CG(function_table), "swoole_typed_array", CG(function_table), "typed_array", arginfo_swoole_typed_array); +#endif } swoole_init(); @@ -875,6 +912,9 @@ PHP_MINIT_FUNCTION(swoole) { php_swoole_thread_map_minit(module_number); php_swoole_thread_arraylist_minit(module_number); #endif +#ifdef SW_STDEXT + php_swoole_stdext_minit(module_number); +#endif SwooleG.fatal_error = fatal_error; Socket::default_buffer_size = SWOOLE_G(socket_buffer_size); @@ -1589,7 +1629,7 @@ static PHP_FUNCTION(swoole_substr_unserialize) { if ((zend_long) buf_len <= offset) { RETURN_FALSE; } - if (length <= 0 || length > (zend_long)(buf_len - offset)) { + if (length <= 0 || length > (zend_long) (buf_len - offset)) { length = buf_len - offset; } zend::unserialize(return_value, buf + offset, length, options ? Z_ARRVAL_P(options) : NULL); @@ -1629,7 +1669,7 @@ static PHP_FUNCTION(swoole_substr_json_decode) { php_error_docref(nullptr, E_WARNING, "Offset must be less than the length of the string"); RETURN_NULL(); } - if (length <= 0 || length > (zend_long)(str_len - offset)) { + if (length <= 0 || length > (zend_long) (str_len - offset)) { length = str_len - offset; } /* For BC reasons, the bool $assoc overrides the long $options bit for PHP_JSON_OBJECT_AS_ARRAY */ diff --git a/ext-src/php_swoole_private.h b/ext-src/php_swoole_private.h index 51c16a0a7e6..6dc704ef10d 100644 --- a/ext-src/php_swoole_private.h +++ b/ext-src/php_swoole_private.h @@ -308,6 +308,9 @@ void php_swoole_thread_queue_minit(int module_number); void php_swoole_thread_map_minit(int module_number); void php_swoole_thread_arraylist_minit(int module_number); #endif +#ifdef SW_STDEXT +void php_swoole_stdext_minit(int module_number); +#endif /** * RINIT @@ -391,10 +394,10 @@ php_socket *php_swoole_convert_to_socket(int sock); bool php_swoole_array_to_cpu_set(const zval *array, cpu_set_t *cpu_set); /** * Converts a cpu_set_t structure to a PHP array. - * + * * Note: On Cygwin platform, CPU_ISSET is a function that takes a non-const pointer as its second parameter, * which is why the cpu_set parameter cannot be declared as const. - * + * * @param array The PHP array to store the CPU set information * @param cpu_set The CPU set structure to convert */ @@ -427,62 +430,48 @@ typedef zend_string error_filename_t; #define SW_ZVAL_SOCKET(return_value, result) ZVAL_OBJ(return_value, &result->std) #define SW_Z_SOCKET_P(zsocket) Z_SOCKET_P(zsocket) -#ifndef ZVAL_IS_BOOL -static sw_inline zend_bool ZVAL_IS_BOOL(zval *v) { - return Z_TYPE_P(v) == IS_TRUE || Z_TYPE_P(v) == IS_FALSE; +static sw_inline zend_bool ZVAL_IS_TRUE(const zval *v) { + return Z_TYPE_P(v) == IS_TRUE; } -#endif -#ifndef ZVAL_IS_TRUE -static sw_inline zend_bool ZVAL_IS_TRUE(zval *v) { - return Z_TYPE_P(v) == IS_TRUE; +static sw_inline zend_bool ZVAL_IS_FALSE(const zval *v) { + return Z_TYPE_P(v) == IS_FALSE; } -#endif -#ifndef ZVAL_IS_UNDEF -static sw_inline zend_bool ZVAL_IS_UNDEF(zval *v) { - return Z_TYPE_P(v) == IS_UNDEF; +static sw_inline zend_bool ZVAL_IS_BOOL(const zval *v) { + return ZVAL_IS_TRUE(v) || ZVAL_IS_FALSE(v); } -#endif -#ifndef ZVAL_IS_FALSE -static sw_inline zend_bool ZVAL_IS_FALSE(zval *v) { - return Z_TYPE_P(v) == IS_FALSE; +static sw_inline zend_bool ZVAL_IS_UNDEF(const zval *v) { + return Z_TYPE_P(v) == IS_UNDEF; } -#endif -#ifndef ZVAL_IS_LONG -static sw_inline zend_bool ZVAL_IS_LONG(zval *v) { +static sw_inline zend_bool ZVAL_IS_LONG(const zval *v) { return Z_TYPE_P(v) == IS_LONG; } -#endif -#ifndef ZVAL_IS_STRING -static sw_inline zend_bool ZVAL_IS_STRING(zval *v) { +static sw_inline zend_bool ZVAL_IS_STRING(const zval *v) { return Z_TYPE_P(v) == IS_STRING; } -#endif -#ifndef Z_BVAL_P -static sw_inline zend_bool Z_BVAL_P(zval *v) { +static sw_inline zend_bool Z_BVAL_P(const zval *v) { return Z_TYPE_P(v) == IS_TRUE; } -#endif -#ifndef ZVAL_IS_ARRAY -static sw_inline zend_bool ZVAL_IS_ARRAY(zval *v) { +static sw_inline zend_bool ZVAL_IS_ARRAY(const zval *v) { return Z_TYPE_P(v) == IS_ARRAY; } -#endif -#ifndef ZVAL_IS_OBJECT -static sw_inline zend_bool ZVAL_IS_OBJECT(zval *v) { +static sw_inline zend_bool ZVAL_IS_REF(const zval *v) { + return Z_TYPE_P(v) == IS_REFERENCE; +} + +static sw_inline zend_bool ZVAL_IS_OBJECT(const zval *v) { return Z_TYPE_P(v) == IS_OBJECT; } -#endif static sw_inline zval *sw_malloc_zval() { - return (zval *) emalloc(sizeof(zval)); + return static_cast(emalloc(sizeof(zval))); } static sw_inline zval *sw_zval_dup(zval *val) { diff --git a/ext-src/php_swoole_stdext.h b/ext-src/php_swoole_stdext.h new file mode 100644 index 00000000000..58c93488aba --- /dev/null +++ b/ext-src/php_swoole_stdext.h @@ -0,0 +1,45 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2015 The Swoole Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.0 of the Apache license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.apache.org/licenses/LICENSE-2.0.html | + | If you did not receive a copy of the Apache2.0 license and are unable| + | to obtain it through the world-wide-web, please send a note to | + | license@swoole.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + */ + +#pragma once + +#include "php_swoole_cxx.h" + +SW_EXTERN_C_BEGIN +PHP_FUNCTION(swoole_call_array_method); +PHP_FUNCTION(swoole_call_string_method); +PHP_FUNCTION(swoole_call_stream_method); +PHP_FUNCTION(swoole_array_search); +PHP_FUNCTION(swoole_array_contains); +PHP_FUNCTION(swoole_array_join); +PHP_FUNCTION(swoole_array_key_exists); +PHP_FUNCTION(swoole_array_map); +PHP_FUNCTION(swoole_array_is_typed); +PHP_FUNCTION(swoole_array_is_empty); +PHP_FUNCTION(swoole_str_split); +PHP_FUNCTION(swoole_str_is_empty); +PHP_FUNCTION(swoole_str_match); +PHP_FUNCTION(swoole_str_match_all); +PHP_FUNCTION(swoole_str_json_decode); +PHP_FUNCTION(swoole_str_json_decode_to_object); +PHP_FUNCTION(swoole_parse_str); +PHP_FUNCTION(swoole_hash); +PHP_FUNCTION(swoole_typed_array); +PHP_FUNCTION(swoole_str_replace); +PHP_FUNCTION(swoole_str_ireplace); +PHP_FUNCTION(swoole_array_replace_str); +PHP_FUNCTION(swoole_array_ireplace_str); +SW_EXTERN_C_END diff --git a/ext-src/stubs/php_swoole_stdext.stub.php b/ext-src/stubs/php_swoole_stdext.stub.php new file mode 100644 index 00000000000..9cec3186c3b --- /dev/null +++ b/ext-src/stubs/php_swoole_stdext.stub.php @@ -0,0 +1,54 @@ + | + +----------------------------------------------------------------------+ + */ +#include "php_swoole_private.h" + +#ifdef SW_STDEXT +#include "php_swoole_stdext.h" +#include "php_swoole_cxx.h" +#include "php_variables.h" +#include "nlohmann/detail/value_t.hpp" + +SW_EXTERN_C_BEGIN +#include "ext/pcre/php_pcre.h" +#include "ext/json/php_json.h" +#include "thirdparty/php/zend/zend_execute.c" +SW_EXTERN_C_END + +enum HashFlag { + HASH_FLAG_TYPED_ARRAY = (1 << 12), +}; + +/** + * This module aims to enhance the PHP standard library without modifying the php-src core code. + * It seeks to introduce strongly-typed arrays and enable the use of built-in methods directly on arrays and strings, + * instead of relying on array_* or str_* functions. + */ + +struct CallInfo { + zval func; + zval this_; + uint8_t op1_type; +}; + +struct ArrayTypeValue { + uint8_t type_of_value; + uint8_t type_of_key; + uint16_t offset_of_value_type_str; + uint16_t len_of_value_type_str; + + bool parse(const char *type_str, size_t len_of_type_str); +}; + +struct ArrayTypeInfo { + ArrayTypeValue self; + ArrayTypeValue element; + zend_class_entry *value_ce; + uint16_t len_of_type_str; + char type_str[0]; + + bool parse(zend_string *type_def); + bool equals(const ArrayTypeInfo *other) const { + return self.type_of_key == other->self.type_of_key && self.type_of_value == other->self.type_of_value && + len_of_type_str == other->len_of_type_str && memcmp(type_str, other->type_str, len_of_type_str) == 0; + } + bool element_type_equals(const ArrayTypeInfo *element_array_type_info) const { + return element_array_type_info->get_type_of_key() == element.type_of_key && + element_array_type_info->get_type_of_value() == element.type_of_value && + element_array_type_info->len_of_type_str == get_len_of_value_type_str() && + memcmp(element_array_type_info->type_str, get_value_type_str(), get_len_of_value_type_str()) == 0; + } + const char *get_value_type_str() const { + return type_str + self.offset_of_value_type_str; + } + uint16_t get_len_of_type_str() const { + return len_of_type_str; + } + uint16_t get_len_of_value_type_str() const { + return self.len_of_value_type_str; + } + uint8_t get_type_of_key() const { + return self.type_of_key; + } + uint8_t get_type_of_value() const { + return self.type_of_value; + } + bool is_list() const { + return self.type_of_key == 0; + } + bool value_is_bool() const { + return self.type_of_value == IS_TRUE || self.type_of_value == IS_FALSE; + } + bool value_is_object() const { + return self.type_of_value == IS_OBJECT; + } + bool value_is_array() const { + return self.type_of_value == IS_ARRAY; + } + bool value_is_string() const { + return self.type_of_value == IS_STRING; + } + bool value_is_numeric() const { + return self.type_of_value == IS_LONG || self.type_of_value == IS_DOUBLE; + } + bool instance_of(const zval *value) const { + return instanceof_function(Z_OBJCE_P(value), value_ce); + } + bool check(const zend_array *ht, const zval *key, const zval *value) const; + ArrayTypeInfo *dup() const { + const auto copy = static_cast(emalloc(sizeof(ArrayTypeInfo) + get_len_of_type_str() + 1)); + memcpy(copy, this, sizeof(ArrayTypeInfo) + get_len_of_type_str() + 1); + return copy; + } +}; + +static zend_function *fn_swoole_call_array_method = nullptr; +static zend_function *fn_swoole_call_string_method = nullptr; +static zend_function *fn_swoole_call_stream_method = nullptr; +static zend_function *fn_array_push = nullptr; +static zend_function *fn_array_unshift = nullptr; +static zend_function *fn_array_splice = nullptr; +static zif_handler ori_handler_array_push; +static zif_handler ori_handler_array_unshift; +static zif_handler ori_handler_array_splice; + +static int opcode_handler_array_assign(zend_execute_data *execute_data); +static int opcode_handler_array_assign_op(zend_execute_data *execute_data); +static int opcode_handler_array_unset(zend_execute_data *execute_data); +static int opcode_handler_foreach_begin(zend_execute_data *execute_data); +static int opcode_handler_method_call(zend_execute_data *execute_data); +static ArrayTypeInfo *get_type_info(zend_array *array); + +static PHP_FUNCTION(swoole_array_push); +static PHP_FUNCTION(swoole_array_unshift); +static PHP_FUNCTION(swoole_array_splice); + +static zend_function *get_function(const zend_array *function_table, const char *name, size_t name_len) { + return static_cast(zend_hash_str_find_ptr(function_table, name, name_len)); +} + +static bool is_typed_array(const zval *zval) { + return HT_FLAGS(Z_ARRVAL_P(zval)) & HASH_FLAG_TYPED_ARRAY; +} + +static std::unordered_map array_methods = { + {"all", "array_all"}, + {"any", "array_any"}, + {"changeKeyCase", "array_change_key_case"}, + {"chunk", "array_chunk"}, + {"column", "array_column"}, + {"countValues", "array_count_values"}, + {"diff", "array_diff"}, + {"diffAssoc", "array_diff_assoc"}, + {"diffKey", "array_diff_key"}, + {"filter", "array_filter"}, + {"find", "array_find"}, + {"flip", "array_flip"}, + {"intersect", "array_intersect"}, + {"intersectAssoc", "array_intersect_assoc"}, + {"isList", "array_is_list"}, + {"keyExists", "swoole_array_key_exists"}, + {"keyFirst", "array_key_first"}, + {"keyLast", "array_key_last"}, + {"keys", "array_keys"}, + {"map", "swoole_array_map"}, + {"pad", "array_pad"}, + {"product", "array_product"}, + {"rand", "array_rand"}, + {"reduce", "array_reduce"}, + {"replace", "array_replace"}, + {"reverse", "array_reverse"}, + {"search", "swoole_array_search"}, + {"slice", "array_slice"}, + {"sum", "array_sum"}, + {"unique", "array_unique"}, + {"values", "array_values"}, + {"count", "count"}, + {"merge", "array_merge"}, + {"contains", "swoole_array_contains"}, + {"join", "swoole_array_join"}, + {"isTyped", "swoole_array_is_typed"}, + {"isEmpty", "swoole_array_is_empty"}, + // pass by ref + {"sort", "sort"}, + {"pop", "array_pop"}, + {"push", "array_push"}, + {"shift", "array_shift"}, + {"unshift", "array_unshift"}, + {"splice", "array_splice"}, + {"walk", "array_walk"}, + {"replaceStr", "swoole_array_replace_str"}, + {"iReplaceStr", "swoole_array_ireplace_str"}, + // serialize + {"serialize", "serialize"}, + {"marshal", "serialize"}, + {"jsonEncode", "json_encode"}, +}; + +/** + * i=ignore case, l=left, r=right + */ +static std::unordered_map string_methods = { + {"length", "strlen"}, + {"isEmpty", "swoole_str_is_empty"}, + {"lower", "strtolower"}, + {"upper", "strtoupper"}, + {"lowerFirst", "lcfirst"}, + {"upperFirst", "ucfirst"}, + {"upperWords", "ucwords"}, + {"addCSlashes", "addcslashes"}, + {"addSlashes", "addslashes"}, + {"chunkSplit", "chunk_split"}, + {"countChars", "count_chars"}, + {"htmlEntityDecode", "html_entity_decode"}, + {"htmlEntityEncode", "htmlentities"}, + {"htmlSpecialCharsEncode", "htmlspecialchars"}, + {"htmlSpecialCharsDecode", "htmlspecialchars_decode"}, + {"trim", "trim"}, + {"lTrim", "ltrim"}, + {"rTrim", "rtrim"}, + {"parseStr", "swoole_parse_str"}, + {"parseUrl", "parse_url"}, + {"contains", "str_contains"}, + {"incr", "str_increment"}, + {"decr", "str_decrement"}, + {"pad", "str_pad"}, + {"repeat", "str_repeat"}, + {"replace", "swoole_str_replace"}, + {"iReplace", "swoole_str_ireplace"}, + {"shuffle", "str_shuffle"}, + {"split", "swoole_str_split"}, // explode + {"startsWith", "str_starts_with"}, + {"endsWith", "str_ends_with"}, + {"wordCount", "str_word_count"}, + {"iCompare", "strcasecmp"}, + {"compare", "strcmp"}, + {"find", "strstr"}, + {"iFind", "stristr"}, + {"stripTags", "strip_tags"}, + {"stripCSlashes", "stripcslashes"}, + {"stripSlashes", "stripslashes"}, + {"iIndexOf", "stripos"}, + {"indexOf", "strpos"}, + {"lastIndexOf", "strrpos"}, + {"iLastIndexOf", "strripos"}, + {"lastCharIndexOf", "strrchr"}, + {"substr", "substr"}, + {"substrCompare", "substr_compare"}, + {"substrCount", "substr_count"}, + {"substrReplace", "substr_replace"}, + {"reverse", "strrev"}, + {"md5", "md5"}, + {"sha1", "sha1"}, + {"crc32", "crc32"}, + {"hash", "swoole_hash"}, + {"hashCode", "swoole_hashcode"}, + {"base64Decode", "base64_decode"}, + {"base64Encode", "base64_encode"}, + {"urlDecode", "urldecode"}, + {"urlEncode", "urlencode"}, + {"rawUrlEncode", "rawurlencode"}, + {"rawUrlDecode", "rawurldecode"}, + {"match", "swoole_str_match"}, + {"matchAll", "swoole_str_match_all"}, + {"isNumeric", "is_numeric"}, + // mbstring + {"mbUpperFirst", "mb_ucfirst"}, + {"mbLowerFirst", "mb_lcfirst"}, + {"mbTrim", "mb_trim"}, + {"mbSubstrCount", "mb_substr_count"}, + {"mbSubstr", "mb_substr"}, + {"mbUpper", "mb_strtoupper"}, + {"mbLower", "mb_strtolower"}, + {"mbFind", "mb_strstr"}, + {"mbIndexOf", "mb_strpos"}, + {"mbLastIndexOf", "mb_strrpos"}, + {"mbILastIndexOf", "mb_strripos"}, + {"mbLastCharIndexOf", "mb_strrchr"}, + {"mbILastCharIndex", "mb_strrichr"}, + {"mbLength", "mb_strlen"}, + {"mbIFind", "mb_stristr"}, + {"mbIIndexOf", "mb_stripos"}, + {"mbCut", "mb_strcut"}, + {"mbRTrim", "mb_rtrim"}, + {"mbLTrim", "mb_ltrim"}, + {"mbDetectEncoding", "mb_detect_encoding"}, + {"mbConvertEncoding", "mb_convert_encoding"}, + {"mbConvertCase", "mb_convert_case"}, + // serialize + {"unserialize", "unserialize"}, + {"unmarshal", "unserialize"}, + {"jsonDecode", "swoole_str_json_decode"}, + {"jsonDecodeToObject", "swoole_str_json_decode_to_object"}, +}; + +static std::unordered_map stream_methods = { + {"write", "fwrite"}, + {"read", "fread"}, + {"close", "fclose"}, + {"dataSync", "fdatasync"}, + {"sync", "fsync"}, + {"truncate", "ftruncate"}, + {"stat", "fstat"}, + {"seek", "fseek"}, + {"tell", "ftell"}, + {"lock", "flock"}, + {"eof", "feof"}, + {"getChar", "fgetc"}, + {"getLine", "fgets"}, +}; + +static void move_first_element(const zval src[], zval dst[], int size, int position) { + zval first = src[0]; + for (int i = 0; i < position; i++) { + dst[i] = src[i + 1]; + } + dst[position] = first; + for (int i = position + 1; i < size; i++) { + dst[i] = src[i]; + } +} + +static void call_func_move_first_arg(zend_function *fn, zend_execute_data *execute_data, zval *retval, int position) { + const zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + const auto argv = static_cast(ecalloc(arg_count, sizeof(zval))); + move_first_element(arg_ptr, argv, arg_count, position); + zend_call_known_function(fn, nullptr, nullptr, retval, arg_count, argv, nullptr); + efree(argv); +} + +static void call_method(const std::unordered_map &method_map, + zend_execute_data *execute_data, + zval *retval) { + const auto call_info = reinterpret_cast(execute_data->run_time_cache); + const auto name = std::string(Z_STRVAL(call_info->func), Z_STRLEN(call_info->func)); + const auto iter = method_map.find(name); + + ON_SCOPE_EXIT { + efree(call_info); + execute_data->run_time_cache = nullptr; + }; + + if (iter == method_map.end()) { + zend_throw_error( + nullptr, "The method `%s` is undefined on %s", name.c_str(), zend_zval_type_name(&call_info->this_)); + return; + } + const auto real_fn = iter->second; + const auto fn = get_function(EG(function_table), real_fn.c_str(), real_fn.length()); + if (!fn) { + zend_throw_error(nullptr, "The function `%s` is undefined", real_fn.c_str()); + return; + } + + const zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + const auto argv = static_cast(ecalloc(arg_count + 1, sizeof(zval))); + + argv[0] = call_info->this_; + for (int i = 0; i < arg_count; i++) { + argv[i + 1] = arg_ptr[i]; + } + + zend_call_known_function(fn, nullptr, nullptr, retval, arg_count + 1, argv, nullptr); + if (call_info->op1_type == IS_VAR) { + zval_ptr_dtor(&call_info->this_); + } + efree(argv); +} + +static int opcode_handler_method_call(zend_execute_data *execute_data) { + const zend_op *opline = EX(opline); + zval *object; + if (opline->op1_type == IS_CONST) { + object = RT_CONSTANT(opline, opline->op1); + } else if (UNEXPECTED(opline->op1_type == IS_UNUSED)) { + return ZEND_USER_OPCODE_DISPATCH; + } else { + object = EX_VAR(opline->op1.var); + } + + auto type = Z_TYPE_P(object); + if (type == IS_REFERENCE) { + type = Z_TYPE_P(Z_REFVAL_P(object)); + } + + if (type == IS_ARRAY || type == IS_STRING || type == IS_RESOURCE) { + auto call_info = static_cast(emalloc(sizeof(CallInfo))); + call_info->func = *RT_CONSTANT(opline, opline->op2); + call_info->this_ = *object; + call_info->op1_type = opline->op1_type; + + zend_function *fbc = nullptr; + switch (type) { + case IS_ARRAY: + fbc = fn_swoole_call_array_method; + break; + case IS_STRING: + fbc = fn_swoole_call_string_method; + break; + default: + fbc = fn_swoole_call_stream_method; + } + + zend_execute_data *call = + zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, object); + + call->run_time_cache = reinterpret_cast(call_info); + call->prev_execute_data = EX(call); + EX(call) = call; + EX(opline)++; + + return ZEND_USER_OPCODE_CONTINUE; + } + + return ZEND_USER_OPCODE_DISPATCH; +} + +void php_swoole_stdext_minit(int module_number) { + zend_set_user_opcode_handler(ZEND_INIT_METHOD_CALL, opcode_handler_method_call); + zend_set_user_opcode_handler(ZEND_ASSIGN_DIM, opcode_handler_array_assign); + zend_set_user_opcode_handler(ZEND_ASSIGN_DIM_OP, opcode_handler_array_assign_op); + zend_set_user_opcode_handler(ZEND_UNSET_DIM, opcode_handler_array_unset); + zend_set_user_opcode_handler(ZEND_FE_RESET_RW, opcode_handler_foreach_begin); + + fn_swoole_call_array_method = get_function(CG(function_table), ZEND_STRL("swoole_call_array_method")); + fn_swoole_call_string_method = get_function(CG(function_table), ZEND_STRL("swoole_call_string_method")); + fn_swoole_call_stream_method = get_function(CG(function_table), ZEND_STRL("swoole_call_stream_method")); + + fn_array_push = get_function(CG(function_table), ZEND_STRL("array_push")); + fn_array_unshift = get_function(CG(function_table), ZEND_STRL("array_unshift")); + fn_array_splice = get_function(CG(function_table), ZEND_STRL("array_splice")); + + ori_handler_array_push = fn_array_push->internal_function.handler; + fn_array_push->internal_function.handler = ZEND_FN(swoole_array_push); + ori_handler_array_unshift = fn_array_unshift->internal_function.handler; + fn_array_unshift->internal_function.handler = ZEND_FN(swoole_array_unshift); + ori_handler_array_splice = fn_array_splice->internal_function.handler; + fn_array_splice->internal_function.handler = ZEND_FN(swoole_array_splice); +} + +#define SW_CREATE_PHP_FUNCTION_WRAPPER(php_func_name, swoole_func_name, position) \ + PHP_FUNCTION(swoole_func_name) { \ + static zend_function *fn_##swoole_func_name = nullptr; \ + if (!fn_##swoole_func_name) { \ + fn_##swoole_func_name = get_function(CG(function_table), ZEND_STRL(#php_func_name)); \ + } \ + call_func_move_first_arg(fn_##swoole_func_name, execute_data, return_value, position); \ + } + +// array +SW_CREATE_PHP_FUNCTION_WRAPPER(array_search, swoole_array_search, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(in_array, swoole_array_contains, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(implode, swoole_array_join, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(array_key_exists, swoole_array_key_exists, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(array_map, swoole_array_map, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(str_replace, swoole_array_replace_str, 2); +SW_CREATE_PHP_FUNCTION_WRAPPER(str_ireplace, swoole_array_ireplace_str, 2); + +// string +SW_CREATE_PHP_FUNCTION_WRAPPER(explode, swoole_str_split, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(hash, swoole_hash, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(str_replace, swoole_str_replace, 2); +SW_CREATE_PHP_FUNCTION_WRAPPER(str_ireplace, swoole_str_ireplace, 2); + +PHP_FUNCTION(swoole_parse_str) { + char *arg; + size_t arglen; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(arg, arglen) + ZEND_PARSE_PARAMETERS_END(); + + array_init(return_value); + auto res = estrndup(arg, arglen); + sapi_module.treat_data(PARSE_STRING, res, return_value); +} + +PHP_FUNCTION(swoole_str_is_empty) { + zend_string *str; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + RETURN_BOOL(str->len == 0); +} + +PHP_FUNCTION(swoole_array_is_empty) { + zval *array; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(array) + ZEND_PARSE_PARAMETERS_END(); + RETURN_BOOL(zend_array_count(Z_ARRVAL_P(array)) == 0); +} + +PHP_FUNCTION(swoole_call_array_method) { + call_method(array_methods, execute_data, return_value); +} + +PHP_FUNCTION(swoole_call_string_method) { + call_method(string_methods, execute_data, return_value); +} + +PHP_FUNCTION(swoole_call_stream_method) { + call_method(stream_methods, execute_data, return_value); +} + +static HashTable *make_typed_array(const uint32_t nSize, const uint32_t nTypeStr) { + const auto ht = static_cast(emalloc(sizeof(HashTable) + sizeof(ArrayTypeInfo) + nTypeStr + 1)); + _zend_hash_init(ht, nSize, ZVAL_PTR_DTOR, false); + HT_FLAGS(ht) |= HASH_FLAG_TYPED_ARRAY; + return ht; +} + +void copy_array_type_info(zval *container, zend_array *src) { + auto src_type_info = get_type_info(src); + zend_array *ht = Z_ARRVAL_P(container); + auto extra_size = sizeof(ArrayTypeInfo) + src_type_info->get_len_of_type_str() + 1; + const auto tmp = static_cast(emalloc(sizeof(HashTable) + extra_size)); + memcpy(tmp, ht, sizeof(HashTable)); + memcpy(reinterpret_cast(tmp) + sizeof(HashTable), src_type_info, extra_size); + HT_FLAGS(tmp) |= HASH_FLAG_TYPED_ARRAY; + Z_ARRVAL_P(container) = tmp; + efree(ht); +} + +static ArrayTypeInfo *get_type_info(zend_array *array) { + return reinterpret_cast(reinterpret_cast(array) + sizeof(HashTable)); +} + +static zend_string *get_array_type_def(const ArrayTypeInfo *info) { + zend_string *result = zend_string_alloc(info->get_len_of_value_type_str() + 16, false); + char *p = result->val; + *p = '<'; + if (info->get_type_of_key() == IS_STRING) { + p++; + strcpy(p, "string,"); + p += 7; + } else if (info->get_type_of_key() == IS_LONG) { + p++; + strcpy(p, "int,"); + p += 4; + } + + memcpy(p, info->get_value_type_str(), info->get_len_of_value_type_str()); + p += info->get_len_of_value_type_str(); + *p = '>'; + p++; + *p = '\0'; + result->len = p - result->val; + + return result; +} + +bool ArrayTypeInfo::check(const zend_array *ht, const zval *key, const zval *value) const { + if (get_type_of_key() > 0) { + if (Z_TYPE_P(key) != get_type_of_key()) { + zend_type_error("Array key type mismatch, expected `%s`, got `%s`", + zend_get_type_by_const(get_type_of_key()), + zend_get_type_by_const(Z_TYPE_P(key))); + return false; + } + } else { + if (Z_TYPE_P(key) == IS_LONG) { + if (Z_LVAL_P(key) > zend_hash_num_elements(ht)) { + zend_throw_error( + nullptr, "Incorrect array key `%ld`, out of the permitted range", (long) Z_LVAL_P(key)); + return false; + } + } else if (!(Z_TYPE_P(key) == IS_UNDEF || Z_TYPE_P(key) == IS_NULL)) { + zend_throw_error(nullptr, "Incorrect array key, must be undef or int"); + return false; + } + } + ZVAL_DEREF(value); + if (value_is_bool() && ZVAL_IS_BOOL(value)) { + return true; + } + if (Z_TYPE_P(value) != get_type_of_value()) { + zend_type_error("Array value type mismatch, expected `%s`, got `%s`", + zend_get_type_by_const(get_type_of_value()), + zend_get_type_by_const(Z_TYPE_P(value))); + return false; + } + if (value_is_object() && !instance_of(value)) { + zend_type_error( + "Array value type mismatch, expected `%s`, got `%s`", value_ce->name->val, Z_OBJCE_P(value)->name->val); + return false; + } + if (value_is_array()) { + const auto element_array_type_info = get_type_info(Z_ARRVAL_P(value)); + const auto element_ht = Z_ARRVAL_P(value); + if (!(HT_FLAGS(element_ht) & HASH_FLAG_TYPED_ARRAY)) { + zend_type_error("Array value type mismatch, expected `%.*s`, got `array`", + get_len_of_value_type_str(), + get_value_type_str()); + return false; + } + if (!element_type_equals(element_array_type_info)) { + const auto element_type_str = get_array_type_def(element_array_type_info); + zend_type_error("Array value type mismatch, expected `%.*s`, got `%.*s`", + get_len_of_value_type_str(), + get_value_type_str(), + (int) ZSTR_LEN(element_type_str), + ZSTR_VAL(element_type_str)); + zend_string_release(element_type_str); + return false; + } + } + return true; +} + +static zval *get_array_on_opline(const zend_op *opline EXECUTE_DATA_DC) { + auto array = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); + if (ZVAL_IS_REF(array)) { + array = Z_REFVAL_P(array); + } + if (!ZVAL_IS_ARRAY(array)) { + return nullptr; + } + return array; +} + +#ifdef DEBUG +static void debug_val(const char *tag, int op_type, zval *value) { + printf("[%s] refcount=%d, op1_type=%d, type=%s, refcounted=%d\n", + tag, + Z_REFCOUNTED_P(value) ? Z_REFCOUNT_P(value) : 0, + op_type, + zend_get_type_by_const(Z_TYPE_P(value)), + Z_REFCOUNTED_P(value)); +} +#else +#define debug_val(tag, op_type, value) +#endif + +static void array_add_or_update(const zend_op *opline, zval *container, const zval *key, zval *value EXECUTE_DATA_DC) { + zval *var_ptr; + HashTable *source = Z_ARRVAL_P(container); + SEPARATE_ARRAY(container); + if (source != Z_ARRVAL_P(container)) { + copy_array_type_info(container, source); + } + HashTable *ht = Z_ARRVAL_P(container); + const zend_op *op_data = opline + 1; + + if (ZVAL_IS_NULL(key)) { + var_ptr = zend_hash_next_index_insert(ht, value); + if (UNEXPECTED(!var_ptr)) { + zend_cannot_add_element(); + goto assign_dim_op_ret_null; + } + } else { + zval *variable_ptr; + if (opline->op2_type == IS_CONST) { + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); + } else { + variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); + } + if (UNEXPECTED(variable_ptr == nullptr)) { + goto assign_dim_op_ret_null; + } + debug_val("1", op_data->op1_type, value); + var_ptr = zend_assign_to_variable(variable_ptr, value, op_data->op1_type, EX_USES_STRICT_TYPES()); + debug_val("2", op_data->op1_type, value); + if (UNEXPECTED(!var_ptr)) { + assign_dim_op_ret_null: + FREE_OP(op_data->op1_type, op_data->op1.var); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + return; + } + } + debug_val("3", op_data->op1_type, value); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); + } + if (op_data->op1_type == IS_VAR) { + Z_TRY_ADDREF_P(value); + } + FREE_OP(op_data->op1_type, op_data->op1.var); + debug_val("4", op_data->op1_type, value); +} + +static void array_op(const zend_op *opline, zval *container, const zval *key, zval *value EXECUTE_DATA_DC) { + HashTable *source = Z_ARRVAL_P(container); + SEPARATE_ARRAY(container); + if (source != Z_ARRVAL_P(container)) { + copy_array_type_info(container, source); + } + const auto type_info = get_type_info(Z_ARRVAL_P(container)); + + zval *variable_ptr; + if ((opline + 1)->op1_type == IS_CONST) { + variable_ptr = zend_fetch_dimension_address_inner_RW_CONST(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); + } else { + variable_ptr = zend_fetch_dimension_address_inner_RW(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); + } + if (UNEXPECTED(variable_ptr == nullptr)) { + assign_dim_op_ret_null: + FREE_OP((opline + 1)->op1_type, (opline + 1)->op1.var); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + return; + } + do { + if (UNEXPECTED(Z_ISREF_P(variable_ptr))) { + zend_reference *ref = Z_REF_P(variable_ptr); + variable_ptr = Z_REFVAL_P(variable_ptr); + if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(ref))) { + zend_binary_assign_op_typed_ref(ref, value OPLINE_CC EXECUTE_DATA_CC); + break; + } + } + const auto opcode = opline->extended_value; + if (opcode == ZEND_CONCAT) { + if (!type_info->value_is_string()) { + zend_type_error("Only string support concat operation"); + goto assign_dim_op_ret_null; + } + } else { + if (!type_info->value_is_numeric()) { + zend_type_error("Only int or float support arithmetic operation"); + goto assign_dim_op_ret_null; + } + } + zend_binary_op(variable_ptr, variable_ptr, value OPLINE_CC); + } while (false); + + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_COPY(EX_VAR(opline->result.var), variable_ptr); + } + FREE_OP((opline + 1)->op1_type, (opline + 1)->op1.var); +} + +typedef std::function ArrayFn; + +static int opcode_handler_array(zend_execute_data *execute_data, const ArrayFn &fn) { + const zend_op *opline = EX(opline); + const zend_op *op_data = opline + 1; + zval *array = get_array_on_opline(opline EXECUTE_DATA_CC); + if (UNEXPECTED(!array)) { + return ZEND_USER_OPCODE_DISPATCH; + } + zend_array *ht = Z_ARRVAL_P(array); + if (!(HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY)) { + return ZEND_USER_OPCODE_DISPATCH; + } + const auto value = get_op_data_zval_ptr_r(op_data->op1_type, op_data->op1); + zval *key; + if (opline->op2_type == IS_CONST) { + key = RT_CONSTANT(opline, opline->op2); + } else if (UNEXPECTED(opline->op2_type == IS_UNUSED)) { + key = &EG(uninitialized_zval); + } else { + key = EX_VAR(opline->op2.var); + } + const auto type_info = get_type_info(ht); + if (!type_info->check(ht, key, value)) { + FREE_OP(op_data->op1_type, op_data->op1.var); + return ZEND_USER_OPCODE_CONTINUE; + } + fn(opline, array, key, value EXECUTE_DATA_CC); + EX(opline) += 2; + return ZEND_USER_OPCODE_CONTINUE; +} + +static int opcode_handler_array_assign(zend_execute_data *execute_data) { + return opcode_handler_array(execute_data, array_add_or_update); +} + +static int opcode_handler_array_assign_op(zend_execute_data *execute_data) { + return opcode_handler_array(execute_data, array_op); +} + +static int opcode_handler_foreach_begin(zend_execute_data *execute_data) { + const zend_op *opline = EX(opline); + zval *array; + if (opline->op1_type == IS_VAR || opline->op1_type == IS_CV) { + array = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); + } else if (opline->op1_type == IS_CONST) { + array = RT_CONSTANT(opline, opline->op1); + } else { + array = _get_zval_ptr_tmp(opline->op1.var EXECUTE_DATA_CC); + } + ZVAL_DEREF(array); + if (UNEXPECTED(!array || !ZVAL_IS_ARRAY(array))) { + return ZEND_USER_OPCODE_DISPATCH; + } + const zend_array *ht = Z_ARRVAL_P(array); + if (HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY) { + zend_throw_error(nullptr, "The type array do not support using references for element value during iteration"); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + Z_FE_ITER_P(EX_VAR(opline->result.var)) = static_cast(-1); + + return ZEND_USER_OPCODE_CONTINUE; + } + return ZEND_USER_OPCODE_DISPATCH; +} + +static int opcode_handler_array_unset(zend_execute_data *execute_data) { + const zend_op *opline = EX(opline); + const zval *array = get_array_on_opline(opline EXECUTE_DATA_CC); + if (!array) { + return ZEND_USER_OPCODE_DISPATCH; + } + zend_array *ht = Z_ARRVAL_P(array); + if (!(HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY)) { + return ZEND_USER_OPCODE_DISPATCH; + } + const auto type_info = get_type_info(ht); + if (type_info->is_list()) { + zend_throw_error(nullptr, "The typed array list do not support random deletion of elements"); + return ZEND_USER_OPCODE_CONTINUE; + } + return ZEND_USER_OPCODE_DISPATCH; +} + +static void remove_all_spaces(char **val, uint16_t *len) { + if (!*val || *len == 0) { + return; + } + + const char *src = *val; + char *dst = *val; + size_t new_len = 0; + + for (size_t i = 0; i < *len; i++) { + if (!isspace((uchar) *src)) { + *dst = *src; + dst++; + new_len++; + } + src++; + } + + *len = new_len; +} + +static int8_t get_type(const char *val, size_t len) { + if (SW_STRCASEEQ(val, len, "int")) { + return IS_LONG; + } else if (SW_STRCASEEQ(val, len, "float")) { + return IS_DOUBLE; + } else if (SW_STRCASEEQ(val, len, "string")) { + return IS_STRING; + } else if (SW_STRCASEEQ(val, len, "bool")) { + return IS_TRUE; // IS_TRUE or IS_FALSE + } else if (val[0] == '<' && val[len - 1] == '>') { + return IS_ARRAY; + } else if (SW_STRCASEEQ(val, len, "resource")) { + return IS_RESOURCE; + } else if (SW_STRCASEEQ(val, len, "null")) { + return IS_NULL; + } else { + return IS_OBJECT; + } +} + +bool ArrayTypeValue::parse(const char *type_str, const size_t len_of_type_str) { + auto pos = strchr(type_str, ','); + if (pos == nullptr) { + type_of_key = 0; + offset_of_value_type_str = 1; + } else { + type_of_key = get_type(type_str + 1, pos - type_str - 1); + if (type_of_key != IS_STRING && type_of_key != IS_LONG) { + zend_throw_error(nullptr, "The key type of array must be string or int, but got %s", pos + 1); + return false; + } + offset_of_value_type_str = pos - type_str + 1; + } + len_of_value_type_str = len_of_type_str - offset_of_value_type_str - 1; + type_of_value = get_type(type_str + offset_of_value_type_str, len_of_value_type_str); + return true; +} + +bool ArrayTypeInfo::parse(zend_string *type_def) { + if (type_def->len >= 65535) { + zend_throw_error(nullptr, "The type definition string is too long (must be less than 65535 characters)"); + return false; + } + zend_string *lc_type_def = zend_string_tolower(type_def); + memcpy(type_str, lc_type_def->val, lc_type_def->len + 1); + len_of_type_str = lc_type_def->len; + zend_string_release(lc_type_def); + + char *tmp_type_str = type_str; + remove_all_spaces(&tmp_type_str, &len_of_type_str); + tmp_type_str[len_of_type_str] = '\0'; + if (tmp_type_str != type_str) { + memmove(type_str, tmp_type_str, len_of_type_str + 1); + } + + if (type_str[0] != '<' || type_str[len_of_type_str - 1] != '>') { + zend_throw_error(nullptr, "The type definition of typed array must start with '<' and end with '>'"); + return false; + } + if (!self.parse(type_str, len_of_type_str)) { + return false; + } + + if (self.type_of_value == IS_OBJECT) { + zend::String type_str_of_value(type_str + self.offset_of_value_type_str, self.len_of_value_type_str); + value_ce = zend_lookup_class(type_str_of_value.get()); + if (!value_ce) { + zend_throw_error(nullptr, "Class '%s' not found", type_str_of_value.val()); + return false; + } + } + + return true; +} + +PHP_FUNCTION(swoole_typed_array) { + zend_string *type_def; + zval *init_values = nullptr; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(type_def) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(init_values) + ZEND_PARSE_PARAMETERS_END(); + + auto tmp_info = static_cast(emalloc(sizeof(ArrayTypeInfo) + ZSTR_LEN(type_def) + 1)); + if (!tmp_info->parse(type_def)) { + efree(tmp_info); + RETURN_NULL(); + } + + if (init_values && is_typed_array(init_values)) { + auto type_info = get_type_info(Z_ARRVAL_P(init_values)); + if (tmp_info->equals(type_info)) { + ZVAL_COPY(return_value, init_values); + } else { + zend_throw_error(nullptr, "The type definition of the typed array does not match the initial values"); + } + efree(tmp_info); + return; + } + + auto n = init_values ? zend_array_count(Z_ARRVAL_P(init_values)) : 0; + auto array = make_typed_array(n, tmp_info->len_of_type_str); + ZVAL_ARR(return_value, array); + auto info = get_type_info(array); + memcpy(info, tmp_info, sizeof(ArrayTypeInfo) + tmp_info->len_of_type_str + 1); + efree(tmp_info); + + if (info->self.type_of_value == IS_ARRAY) { + if (!info->element.parse(info->get_value_type_str(), info->get_len_of_value_type_str())) { + zval_ptr_dtor(return_value); + RETURN_NULL(); + } + } + + if (init_values) { + zend_string *str_key; + zend_ulong num_key; + zval *zv; + zval zk; + HashTable *ht = Z_ARRVAL_P(init_values); + + ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) { + if (str_key) { + ZVAL_STR(&zk, str_key); + } else { + ZVAL_LONG(&zk, num_key); + } + if (!info->check(array, &zk, zv)) { + zval_ptr_dtor(return_value); + RETURN_NULL(); + } + Z_TRY_ADDREF_P(zv); + if (str_key) { + zend_hash_add(array, str_key, zv); + } else { + zend_hash_index_add(array, num_key, zv); + } + } + ZEND_HASH_FOREACH_END(); + } +} + +PHP_FUNCTION(swoole_array_is_typed) { + zend_string *type_def = nullptr; + zval *array; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ARRAY(array) + Z_PARAM_OPTIONAL + Z_PARAM_STR(type_def) + ZEND_PARSE_PARAMETERS_END(); + + HashTable *ht = Z_ARRVAL_P(array); + if (!(HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY)) { + RETURN_FALSE; + } + if (!type_def) { + RETURN_TRUE; + } + + const auto tmp_info = static_cast(emalloc(sizeof(ArrayTypeInfo) + ZSTR_LEN(type_def) + 1)); + if (!tmp_info->parse(type_def)) { + efree(tmp_info); + RETURN_FALSE; + } + + const auto info = get_type_info(ht); + RETVAL_BOOL(info->equals(tmp_info)); + efree(tmp_info); +} + +static PHP_FUNCTION(swoole_array_push) { + zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + zval *array = &arg_ptr[0]; + ZVAL_DEREF(array); + + if (Z_TYPE_P(array) == IS_ARRAY && is_typed_array(array)) { + auto source = Z_ARRVAL_P(array); + auto type_info = get_type_info(source); + for (int i = 1; i < arg_count; i++) { + if (!type_info->check(source, &EG(uninitialized_zval), &arg_ptr[i])) { + return; + } + } + } + ori_handler_array_push(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} + +static PHP_FUNCTION(swoole_array_unshift) { + zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + zval *array = &arg_ptr[0]; + ZVAL_DEREF(array); + + if (Z_TYPE_P(array) == IS_ARRAY && is_typed_array(array)) { + auto source = Z_ARRVAL_P(array); + auto type_info = get_type_info(source); + for (int i = 1; i < arg_count; i++) { + if (!type_info->check(source, &EG(uninitialized_zval), &arg_ptr[i])) { + return; + } + } + ori_handler_array_unshift(INTERNAL_FUNCTION_PARAM_PASSTHRU); + HT_FLAGS(source) |= HASH_FLAG_TYPED_ARRAY; + } else { + ori_handler_array_unshift(INTERNAL_FUNCTION_PARAM_PASSTHRU); + } +} + +static PHP_FUNCTION(swoole_array_splice) { + const zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + const zval *array = &arg_ptr[0]; + ZVAL_DEREF(array); + if (Z_TYPE_P(array) == IS_ARRAY && is_typed_array(array)) { + if (arg_count > 3) { + auto type_info = get_type_info(Z_ARRVAL_P(array)); + auto values = &arg_ptr[3]; + ZVAL_DEREF(values); + if (Z_TYPE_P(values) == IS_ARRAY) { + zval *zv; + HashTable *ht = Z_ARRVAL_P(values); + ZEND_HASH_FOREACH_VAL(ht, zv) { + if (!type_info->check(Z_ARRVAL_P(array), &EG(uninitialized_zval), zv)) { + return; + } + } + ZEND_HASH_FOREACH_END(); + } else { + if (!type_info->check(Z_ARRVAL_P(array), &EG(uninitialized_zval), values)) { + return; + } + } + } + const auto source = Z_ARRVAL_P(array); + ori_handler_array_splice(execute_data, return_value); + HT_FLAGS(source) |= HASH_FLAG_TYPED_ARRAY; + } else { + ori_handler_array_splice(execute_data, return_value); + } +} + +static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, int global) /* {{{ */ +{ + /* parameters */ + zend_string *regex; /* Regular expression */ + zend_string *subject; /* String to match against */ + pcre_cache_entry *pce; /* Compiled regular expression */ + zend_long flags = 0; /* Match control flags */ + zend_long start_offset = 0; /* Where the new search starts */ + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STR(subject) + Z_PARAM_STR(regex) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(flags) + Z_PARAM_LONG(start_offset) + ZEND_PARSE_PARAMETERS_END(); + + /* Compile regex or get it from cache. */ + if ((pce = pcre_get_compiled_regex_cache(regex)) == nullptr) { + RETURN_FALSE; + } + + zval count = {}; + php_pcre_pce_incref(pce); +#if PHP_VERSION_ID >= 80400 + php_pcre_match_impl(pce, subject, &count, return_value, global == 1, flags, start_offset); +#else + php_pcre_match_impl(pce, subject, &count, return_value, global, ZEND_NUM_ARGS() >= 3, flags, start_offset); +#endif + php_pcre_pce_decref(pce); +} +/* }}} */ + +PHP_FUNCTION(swoole_str_match) { + php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} + +PHP_FUNCTION(swoole_str_match_all) { + php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} + +static void php_do_json_decode(INTERNAL_FUNCTION_PARAMETERS, bool assoc) /* {{{ */ +{ + char *str; + size_t str_len; + zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH; + zend_long options = 0; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(depth) + Z_PARAM_LONG(options) + ZEND_PARSE_PARAMETERS_END(); + + if (assoc) { + options |= PHP_JSON_OBJECT_AS_ARRAY; + } else { + options &= ~PHP_JSON_OBJECT_AS_ARRAY; + } + + zend::json_decode(return_value, str, str_len, options, depth); +} +/* }}} */ + +PHP_FUNCTION(swoole_str_json_decode) { + php_do_json_decode(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); +} + +PHP_FUNCTION(swoole_str_json_decode_to_object) { + php_do_json_decode(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); +} +#endif diff --git a/scripts/docker-compile-with-thread.sh b/scripts/docker-compile-with-thread.sh index 4f0f5a6c6e4..5a0bf7f8aa0 100755 --- a/scripts/docker-compile-with-thread.sh +++ b/scripts/docker-compile-with-thread.sh @@ -20,6 +20,7 @@ phpize --enable-mysqlnd \ --enable-swoole-curl \ --enable-cares \ +--enable-swoole-stdext \ --enable-swoole-pgsql \ --enable-swoole-thread \ --with-swoole-odbc=unixODBC,/usr \ diff --git a/scripts/docker-compile.sh b/scripts/docker-compile.sh index 6b5da6bfc19..9df35a7640e 100755 --- a/scripts/docker-compile.sh +++ b/scripts/docker-compile.sh @@ -18,6 +18,7 @@ option="--enable-brotli \ --enable-sockets \ --enable-mysqlnd \ --enable-swoole-curl \ + --enable-swoole-stdext \ --enable-cares \ --enable-swoole-pgsql \ --with-swoole-odbc=unixODBC,/usr \ diff --git a/scripts/format-changed-files.sh b/scripts/format-changed-files.sh index 231c5063427..d5cf4586dbb 100755 --- a/scripts/format-changed-files.sh +++ b/scripts/format-changed-files.sh @@ -5,6 +5,7 @@ __DIR__=$(cd "$(dirname "$0")" || exit;pwd) cpp_files=$(git status --porcelain | grep '^[ M]' | grep '\.\(cc\|cpp\|h\)$' | awk '{print $2}') php_files=$(git status --porcelain | grep '^[ M]' | grep '\.\(php\|phpt\)$' | awk '{print $2}') +arginfo_files=$(git status --porcelain | grep '^[ M]' | grep '_arginfo\.h$' | awk '{print $2}') if [ -z "$cpp_files" ] && [ -z "$php_files" ]; then echo "No files to format." @@ -15,8 +16,11 @@ fi if [ ! -z "$cpp_files" ]; then echo "Formatting C/C++ files..." for file in $cpp_files; do - echo " - $file" - clang-format -i "$file" + # 额外检查确保不处理 _arginfo.h 文件 + if [[ "$file" != *_arginfo.h ]]; then + echo " - $file" + clang-format -i "$file" + fi done fi @@ -29,4 +33,13 @@ if [ ! -z "$php_files" ]; then done fi +# 显示跳过的 _arginfo.h 文件 +if [ ! -z "$arginfo_files" ]; then + echo "Skipped auto-generated files:" + for file in $arginfo_files; do + echo " - $file (auto-generated)" + done +fi + + echo "✅ Formatting completed successfully!" diff --git a/scripts/make.sh b/scripts/make.sh index d548fe9f40a..5f0b013bba5 100755 --- a/scripts/make.sh +++ b/scripts/make.sh @@ -9,6 +9,7 @@ COMPILE_PARAMS="--enable-openssl \ --enable-swoole-curl \ --enable-cares \ --enable-swoole-pgsql \ +--enable-swoole-stdext \ --with-swoole-odbc=unixODBC,/usr \ --enable-swoole-sqlite" diff --git a/tests/swoole_stdext/array_method/0.phpt b/tests/swoole_stdext/array_method/0.phpt new file mode 100644 index 00000000000..d0ec24e0892 --- /dev/null +++ b/tests/swoole_stdext/array_method/0.phpt @@ -0,0 +1,21 @@ +--TEST-- +swoole_stdext/array_method: 0 +--SKIPIF-- + +--FILE-- +'); +$array[0] = 1; +$array[1] = 2; +$array[2] = 3; +$array[3] = 999; +Assert::false($array->isEmpty()); +Assert::eq($array->count(), count($array)); +Assert::eq($array->slice(0, 2), [1, 2]); +Assert::true($array->contains(999)); +Assert::eq($array->search(999), 3); +Assert::true([]->isEmpty()); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/array_method/1.phpt b/tests/swoole_stdext/array_method/1.phpt new file mode 100644 index 00000000000..db974ec88e2 --- /dev/null +++ b/tests/swoole_stdext/array_method/1.phpt @@ -0,0 +1,18 @@ +--TEST-- +swoole_stdext/array_method: 1 +--SKIPIF-- + +--FILE-- +', ["lemon", "orange", "banana", "apple"]); +$sorted_array = array("apple", "banana", "lemon", "orange",); + +$ref = &$array; +$ref->sort(SORT_NATURAL | SORT_FLAG_CASE); + +Assert::same($array, $sorted_array); +Assert::same($ref, $sorted_array); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/array_method/2.phpt b/tests/swoole_stdext/array_method/2.phpt new file mode 100644 index 00000000000..f85aa1e5cd4 --- /dev/null +++ b/tests/swoole_stdext/array_method/2.phpt @@ -0,0 +1,27 @@ +--TEST-- +swoole_stdext/array_method: 2 +--SKIPIF-- + +--FILE-- +count(), 4); +$ref = &$stack; +$fruit = $ref->shift(); +Assert::eq($fruit, "orange"); +Assert::eq($stack->count(), 3); +Assert::eq($array->count(), 4); + +$ref->unshift("mango"); +Assert::eq($stack->count(), 4); + +$stack2 = array("orange", "banana", "apple", "raspberry"); +Assert::eq($stack2->count(), 4); +$stack2->shift(); +?> +--EXPECTF-- +Warning: array_shift(): Argument #1 ($array) must be passed by reference, value given in %s on line %d diff --git a/tests/swoole_stdext/array_method/method.phpt b/tests/swoole_stdext/array_method/method.phpt new file mode 100644 index 00000000000..44bc7d827d9 --- /dev/null +++ b/tests/swoole_stdext/array_method/method.phpt @@ -0,0 +1,215 @@ +--TEST-- +swoole_stdext/string_method: all array methods test +--SKIPIF-- + +--FILE-- += 80400) { + $array = [ + 'a' => 'dog', + 'b' => 'cat', + 'c' => 'cow', + 'd' => 'duck', + 'e' => 'goose', + 'f' => 'elephant' + ]; + Assert::eq( + $array->all(fn(string $value) => $value->length() > 12), + array_all($array, fn(string $value) => $value->length() > 12) + ); + + Assert::eq( + $array->any(fn(string $value) => $value->length() > 5), + array_any($array, fn(string $value) => $value->length() > 5) + ); +} + +$array = array("FirSt" => 1, "SecOnd" => 4); +Assert::eq($array->changeKeyCase(CASE_UPPER), array_change_key_case($array, CASE_UPPER)); + +$array = array('a', 'b', 'c', 'd', 'e'); +Assert::eq($array->chunk(2), array_chunk($array, 2)); + +$records = [ + [ + 'id' => 2135, + 'first_name' => 'John', + 'last_name' => 'Doe', + ], + [ + 'id' => 3245, + 'first_name' => 'Sally', + 'last_name' => 'Smith', + ], + [ + 'id' => 5342, + 'first_name' => 'Jane', + 'last_name' => 'Jones', + ], + [ + 'id' => 5623, + 'first_name' => 'Peter', + 'last_name' => 'Doe', + ] +]; +Assert::eq($records->column('id'), array_column($records, 'id')); + +$array = array(1, "hello", 1, "world", "hello"); +Assert::eq($array->countValues(), array_count_values($array)); + +$array1 = array("a" => "green", "red", "blue", "red"); +$array2 = array("b" => "green", "yellow", "red"); +Assert::eq($array1->diff($array2), array_diff($array1, $array2)); + +$array1 = array("a" => "green", "b" => "brown", "c" => "blue", "red"); +$array2 = array("a" => "green", "yellow", "red"); +Assert::eq($array1->diffAssoc($array2), array_diff_assoc($array1, $array2)); + +$array1 = array('blue' => 1, 'red' => 2, 'green' => 3, 'purple' => 4); +$array2 = array('green' => 5, 'yellow' => 7, 'cyan' => 8); +Assert::eq($array1->diffKey($array2), array_diff_key($array1, $array2)); + +function odd($var) +{ + return $var & 1; +} +$array = [6, 7, 8, 9, 10, 11, 12]; +Assert::eq($array->filter('odd'), array_filter($array, 'odd')); + +if (PHP_VERSION_ID >= 80400) { + $array = [ + 'a' => 'dog', + 'b' => 'cat', + 'c' => 'cow', + 'd' => 'duck', + 'e' => 'goose', + 'f' => 'elephant' + ]; + function compare(string $value) { + return strlen($value) > 4; + } + Assert::eq($array->find('compare'), array_find($array, 'compare')); +} + +$input = array("oranges", "apples", "pears"); +Assert::eq($input->flip(), array_flip($input)); + +$array1 = array("a" => "green", "red", "blue"); +$array2 = array("b" => "green", "yellow", "red"); +Assert::eq($array1->intersect($array2), array_intersect($array1, $array2)); +Assert::eq($array1->intersectAssoc($array2), array_intersect_assoc($array1, $array2)); + +$array = ['apple', 2, 3]; +Assert::eq($array->isList(), array_is_list($array)); + +$array = ['first' => 1, 'second' => 4]; +Assert::eq($array->keyExists('first'), array_key_exists('first', $array)); + +$array = ['a' => 1, 'b' => 2, 'c' => 3]; +Assert::eq($array->keyFirst(), array_key_first($array)); +Assert::eq($array->keyLast(), array_key_last($array)); +Assert::eq($array->keys(), array_keys($array)); +Assert::eq($array->values(), array_values($array)); + +function cube($n) +{ + return ($n * $n * $n); +} + +$a = [1, 2, 3, 4, 5]; +Assert::eq($a->map('cube'), array_map('cube', $a)); + +$array = array(12, 10, 9); +Assert::eq($array->pad(5, 0), array_pad($array, 5, 0)); + +$a = array(2, 4, 6, 8); +Assert::eq($a->product(), array_product($a)); + +$array = array("Neo", "Morpheus", "Trinity", "Cypher", "Tank"); +Assert::notEq($array->rand(2), array_rand($array, 2)); + +function sum($carry, $item) +{ + $carry += $item; + return $carry; +} +$array = array(1, 2, 3, 4, 5); +Assert::eq($array->reduce('sum'), array_reduce($array, 'sum')); + +$base = array("orange", "banana", "apple", "raspberry"); +$replacements = array(0 => "pineapple", 4 => "cherry"); +$replacements2 = array(0 => "grape"); +Assert::eq($base->replace($replacements, $replacements2), array_replace($base, $replacements, $replacements2)); + +$array = array("php", 4.0, array("green", "red")); +Assert::eq($array->reverse()->reverse(), array_reverse(array_reverse($array))); + +$array = array(0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red'); +Assert::eq($array->search('green'), array_search('green', $array)); + +$array = array("a", "b", "c", "d", "e"); +Assert::eq($array->slice(-2, 1), array_slice($array, -2, 1)); + +$a = array(2, 4, 6, 8); +Assert::eq($a->sum(), array_sum($a)); + +$array = ["a" => "green", "red", "b" => "green", "blue", "red"]; +Assert::eq($array->unique(), array_unique($array)); +Assert::eq($array->count(), count($array)); + +$array = array("Mac", "NT", "Irix", "Linux"); +Assert::eq($array->contains("Mac"), in_array("Mac", $array)); +Assert::eq($array->join(","), implode(",", $array)); + +$array = []; +Assert::true($array->isEmpty()); + +$array = typed_array('', ["lemon", "orange", "banana", "apple"]); +Assert::true($array->isTyped()); + +$fruits1 = array("lemon", "orange", "banana", "apple"); +$fruits2 = array("lemon", "orange", "banana", "apple"); +$result = &$fruits1; +sort($fruits2); +Assert::eq($result->sort(), $fruits2); +Assert::eq($fruits1, $fruits2); + +$stack = array("orange", "banana", "apple", "raspberry"); +$result = &$stack; +Assert::eq($result->pop(), 'raspberry'); +Assert::eq(array_pop($stack), 'apple'); + +$array = array("red","green"); +$result = &$array; +$result->push("blue"); +Assert::eq($array, ["red","green", "blue"]); +array_push($array, "yellow"); +Assert::eq($array, ["red","green", "blue", "yellow"]); + +$stack = array("orange", "banana", "apple", "raspberry"); +$result = &$stack; +Assert::eq($result->shift(), 'orange'); +Assert::eq(array_shift($stack), 'banana'); + +$queue = ["orange", "banana"]; +$result = &$queue; +$result->unshift("orange"); +Assert::eq($queue, ["orange", "orange", "banana"]); +array_unshift($queue, "orange"); +Assert::eq($queue, ["orange", "orange", "orange", "banana"]); + +$array1 = array("red", "green", "blue", "yellow"); +$array2 = array("red", "green", "blue", "yellow"); + +$result = &$array1; +Assert::eq($result->splice(2), array_splice($array2, 2)); +Assert::eq($array1, $array2); + +$find = array("Hello","world"); +$replace = array("B"); +$arr = array("Hello","world","!"); +Assert::eq($arr->replaceStr($find, $replace), str_replace($find, $replace, $arr)); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/stream_method/0.phpt b/tests/swoole_stdext/stream_method/0.phpt new file mode 100644 index 00000000000..9aab805acb7 --- /dev/null +++ b/tests/swoole_stdext/stream_method/0.phpt @@ -0,0 +1,36 @@ +--TEST-- +swoole_stdext/stream_method: 0 +--SKIPIF-- + +--FILE-- +write($rdata->base64Encode()), $rdata->length()); +$fp->seek(0); +Assert::eq($rdata, $fp->read(8192)->base64Decode()); +Assert::eq($fp->stat(), fstat($fp)); +Assert::true($fp->sync()); +Assert::true($fp->dataSync()); +$fp->seek(100); +Assert::true($fp->tell() == 100); +Assert::true($fp->lock(LOCK_SH) == true); +Assert::true($fp->lock(LOCK_UN) == true); +Assert::true($fp->eof() == feof($fp)); + +$fp->seek(0); +$char = $fp->getChar(); +$fp->seek(0); +Assert::eq($char, fgetc($fp)); +$fp->seek(0); +$line = $fp->getLine(); +$fp->seek(0); +Assert::eq($line, fgets($fp)); +Assert::true($fp->truncate(1000)); + +Assert::true($fp->close()); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/0.phpt b/tests/swoole_stdext/string_method/0.phpt new file mode 100644 index 00000000000..b7b77f11b4c --- /dev/null +++ b/tests/swoole_stdext/string_method/0.phpt @@ -0,0 +1,18 @@ +--TEST-- +swoole_stdext/string_method: 0 +--SKIPIF-- + +--FILE-- +isEmpty()); +Assert::eq($string->length(), strlen($string)); +Assert::eq($string->substr(0, 5), 'hello'); +Assert::eq($string->contains('world'), true); +Assert::eq($string->indexOf('test'), strpos($string, 'test')); +Assert::eq($string->split(' '), explode(' ', $string)); +Assert::true(''->isEmpty()); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/1.phpt b/tests/swoole_stdext/string_method/1.phpt new file mode 100644 index 00000000000..1df0e03e142 --- /dev/null +++ b/tests/swoole_stdext/string_method/1.phpt @@ -0,0 +1,16 @@ +--TEST-- +swoole_stdext/string_method: 1 +--SKIPIF-- + +--FILE-- +parseStr(); +Assert::eq($output['first'], 'value'); +Assert::eq($output['arr'][0], 'foo bar'); +Assert::eq($output['arr'][1], 'baz'); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/json.phpt b/tests/swoole_stdext/string_method/json.phpt new file mode 100644 index 00000000000..f6db8064593 --- /dev/null +++ b/tests/swoole_stdext/string_method/json.phpt @@ -0,0 +1,15 @@ +--TEST-- +swoole_stdext/string_method: json +--SKIPIF-- + +--FILE-- + random_bytes(128)->base64Encode(), 'b' => random_int(1, PHP_INT_MAX), 'c' => php_uname()]; + +$str = $array->jsonEncode(); +Assert::notEmpty($str); +Assert::eq($str->jsonDecode(), $array); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/marshal.phpt b/tests/swoole_stdext/string_method/marshal.phpt new file mode 100644 index 00000000000..4a80f1ab200 --- /dev/null +++ b/tests/swoole_stdext/string_method/marshal.phpt @@ -0,0 +1,15 @@ +--TEST-- +swoole_stdext/string_method: marshal +--SKIPIF-- + +--FILE-- + random_bytes(128)->base64Encode(), 'b' => random_int(1, PHP_INT_MAX), 'c' => php_uname()]; + +$str = $array->marshal(); +Assert::notEmpty($str); +Assert::eq($str->unmarshal(), $array); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/match.phpt b/tests/swoole_stdext/string_method/match.phpt new file mode 100644 index 00000000000..e2a729a325a --- /dev/null +++ b/tests/swoole_stdext/string_method/match.phpt @@ -0,0 +1,25 @@ +--TEST-- +swoole_stdext/string_method: match +--SKIPIF-- + +--FILE-- +match($regex1, PREG_OFFSET_CAPTURE); + +preg_match($regex1, $str, $matches2, PREG_OFFSET_CAPTURE); +Assert::eq($matches, $matches2); + + +$html = "bold textclick me"; +$regex2 = "/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/"; +preg_match_all($regex2, $html, $matches2, PREG_SET_ORDER); + +$matches = $html->matchAll($regex2, PREG_SET_ORDER); +Assert::eq($matches, $matches2); + +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/mbstring.phpt b/tests/swoole_stdext/string_method/mbstring.phpt new file mode 100644 index 00000000000..1cc129d4dad --- /dev/null +++ b/tests/swoole_stdext/string_method/mbstring.phpt @@ -0,0 +1,62 @@ +--TEST-- +swoole_stdext/string_method: all mbstring methods test +--SKIPIF-- + +--FILE-- +mbUpperFirst(), mb_ucfirst($text)); + +$text = 'Bbbb大声向宇宙呐喊bbbb'; +Assert::eq($text->mbLowerFirst(), mb_lcfirst($text)); + +$text = "\t\t大声向宇宙呐喊 :) ... "; +Assert::eq($text->mbTrim(), mb_trim($text)); +Assert::eq($text->mbLTrim(), mb_ltrim($text)); +Assert::eq($text->mbRTrim(), mb_rtrim($text)); + +$text = '大声大声大声向宇宙呐喊'; +Assert::eq($text->mbSubstrCount('大声'), mb_substr_count($text, '大声')); + +$text = "大声大声大声向宇宙呐喊"; +Assert::eq($text->mbSubstr(0, -1), mb_substr($text, 0, -1)); + +$text = 'b大声大声大声向宇宙呐喊'; +Assert::eq($text->mbUpper(), mb_strtoupper($text)); + +$text = 'BBBBBB大声大声大声向宇宙呐喊'; +Assert::eq($text->mbLower(), mb_strtolower($text)); + +$email = 'name我哦知道@example.com'; +Assert::eq($email->mbFind('哦'), mb_strstr($email, '哦')); +Assert::eq($email->mbIFind('道'), mb_stristr($email, '道')); + +$mystring1 = '当地特产'; +$mystring2 = '现在只是谈谈'; +Assert::true($mystring1->mbIndexOf('地') === mb_strpos($mystring1, '地')); +Assert::true($mystring2->mbIIndexOf('A') === mb_stripos($mystring2, 'a')); + +$mystring = '啦啦啦啦啦啦啦'; +Assert::true($mystring->mbLastIndexOf('好') === false); +Assert::true(mb_strrpos($mystring, '好') === false); +Assert::eq($mystring->mbILastIndexOf('啦'), mb_strripos($mystring, '啦')); +Assert::eq($mystring->mbLastCharIndexOf('啦'), mb_strrchr($mystring, '啦')); +Assert::eq($mystring->mbILastCharIndex('啦'), mb_strrichr($mystring, '啦')); + +$text = '啦啦啦啦啦啦啦'; +Assert::eq($text->mbLength(), mb_strlen($text)); +Assert::eq($text->mbCut(0, 2), mb_strcut($text, 0, 2)); +Assert::eq($text->mbDetectEncoding(), mb_detect_encoding($text)); +Assert::eq($text->mbConvertEncoding('GBK'), mb_convert_encoding($text, 'GBK')); + +$text = 'bbbbba啦啦啦啦啦啦啦'; +Assert::eq( + $text->mbConvertCase(MB_CASE_UPPER)->mbConvertCase(MB_CASE_LOWER), + mb_convert_case(mb_convert_case($text, MB_CASE_UPPER), MB_CASE_LOWER) + ); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/method.phpt b/tests/swoole_stdext/string_method/method.phpt new file mode 100644 index 00000000000..739d8aa43e6 --- /dev/null +++ b/tests/swoole_stdext/string_method/method.phpt @@ -0,0 +1,163 @@ +--TEST-- +swoole_stdext/string_method: all string methods test +--SKIPIF-- + +--FILE-- +length(), strlen($text)); + +$text = ''; +Assert::eq($text->isEmpty(), true); + +$text = 'A'; +Assert::eq($text->lower(), strtolower($text)); +Assert::eq($text->lowerFirst(), lcfirst($text)); + +$text = 'b'; +Assert::eq($text->upper(), strtoupper($text)); +Assert::eq($text->upperFirst(), ucfirst($text)); + +$text = 'hello world!'; +Assert::eq($text->upperWords(), ucwords($text)); + +$text = "PHP isThirty\nYears Old!\tYay to the Elephant!\n"; +$characters = "\0..\37!@\177..\377"; +Assert::eq($text->addCSlashes($characters), addCSlashes($text, $characters)); + +$text = "O'Reilly?"; +Assert::eq($text->addSlashes(), addslashes($text)); + +$text = 'This is quite a long string, which will get broken up because the line is going to be too long after base64 encoding it.'; +Assert::eq($text->base64Encode()->chunkSplit(), chunk_split(base64_encode($text))); + +$text = "Two Ts and one F."; +Assert::eq($text->countChars(1), count_chars($text, 1)); + +$text = "I'll \"walk\" the dog now"; +Assert::eq($text->htmlEntityEncode()->htmlEntityDecode(), html_entity_decode(htmlentities($text))); + +$text = "Test"; +Assert::eq($text->htmlSpecialCharsEncode(ENT_QUOTES)->htmlSpecialCharsDecode(), htmlspecialchars_decode(htmlspecialchars($text, ENT_QUOTES))); + +$text = "\t\tThese are a few words :) ... "; +Assert::eq($text->trim(), trim($text)); +Assert::eq($text->lTrim(), ltrim($text)); +Assert::eq($text->rTrim(), rtrim($text)); + +$text = "first=value&arr[]=foo+bar&arr[]=baz"; +parse_str($text, $result); +Assert::eq($text->parseStr(), $result); + +$url = 'http://username:password@hostname:9090/path?arg=value#anchor'; +Assert::eq($url->parseUrl(), parse_url($url)); + +$text = 'The lazy fox jumped over the fence'; +Assert::eq($text->contains('lazy'), str_contains($text, 'lazy')); + +if (PHP_VERSION_ID >= 80300) { + $text = 'ABC'; + Assert::eq($text->incr(), str_increment($text)); + Assert::eq($text->decr(), str_decrement($text)); +} + +$text = ""; +Assert::eq($text->replace("%BODY%", "black"), str_replace("%BODY%", "black", $text)); +Assert::eq($text->iReplace("%body%", "black"), str_ireplace("%body%", "black", $text)); + +$text = "Alien"; +Assert::eq($text->pad(10), str_pad($text, 10)); + +$text = "-="; +Assert::eq($text->repeat(10), str_repeat($text, 10)); + +$text = 'abcdef'; +Assert::notEq($text->shuffle(), str_shuffle($text)); + +$text = "piece1 piece2 piece3 piece4 piece5 piece6"; +Assert::eq($text->split(' '), explode(' ', $text)); + +$text = 'The lazy fox jumped over the fence'; +Assert::eq($text->startsWith('The'), str_starts_with($text, 'The')); +Assert::eq($text->endsWith('fence'), str_ends_with($text, 'fence')); + +$text = "Hello fri3nd, you're + looking good today!"; +Assert::eq($text->wordCount(0), str_word_count($text, 0)); +Assert::eq($text->wordCount(1), str_word_count($text, 1)); +Assert::eq($text->wordCount(2), str_word_count($text, 2)); + +$var1 = "Hello"; +$var2 = "hello"; +Assert::eq($var1->iCompare($var2), strcasecmp($var1, $var2)); +Assert::eq($var1->compare($var2), strcmp($var1, $var2)); + +$email = 'name@example.com'; +Assert::eq($email->find('@'), strstr($email, '@')); +Assert::eq($email->iFind('N'), stristr($email, 'N')); + +$text = '

Test paragraph.

Other text'; +Assert::eq($text->stripTags(), strip_tags($text)); + +$text = 'I\'d have a coffee.\nNot a problem.'; +Assert::eq($text->stripCSlashes(), stripcslashes($text)); + +$str = "Is your name O\'reilly?"; +Assert::eq($str->stripSlashes(), stripslashes($str)); + +$mystring1 = 'xyz'; +$mystring2 = 'ABC'; +Assert::true($mystring1->iIndexOf('A') === stripos($mystring1, 'a')); +Assert::true($mystring2->iIndexOf('A') === stripos($mystring2, 'a')); +Assert::eq($mystring2->indexOf('a'), strpos($mystring2, 'a')); + +$mystring = 'Elephpant'; +Assert::true($mystring->lastIndexOf('b') === false); +Assert::true(strrpos($mystring, 'b') === false); +Assert::eq($mystring->iLastIndexOf('E'), strripos($mystring, 'E')); +Assert::eq($mystring->lastCharIndexOf('E'), strrchr($mystring, 'E')); + +$text = "abcdef"; +Assert::eq($text->substr(0, -1), substr($text, 0, -1)); +Assert::eq($text->substrCompare("bc", 1, 2), substr_compare($text, "bc", 1, 2)); + +$text = 'This is a test'; +Assert::eq($text->substrCount('is'), substr_count($text, 'is')); + +$var = 'ABCDEFGH:/MNRPQR/'; +Assert::eq($var->substrReplace('bob', 0), substr_replace($var, 'bob', 0)); + +$var = 'abcdefghijklmn'; +Assert::eq($var->reverse(), strrev($var)); +Assert::eq($var->md5(), md5($var)); +Assert::eq($var->sha1(), sha1($var)); +Assert::eq($var->crc32(), crc32($var)); +Assert::eq($var->hash('sha256'), hash('sha256', $var)); + +$str = 'This is an encoded string'; +Assert::eq($str->base64Encode()->base64Decode(), base64_decode(base64_encode($str))); + +$text = 'Data123!@-_ +'; +Assert::eq($text->urlEncode()->urlDecode(), urldecode(urlencode($text))); + +$text = 'foo @+%/'; +Assert::eq($text->rawUrlEncode()->rawUrlDecode(), rawurldecode(rawurlencode($text))); + +$pattern = "/php/i"; +$text = "PHP is the web scripting language of choice."; +$result1 = $text->match("/php/i"); +preg_match("/php/i", $text, $result2); +Assert::eq($result1, $result2); + +$pattern = "/\(? (\d{3})? \)? (?(1) [\-\s] ) \d{3}-\d{4}/x"; +$subject = "Call 555-1212 or 1-800-555-1212"; +$result1 = $subject->matchAll($pattern); +preg_match_all($pattern, $subject, $result2); +Assert::eq($result1, $result2); + +$text = '112'; +Assert::eq($text->isNumeric(), is_numeric($text)); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/typed_array/0.phpt b/tests/swoole_stdext/typed_array/0.phpt new file mode 100644 index 00000000000..4e6a01ec284 --- /dev/null +++ b/tests/swoole_stdext/typed_array/0.phpt @@ -0,0 +1,19 @@ +--TEST-- +swoole_stdext/typed_array: 0 +--SKIPIF-- + +--FILE-- +'); + +try { + $array = typed_array('getMessage()->contains("must start with '<' and end with '>'")); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/1.phpt b/tests/swoole_stdext/typed_array/1.phpt new file mode 100644 index 00000000000..bd46c6c46ab --- /dev/null +++ b/tests/swoole_stdext/typed_array/1.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_stdext/typed_array: 1 +--SKIPIF-- + +--FILE-- +'); +$array[] = 123; + +try { + $array[] = '456'; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +$array = typed_array(''); +$array[] = true; +$array[] = false; +try { + $array[] = '456'; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +$array = typed_array(''); +$array[] = '456'; +try { + $array[] = true; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +$array = typed_array(''); +$array[] = 4556.56; +try { + $array[] = 456; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +$array = typed_array(''); +$array[] = new stdClass(); +try { + $array[] = new ArrayObject(); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +?> +--EXPECTF-- diff --git a/tests/swoole_stdext/typed_array/10.phpt b/tests/swoole_stdext/typed_array/10.phpt new file mode 100644 index 00000000000..aa5b850e194 --- /dev/null +++ b/tests/swoole_stdext/typed_array/10.phpt @@ -0,0 +1,25 @@ +--TEST-- +swoole_stdext/typed_array: 10 +--SKIPIF-- + +--FILE-- +'); +$array[999] = $bytes; +$copy = typed_array('', $array); +Assert::eq($copy[999], $bytes); + +try { + $copy2 = typed_array('', $array); +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('not match the initial values')); + echo "DONE\n"; +} + + +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/11.phpt b/tests/swoole_stdext/typed_array/11.phpt new file mode 100644 index 00000000000..94c7ec88033 --- /dev/null +++ b/tests/swoole_stdext/typed_array/11.phpt @@ -0,0 +1,21 @@ +--TEST-- +swoole_stdext/typed_array: 11 +--SKIPIF-- + +--FILE-- +', [1, $num, 3]); +foreach($array as &$v) { + var_dump($v); + echo "each\n"; +} +echo "end\n"; +?> +--EXPECTF-- +Fatal error: Uncaught Error: The type array do not support using references for element value during iteration in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/swoole_stdext/typed_array/2.phpt b/tests/swoole_stdext/typed_array/2.phpt new file mode 100644 index 00000000000..5811d4e4a3d --- /dev/null +++ b/tests/swoole_stdext/typed_array/2.phpt @@ -0,0 +1,29 @@ +--TEST-- +swoole_stdext/typed_array: 2 +--SKIPIF-- + +--FILE-- +>'); +$array[999] = typed_array(''); + +try { + $array[888] = typed_array(''); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +try { + $array[777] = []; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +?> +--EXPECT-- +DONE +DONE diff --git a/tests/swoole_stdext/typed_array/3.phpt b/tests/swoole_stdext/typed_array/3.phpt new file mode 100644 index 00000000000..d7963322555 --- /dev/null +++ b/tests/swoole_stdext/typed_array/3.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_stdext/typed_array: 3 +--SKIPIF-- + +--FILE-- +'); +$map['a'] = 1; +$map['b'] = 2; +$map['c'] = 3; +unset($map['b']); + +Assert::true(isset($map['a'])); +Assert::true(isset($map['c'])); +Assert::false(isset($map['b'])); + +$array = typed_array(''); +$array[] = 3; +$array[] = 5; +$array[] = 7; + +try { + unset($array[0]); +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('not support')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE \ No newline at end of file diff --git a/tests/swoole_stdext/typed_array/4.phpt b/tests/swoole_stdext/typed_array/4.phpt new file mode 100644 index 00000000000..9d4e71bab6c --- /dev/null +++ b/tests/swoole_stdext/typed_array/4.phpt @@ -0,0 +1,29 @@ +--TEST-- +swoole_stdext/typed_array: 4 +--SKIPIF-- + +--FILE-- +'); +$array[999] = 'test'; + +try { + $array['hello'] = 'world'; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array key type mismatch')); + echo "DONE\n"; +} + +$key = str_repeat('a', 1000); +try { + $array[$key] = 'world'; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array key type mismatch')); + echo "DONE\n"; +} +?> +--EXPECTF-- +DONE +DONE diff --git a/tests/swoole_stdext/typed_array/5.phpt b/tests/swoole_stdext/typed_array/5.phpt new file mode 100644 index 00000000000..e3a83dfe71c --- /dev/null +++ b/tests/swoole_stdext/typed_array/5.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_stdext/typed_array: 5 +--SKIPIF-- + +--FILE-- +'); +$array[] = 3; +$array[] = 5; +$array[] = 7; +$array[3] = random_int(1, 100); + +try { + $array[5] = 999; +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('out of the permitted range')); + echo "DONE\n"; +} + +try { + $array["hello"] = 999; +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('must be undef or int')); + echo "DONE\n"; +} + +?> +--EXPECT-- +DONE +DONE \ No newline at end of file diff --git a/tests/swoole_stdext/typed_array/6.phpt b/tests/swoole_stdext/typed_array/6.phpt new file mode 100644 index 00000000000..b823dcf51d0 --- /dev/null +++ b/tests/swoole_stdext/typed_array/6.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_stdext/typed_array: 6 +--SKIPIF-- + +--FILE-- +', [1, $num, 3]); +Assert::eq($array->count(), 3); +Assert::eq($array[1], $num); + +try { + $array = typed_array('', [1, "hello", 3]); +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/7.phpt b/tests/swoole_stdext/typed_array/7.phpt new file mode 100644 index 00000000000..04f19e1604c --- /dev/null +++ b/tests/swoole_stdext/typed_array/7.phpt @@ -0,0 +1,45 @@ +--TEST-- +swoole_stdext/typed_array: 7 +--SKIPIF-- + +--FILE-- +', [1, $num, 3]); + +$copy = []; +foreach($array as $k => $v) { + $copy[] = $v; + $array[$k] = $v * 2; +} + +Assert::eq($copy[1], $num); +Assert::eq($array[1], $num * 2); +Assert::true($array->isTyped()); + +try { + $array[] = 'hello'; +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +foreach($copy as &$v) { + $v = 0; +} +Assert::eq($copy->sum(), 0); + +try { + foreach ($array as &$v) { + var_dump($v); + } +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('not support using references for element value')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE +DONE diff --git a/tests/swoole_stdext/typed_array/8.phpt b/tests/swoole_stdext/typed_array/8.phpt new file mode 100644 index 00000000000..19bb697166f --- /dev/null +++ b/tests/swoole_stdext/typed_array/8.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_stdext/typed_array: 8 +--SKIPIF-- + +--FILE-- +'); + +$arr[] = 1; +$arr[0] += 10; +Assert::eq($arr[0], 11); + +try { + $arr[0] .= "hello world"; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/9.phpt b/tests/swoole_stdext/typed_array/9.phpt new file mode 100644 index 00000000000..d41386d948f --- /dev/null +++ b/tests/swoole_stdext/typed_array/9.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_stdext/typed_array: 9 +--SKIPIF-- + +--FILE-- +'); +$array[999] = random_bytes(128); + +try { + $a = typed_array(''); + $array[888] = $a; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_pop.phpt b/tests/swoole_stdext/typed_array/array_pop.phpt new file mode 100644 index 00000000000..fb4ce9e8089 --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_pop.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_stdext/typed_array: array_pop +--SKIPIF-- + +--FILE-- +', [1, 3, 5, 7]); +$v = array_pop($arr); +Assert::eq($v, 7); +Assert::eq($arr[2], 5); +Assert::eq($arr->count(), 3); +Assert::true($arr->isList()); + +try { + array_push($arr, 9, 'hello world', true); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_push.phpt b/tests/swoole_stdext/typed_array/array_push.phpt new file mode 100644 index 00000000000..e60693717bf --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_push.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_stdext/typed_array: 9 +--SKIPIF-- + +--FILE-- +', [1, 3, 5]); +array_push($arr, 7); +Assert::eq($arr[3], 7); +Assert::true($arr->isList()); + +try { + array_push($arr, 9, 'hello world', true); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_shift.phpt b/tests/swoole_stdext/typed_array/array_shift.phpt new file mode 100644 index 00000000000..85ea228efa1 --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_shift.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_stdext/typed_array: array_shift +--SKIPIF-- + +--FILE-- +', [1, 3, 5, 7]); +$v = array_shift($arr); +Assert::eq($v, 1); +Assert::eq($arr[1], 5); +Assert::eq($arr->count(), 3); +Assert::true($arr->isList()); + +try { + array_unshift($arr, 9, 'hello world', true); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_splice.phpt b/tests/swoole_stdext/typed_array/array_splice.phpt new file mode 100644 index 00000000000..31524ac356e --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_splice.phpt @@ -0,0 +1,36 @@ +--TEST-- +swoole_stdext/typed_array: array_splice +--SKIPIF-- + +--FILE-- +', array("red", "green", "blue", "yellow")); +array_splice($arr, 2); +Assert::eq($arr->count(), 2); +array_push($arr, "purple", "orange"); +Assert::eq($arr->count(), 4); +Assert::true($arr->isList()); + +array_splice($arr, 1, count($arr), "black"); +Assert::true($arr->contains("black")); +Assert::eq($arr->count(), 2); +Assert::true($arr->isList()); +Assert::true($arr->isTyped()); + +array_splice($arr, -1, 1, array("gray", "maroon")); +Assert::eq($arr->count(), 3); +Assert::true($arr->contains("maroon")); +Assert::true($arr->isTyped()); +Assert::true($arr->isList()); + +try { + array_splice($arr, -1, 1, array(9999, false)); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_unshift.phpt b/tests/swoole_stdext/typed_array/array_unshift.phpt new file mode 100644 index 00000000000..9d79b953955 --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_unshift.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_stdext/typed_array: 10 +--SKIPIF-- + +--FILE-- +', [1, 3, 5]); +array_unshift($arr, 999); +Assert::eq($arr[0], 999); +Assert::true($arr->isList()); + +try { + array_unshift($arr, 9, 'hello world', true); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/resource.phpt b/tests/swoole_stdext/typed_array/resource.phpt new file mode 100644 index 00000000000..de2add6a0e3 --- /dev/null +++ b/tests/swoole_stdext/typed_array/resource.phpt @@ -0,0 +1,21 @@ +--TEST-- +swoole_stdext/typed_array: resource +--SKIPIF-- + +--FILE-- +'); +$map['a'] = fopen(__FILE__, 'r'); +Assert::eq(get_resource_type($map['a']), 'stream'); + +try { + $map['b'] = 1; +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/sort.phpt b/tests/swoole_stdext/typed_array/sort.phpt new file mode 100644 index 00000000000..0b92db41358 --- /dev/null +++ b/tests/swoole_stdext/typed_array/sort.phpt @@ -0,0 +1,20 @@ +--TEST-- +swoole_stdext/typed_array: sort +--SKIPIF-- + +--FILE-- +', [1243, 3434, 5453, 4532, 2, 3454, 5233, 655, 234, 6, 2356, 4554]); +Assert::true($arr->isList()); +Assert::true($arr->isTyped()); + +sort($arr); +Assert::true($arr->isTyped()); + +shuffle($arr); +Assert::true($arr->isTyped()); + +?> +--EXPECT-- diff --git a/thirdparty/php/standard/var_decoder.cc b/thirdparty/php/standard/var_decoder.cc index 1e4814d2cec..29b1da5cdf3 100644 --- a/thirdparty/php/standard/var_decoder.cc +++ b/thirdparty/php/standard/var_decoder.cc @@ -36,10 +36,13 @@ static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ return "The decoded property name is invalid"; case PHP_JSON_ERROR_UTF16: return "Single unpaired UTF-16 surrogate in unicode escape"; + case PHP_JSON_ERROR_NON_BACKED_ENUM: + return "Non-backed enums have no default serialization"; default: return "Unknown error"; } } +/* }}} */ void json_decode(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth) { if (!(options & PHP_JSON_THROW_ON_ERROR)) { diff --git a/thirdparty/php/zend/zend_execute.c b/thirdparty/php/zend/zend_execute.c new file mode 100644 index 00000000000..22b9de1836b --- /dev/null +++ b/thirdparty/php/zend/zend_execute.c @@ -0,0 +1,643 @@ + +#include "zend.h" +#include "zend_compile.h" + +#if defined(ZEND_VM_FP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) +#define EXECUTE_DATA_D void +#define EXECUTE_DATA_C +#define EXECUTE_DATA_DC +#define EXECUTE_DATA_CC +#define NO_EXECUTE_DATA_CC +#else +#define EXECUTE_DATA_D zend_execute_data *execute_data +#define EXECUTE_DATA_C execute_data +#define EXECUTE_DATA_DC , EXECUTE_DATA_D +#define EXECUTE_DATA_CC , EXECUTE_DATA_C +#define NO_EXECUTE_DATA_CC , NULL +#endif + +#if defined(ZEND_VM_FP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) +#define OPLINE_D void +#define OPLINE_C +#define OPLINE_DC +#define OPLINE_CC +#else +#define OPLINE_D const zend_op *opline +#define OPLINE_C opline +#define OPLINE_DC , OPLINE_D +#define OPLINE_CC , OPLINE_C +#endif + +#define FREE_OP(type, var) \ + if ((type) & (IS_TMP_VAR | IS_VAR)) { \ + zval_ptr_dtor_nogc(EX_VAR(var)); \ + } + +#define RETURN_VALUE_USED(opline) ((opline)->result_type != IS_UNUSED) + +#define CV_DEF_OF(i) (EX(func)->op_array.vars[i]) + +#define get_zval_ptr(op_type, node, type) _get_zval_ptr(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_zval_ptr_deref(op_type, node, type) _get_zval_ptr_deref(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_zval_ptr_undef(op_type, node, type) _get_zval_ptr_undef(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_op_data_zval_ptr_r(op_type, node) _get_op_data_zval_ptr_r(op_type, node EXECUTE_DATA_CC OPLINE_CC) +#define get_op_data_zval_ptr_deref_r(op_type, node) \ + _get_op_data_zval_ptr_deref_r(op_type, node EXECUTE_DATA_CC OPLINE_CC) +#define get_zval_ptr_ptr(op_type, node, type) _get_zval_ptr_ptr(op_type, node, type EXECUTE_DATA_CC) +#define get_zval_ptr_ptr_undef(op_type, node, type) _get_zval_ptr_ptr(op_type, node, type EXECUTE_DATA_CC) +#define get_obj_zval_ptr(op_type, node, type) _get_obj_zval_ptr(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_obj_zval_ptr_undef(op_type, node, type) \ + _get_obj_zval_ptr_undef(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_obj_zval_ptr_ptr(op_type, node, type) _get_obj_zval_ptr_ptr(op_type, node, type EXECUTE_DATA_CC) + +#if PHP_VERSION_ID < 80300 +static ZEND_COLD void zend_illegal_container_offset(zend_string *container, const zval *offset, int type) { + switch (type) { + case BP_VAR_IS: + zend_type_error("Cannot access offset of type %s in isset or empty", + zend_zval_type_name(offset)); + return; + case BP_VAR_UNSET: + /* Consistent error for when trying to unset a string offset */ + if (zend_string_equals(container, ZSTR_KNOWN(ZEND_STR_STRING))) { + zend_throw_error(NULL, "Cannot unset string offsets"); + } else { + zend_type_error("Cannot unset offset of type %s on %s", zend_zval_type_name(offset), ZSTR_VAL(container)); + } + return; + default: + zend_type_error("Cannot access offset of type %s on %s", + zend_zval_type_name(offset), ZSTR_VAL(container)); + return; + } +} +#endif + +static zend_always_inline zval *_get_zval_ptr_tmp(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + ZEND_ASSERT(Z_TYPE_P(ret) != IS_REFERENCE); + + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_var(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_var_deref(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + ZVAL_DEREF(ret); + return ret; +} + +static zend_never_inline ZEND_COLD zval *zval_undefined_cv(uint32_t var EXECUTE_DATA_DC) { + if (EXPECTED(EG(exception) == NULL)) { + zend_string *cv = CV_DEF_OF(EX_VAR_TO_NUM(var)); + zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(cv)); + } + return &EG(uninitialized_zval); +} + +static zend_never_inline ZEND_COLD zval *ZEND_FASTCALL _zval_undefined_op1(EXECUTE_DATA_D) { + return zval_undefined_cv(EX(opline)->op1.var EXECUTE_DATA_CC); +} + +static zend_never_inline ZEND_COLD zval *ZEND_FASTCALL _zval_undefined_op2(EXECUTE_DATA_D) { + return zval_undefined_cv(EX(opline)->op2.var EXECUTE_DATA_CC); +} + +#define ZVAL_UNDEFINED_OP1() _zval_undefined_op1(EXECUTE_DATA_C) +#define ZVAL_UNDEFINED_OP2() _zval_undefined_op2(EXECUTE_DATA_C) + +static zend_never_inline ZEND_COLD zval *_get_zval_cv_lookup(zval *ptr, uint32_t var, int type EXECUTE_DATA_DC) { + switch (type) { + case BP_VAR_R: + case BP_VAR_UNSET: + ptr = zval_undefined_cv(var EXECUTE_DATA_CC); + break; + case BP_VAR_IS: + ptr = &EG(uninitialized_zval); + break; + case BP_VAR_RW: + zval_undefined_cv(var EXECUTE_DATA_CC); + ZEND_FALLTHROUGH; + case BP_VAR_W: + ZVAL_NULL(ptr); + break; + } + return ptr; +} + +static zend_always_inline zval *_get_zval_ptr_cv(uint32_t var, int type EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + if (type == BP_VAR_W) { + ZVAL_NULL(ret); + } else { + return _get_zval_cv_lookup(ret, var, type EXECUTE_DATA_CC); + } + } + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_deref(uint32_t var, int type EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + if (type == BP_VAR_W) { + ZVAL_NULL(ret); + return ret; + } else { + return _get_zval_cv_lookup(ret, var, type EXECUTE_DATA_CC); + } + } + ZVAL_DEREF(ret); + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_R(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + return zval_undefined_cv(var EXECUTE_DATA_CC); + } + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_deref_BP_VAR_R(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + return zval_undefined_cv(var EXECUTE_DATA_CC); + } + ZVAL_DEREF(ret); + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_IS(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_RW(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + zval_undefined_cv(var EXECUTE_DATA_CC); + ZVAL_NULL(ret); + return ret; + } + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_W(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (Z_TYPE_P(ret) == IS_UNDEF) { + ZVAL_NULL(ret); + } + return ret; +} + +static zend_always_inline zval *_get_zval_ptr(int op_type, znode_op node, int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (!ZEND_DEBUG || op_type == IS_VAR) { + return _get_zval_ptr_var(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_TMP_VAR); + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline, node); + } else if (op_type == IS_CV) { + return _get_zval_ptr_cv(node.var, type EXECUTE_DATA_CC); + } else { + return NULL; + } + } +} + +static zend_always_inline zval *_get_op_data_zval_ptr_r(int op_type, znode_op node EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (!ZEND_DEBUG || op_type == IS_VAR) { + return _get_zval_ptr_var(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_TMP_VAR); + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline + 1, node); + } else if (op_type == IS_CV) { + return _get_zval_ptr_cv_BP_VAR_R(node.var EXECUTE_DATA_CC); + } else { + return NULL; + } + } +} + +static zend_always_inline ZEND_ATTRIBUTE_UNUSED zval *_get_zval_ptr_deref(int op_type, + znode_op node, + int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (op_type == IS_TMP_VAR) { + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_VAR); + return _get_zval_ptr_var_deref(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline, node); + } else if (op_type == IS_CV) { + return _get_zval_ptr_cv_deref(node.var, type EXECUTE_DATA_CC); + } else { + return NULL; + } + } +} + +static zend_always_inline ZEND_ATTRIBUTE_UNUSED zval *_get_op_data_zval_ptr_deref_r( + int op_type, znode_op node EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (op_type == IS_TMP_VAR) { + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_VAR); + return _get_zval_ptr_var_deref(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline + 1, node); + } else if (op_type == IS_CV) { + return _get_zval_ptr_cv_deref_BP_VAR_R(node.var EXECUTE_DATA_CC); + } else { + return NULL; + } + } +} + +static zend_always_inline zval *_get_zval_ptr_undef(int op_type, znode_op node, int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (!ZEND_DEBUG || op_type == IS_VAR) { + return _get_zval_ptr_var(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_TMP_VAR); + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline, node); + } else if (op_type == IS_CV) { + return EX_VAR(node.var); + } else { + return NULL; + } + } +} + +static zend_always_inline zval *_get_zval_ptr_ptr_var(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (EXPECTED(Z_TYPE_P(ret) == IS_INDIRECT)) { + ret = Z_INDIRECT_P(ret); + } + return ret; +} + +static inline zval *_get_zval_ptr_ptr(int op_type, znode_op node, int type EXECUTE_DATA_DC) { + if (op_type == IS_CV) { + return _get_zval_ptr_cv(node.var, type EXECUTE_DATA_CC); + } else /* if (op_type == IS_VAR) */ { + ZEND_ASSERT(op_type == IS_VAR); + return _get_zval_ptr_ptr_var(node.var EXECUTE_DATA_CC); + } +} + +static inline ZEND_ATTRIBUTE_UNUSED zval *_get_obj_zval_ptr(int op_type, + znode_op op, + int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type == IS_UNUSED) { + return &EX(This); + } + return get_zval_ptr(op_type, op, type); +} + +static inline ZEND_ATTRIBUTE_UNUSED zval *_get_obj_zval_ptr_undef(int op_type, + znode_op op, + int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type == IS_UNUSED) { + return &EX(This); + } + return get_zval_ptr_undef(op_type, op, type); +} + +static inline ZEND_ATTRIBUTE_UNUSED zval *_get_obj_zval_ptr_ptr(int op_type, znode_op node, int type EXECUTE_DATA_DC) { + if (op_type == IS_UNUSED) { + return &EX(This); + } + return get_zval_ptr_ptr(op_type, node, type); +} + +static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_index(const zend_string *offset) { + zend_error(E_WARNING, "Undefined array key \"%s\"", ZSTR_VAL(offset)); +} + +static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_offset(zend_long lval) { + zend_error(E_WARNING, "Undefined array key " ZEND_LONG_FMT, lval); +} + +static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_illegal_array_offset_access(const zval *offset) { + zend_illegal_container_offset(ZSTR_KNOWN(ZEND_STR_ARRAY), offset, BP_VAR_RW); +} + +static zend_never_inline uint8_t slow_index_convert(HashTable *ht, const zval *dim, zend_value *value EXECUTE_DATA_DC) { + switch (Z_TYPE_P(dim)) { + case IS_UNDEF: { + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + ZVAL_UNDEFINED_OP2(); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + ZEND_FALLTHROUGH; + } + case IS_NULL: + value->str = ZSTR_EMPTY_ALLOC(); + return IS_STRING; + case IS_DOUBLE: + value->lval = zend_dval_to_lval(Z_DVAL_P(dim)); + if (!zend_is_long_compatible(Z_DVAL_P(dim), value->lval)) { + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + zend_incompatible_double_to_long_error(Z_DVAL_P(dim)); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + } + return IS_LONG; + case IS_RESOURCE: + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + zend_use_resource_as_offset(dim); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + value->lval = Z_RES_HANDLE_P(dim); + return IS_LONG; + case IS_FALSE: + value->lval = 0; + return IS_LONG; + case IS_TRUE: + value->lval = 1; + return IS_LONG; + default: + zend_illegal_array_offset_access(dim); + return IS_NULL; + } +} + +static zend_never_inline uint8_t slow_index_convert_w(HashTable *ht, + const zval *dim, + zend_value *value EXECUTE_DATA_DC) { + switch (Z_TYPE_P(dim)) { + case IS_UNDEF: { + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + ZVAL_UNDEFINED_OP2(); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && GC_DELREF(ht) != 1) { + if (!GC_REFCOUNT(ht)) { + zend_array_destroy(ht); + } + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + ZEND_FALLTHROUGH; + } + case IS_NULL: + value->str = ZSTR_EMPTY_ALLOC(); + return IS_STRING; + case IS_DOUBLE: + value->lval = zend_dval_to_lval(Z_DVAL_P(dim)); + if (!zend_is_long_compatible(Z_DVAL_P(dim), value->lval)) { + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + zend_incompatible_double_to_long_error(Z_DVAL_P(dim)); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && GC_DELREF(ht) != 1) { + if (!GC_REFCOUNT(ht)) { + zend_array_destroy(ht); + } + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + } + return IS_LONG; + case IS_RESOURCE: + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + zend_use_resource_as_offset(dim); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && GC_DELREF(ht) != 1) { + if (!GC_REFCOUNT(ht)) { + zend_array_destroy(ht); + } + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + value->lval = Z_RES_HANDLE_P(dim); + return IS_LONG; + case IS_FALSE: + value->lval = 0; + return IS_LONG; + case IS_TRUE: + value->lval = 1; + return IS_LONG; + default: + zend_illegal_array_offset_access(dim); + return IS_NULL; + } +} + +static zend_always_inline zval *zend_fetch_dimension_address_inner(HashTable *ht, + const zval *dim, + int dim_type, + int type EXECUTE_DATA_DC) { + zval *retval = NULL; + zend_string *offset_key; + zend_ulong hval; + +try_again: + if (EXPECTED(Z_TYPE_P(dim) == IS_LONG)) { + hval = Z_LVAL_P(dim); + num_index: + if (type != BP_VAR_W) { + ZEND_HASH_INDEX_FIND(ht, hval, retval, num_undef); + return retval; + num_undef: + switch (type) { + case BP_VAR_R: + zend_undefined_offset(hval); + ZEND_FALLTHROUGH; + case BP_VAR_UNSET: + case BP_VAR_IS: + retval = &EG(uninitialized_zval); + break; + case BP_VAR_RW: + retval = zend_undefined_offset_write(ht, hval); + break; + } + } else { + ZEND_HASH_INDEX_LOOKUP(ht, hval, retval); + } + } else if (EXPECTED(Z_TYPE_P(dim) == IS_STRING)) { + offset_key = Z_STR_P(dim); + if (ZEND_CONST_COND(dim_type != IS_CONST, 1)) { + if (ZEND_HANDLE_NUMERIC(offset_key, hval)) { + goto num_index; + } + } + str_index: + if (type != BP_VAR_W) { + retval = zend_hash_find_ex(ht, offset_key, ZEND_CONST_COND(dim_type == IS_CONST, 0)); + if (!retval) { + switch (type) { + case BP_VAR_R: + zend_undefined_index(offset_key); + ZEND_FALLTHROUGH; + case BP_VAR_UNSET: + case BP_VAR_IS: + retval = &EG(uninitialized_zval); + break; + case BP_VAR_RW: + retval = zend_undefined_index_write(ht, offset_key); + break; + } + } + } else { + retval = zend_hash_lookup(ht, offset_key); + } + } else if (EXPECTED(Z_TYPE_P(dim) == IS_REFERENCE)) { + dim = Z_REFVAL_P(dim); + goto try_again; + } else { + zend_value val; + uint8_t t; + + if (type != BP_VAR_W && type != BP_VAR_RW) { + t = slow_index_convert(ht, dim, &val EXECUTE_DATA_CC); + } else { + t = slow_index_convert_w(ht, dim, &val EXECUTE_DATA_CC); + } + if (t == IS_STRING) { + offset_key = val.str; + goto str_index; + } else if (t == IS_LONG) { + hval = val.lval; + goto num_index; + } else { + retval = (type == BP_VAR_W || type == BP_VAR_RW) ? NULL : &EG(uninitialized_zval); + } + } + return retval; +} + +static zend_never_inline zval *ZEND_FASTCALL +zend_fetch_dimension_address_inner_RW_CONST(HashTable *ht, const zval *dim EXECUTE_DATA_DC) { + return zend_fetch_dimension_address_inner(ht, dim, IS_CONST, BP_VAR_RW EXECUTE_DATA_CC); +} + +static zend_never_inline zval *ZEND_FASTCALL zend_fetch_dimension_address_inner_RW(HashTable *ht, + const zval *dim EXECUTE_DATA_DC) { + return zend_fetch_dimension_address_inner(ht, dim, IS_TMP_VAR, BP_VAR_RW EXECUTE_DATA_CC); +} + +static zend_never_inline zval *ZEND_FASTCALL zend_fetch_dimension_address_inner_W(HashTable *ht, + const zval *dim EXECUTE_DATA_DC) { + return zend_fetch_dimension_address_inner(ht, dim, IS_TMP_VAR, BP_VAR_W EXECUTE_DATA_CC); +} + +static zend_never_inline zval *ZEND_FASTCALL +zend_fetch_dimension_address_inner_W_CONST(HashTable *ht, const zval *dim EXECUTE_DATA_DC) { + return zend_fetch_dimension_address_inner(ht, dim, IS_CONST, BP_VAR_W EXECUTE_DATA_CC); +} + +static zend_always_inline int zend_binary_op(zval *ret, zval *op1, zval *op2 OPLINE_DC) { + static const binary_op_type zend_binary_ops[] = {add_function, + sub_function, + mul_function, + div_function, + mod_function, + shift_left_function, + shift_right_function, + concat_function, + bitwise_or_function, + bitwise_and_function, + bitwise_xor_function, + pow_function}; + /* size_t cast makes GCC to better optimize 64-bit PIC code */ + size_t opcode = (size_t) opline->extended_value; + + return zend_binary_ops[opcode - ZEND_ADD](ret, op1, op2); +} + +static zend_never_inline void zend_binary_assign_op_typed_ref(zend_reference *ref, + zval *value OPLINE_DC EXECUTE_DATA_DC) { + zval z_copy; + + /* Make sure that in-place concatenation is used if the LHS is a string. */ + if (opline->extended_value == ZEND_CONCAT && Z_TYPE(ref->val) == IS_STRING) { + concat_function(&ref->val, &ref->val, value); + ZEND_ASSERT(Z_TYPE(ref->val) == IS_STRING && "Concat should return string"); + return; + } + + zend_binary_op(&z_copy, &ref->val, value OPLINE_CC); + if (EXPECTED(zend_verify_ref_assignable_zval(ref, &z_copy, EX_USES_STRICT_TYPES()))) { + zval_ptr_dtor(&ref->val); + ZVAL_COPY_VALUE(&ref->val, &z_copy); + } else { + zval_ptr_dtor(&z_copy); + } +}