diff --git a/NEWS b/NEWS index cadfda9bde6b..5044ac4f1a92 100644 --- a/NEWS +++ b/NEWS @@ -23,9 +23,9 @@ PHP NEWS . Fixed bug GH-20426 (Spoofchecker::setRestrictionLevel() error message suggests missing constants). (DanielEScherzer) -- Mbstring - . Fixed bug GH-20674 (Fix GH-20674 mb_decode_mimeheader does not handle - separator). (Yuya Hamada) +- JSON: + . Enriched JSON last error / exception message with error location. + (Juan Morales) - Fibers: . Fixed bug GH-20483 (ASAN stack overflow with fiber.stack_size INI @@ -35,8 +35,6 @@ PHP NEWS . ini_set() with mbstring.detect_order changes the order of mb_detect_order as intended, since mbstring.detect_order is an INI_ALL setting. (tobee94) . Added GB18030-2022 to default encoding list for zh-CN. (HeRaNO) - . Fixed bug GH-20674 (Fix GH-20674 mb_decode_mimeheader does not handle - separator). (Yuya Hamada) - Opcache: . Fixed bug GH-20051 (apache2 shutdowns when restart is requested during diff --git a/UPGRADING b/UPGRADING index 0ea423b32d38..338a3e5179a6 100644 --- a/UPGRADING +++ b/UPGRADING @@ -40,6 +40,10 @@ PHP 8.6 UPGRADE NOTES IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE identity fallbacks. It is supported from icu 63. +- JSON: + . Added extra info about error location to the JSON error messages returned + from json_last_error_msg() and JsonException message. + - Phar: . Overriding the getMTime() and getPathname() methods of SplFileInfo now influences the result of the phar buildFrom family of functions. diff --git a/ext/curl/curl.stub.php b/ext/curl/curl.stub.php index c9cda9c0d4b0..aadab8cb0b0d 100644 --- a/ext/curl/curl.stub.php +++ b/ext/curl/curl.stub.php @@ -3133,7 +3133,7 @@ */ const CURLOPT_TLS13_CIPHERS = UNKNOWN; -#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ /** * @var int * @cvalue CURLOPT_DOH_URL @@ -3769,7 +3769,7 @@ function curl_getinfo(CurlHandle $handle, ?int $option = null): mixed {} /** @refcount 1 */ function curl_init(?string $url = null): CurlHandle|false {} -#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ function curl_upkeep(CurlHandle $handle): bool {} #endif diff --git a/ext/curl/curl_arginfo.h b/ext/curl/curl_arginfo.h index 7e4a5789d599..25fac2948bf2 100644 --- a/ext/curl/curl_arginfo.h +++ b/ext/curl/curl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 2a2772e99deea07c0bc148e9715e6a960230cf4d */ + * Stub hash: 10ebdc94560ed19ecd6b61a11b3dab5d32989d66 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_close, 0, 1, IS_VOID, 0) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) @@ -49,7 +49,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_curl_init, 0, 0, CurlHandle, ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, url, IS_STRING, 1, "null") ZEND_END_ARG_INFO() -#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_upkeep, 0, 1, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) ZEND_END_ARG_INFO() @@ -157,7 +157,7 @@ ZEND_FUNCTION(curl_exec); ZEND_FUNCTION(curl_file_create); ZEND_FUNCTION(curl_getinfo); ZEND_FUNCTION(curl_init); -#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ ZEND_FUNCTION(curl_upkeep); #endif ZEND_FUNCTION(curl_multi_add_handle); @@ -196,7 +196,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(curl_file_create, arginfo_curl_file_create) ZEND_FE(curl_getinfo, arginfo_curl_getinfo) ZEND_FE(curl_init, arginfo_curl_init) -#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ ZEND_FE(curl_upkeep, arginfo_curl_upkeep) #endif ZEND_FE(curl_multi_add_handle, arginfo_curl_multi_add_handle) @@ -834,7 +834,7 @@ static void register_curl_symbols(int module_number) REGISTER_LONG_CONSTANT("CURLOPT_DISALLOW_USERNAME_IN_URL", CURLOPT_DISALLOW_USERNAME_IN_URL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CURLOPT_PROXY_TLS13_CIPHERS", CURLOPT_PROXY_TLS13_CIPHERS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CURLOPT_TLS13_CIPHERS", CURLOPT_TLS13_CIPHERS, CONST_PERSISTENT); -#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ REGISTER_LONG_CONSTANT("CURLOPT_DOH_URL", CURLOPT_DOH_URL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CURLOPT_UPKEEP_INTERVAL_MS", CURLOPT_UPKEEP_INTERVAL_MS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CURLOPT_UPLOAD_BUFFERSIZE", CURLOPT_UPLOAD_BUFFERSIZE, CONST_PERSISTENT); diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 5f69beac8e93..62a55b4df528 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -221,6 +221,54 @@ static zend_object *curl_clone_obj(zend_object *object); php_curl *init_curl_handle_into_zval(zval *curl); static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields); +struct php_curl_feature { + const char *name; + int bitmask; +}; + +/* To update on each new cURL release using src/main.c in cURL sources */ +static const struct php_curl_feature php_curl_features[] = { + { "AsynchDNS", CURL_VERSION_ASYNCHDNS }, + { "CharConv", CURL_VERSION_CONV }, + { "Debug", CURL_VERSION_DEBUG }, + { "GSS-Negotiate", CURL_VERSION_GSSNEGOTIATE }, + { "IDN", CURL_VERSION_IDN }, + { "IPv6", CURL_VERSION_IPV6 }, + { "krb4", CURL_VERSION_KERBEROS4 }, + { "Largefile", CURL_VERSION_LARGEFILE }, + { "libz", CURL_VERSION_LIBZ }, + { "NTLM", CURL_VERSION_NTLM }, + { "NTLMWB", CURL_VERSION_NTLM_WB }, + { "SPNEGO", CURL_VERSION_SPNEGO }, + { "SSL", CURL_VERSION_SSL }, + { "SSPI", CURL_VERSION_SSPI }, + { "TLS-SRP", CURL_VERSION_TLSAUTH_SRP }, + { "HTTP2", CURL_VERSION_HTTP2 }, + { "GSSAPI", CURL_VERSION_GSSAPI }, + { "KERBEROS5", CURL_VERSION_KERBEROS5 }, + { "UNIX_SOCKETS", CURL_VERSION_UNIX_SOCKETS }, + { "PSL", CURL_VERSION_PSL }, + { "HTTPS_PROXY", CURL_VERSION_HTTPS_PROXY }, + { "MULTI_SSL", CURL_VERSION_MULTI_SSL }, + { "BROTLI", CURL_VERSION_BROTLI }, +#if LIBCURL_VERSION_NUM >= 0x074001 /* Available since 7.64.1 */ + { "ALTSVC", CURL_VERSION_ALTSVC }, +#endif +#if LIBCURL_VERSION_NUM >= 0x074200 /* Available since 7.66.0 */ + { "HTTP3", CURL_VERSION_HTTP3 }, +#endif +#if LIBCURL_VERSION_NUM >= 0x074800 /* Available since 7.72.0 */ + { "UNICODE", CURL_VERSION_UNICODE }, + { "ZSTD", CURL_VERSION_ZSTD }, +#endif +#if LIBCURL_VERSION_NUM >= 0x074a00 /* Available since 7.74.0 */ + { "HSTS", CURL_VERSION_HSTS }, +#endif +#if LIBCURL_VERSION_NUM >= 0x074c00 /* Available since 7.76.0 */ + { "GSASL", CURL_VERSION_GSASL }, +#endif +}; + /* {{{ PHP_INI_BEGIN */ PHP_INI_BEGIN() PHP_INI_ENTRY("curl.cainfo", "", PHP_INI_SYSTEM, NULL) @@ -237,69 +285,17 @@ PHP_MINFO_FUNCTION(curl) d = curl_version_info(CURLVERSION_NOW); php_info_print_table_start(); - php_info_print_table_row(2, "cURL support", "enabled"); + php_info_print_table_row(2, "cURL support", "enabled"); php_info_print_table_row(2, "cURL Information", d->version); snprintf(str, sizeof(str), "%d", d->age); php_info_print_table_row(2, "Age", str); - /* To update on each new cURL release using src/main.c in cURL sources */ - /* make sure to sync this list with curl_version as well */ if (d->features) { - struct feat { - const char *name; - int bitmask; - }; - unsigned int i; - static const struct feat feats[] = { - {"AsynchDNS", CURL_VERSION_ASYNCHDNS}, - {"CharConv", CURL_VERSION_CONV}, - {"Debug", CURL_VERSION_DEBUG}, - {"GSS-Negotiate", CURL_VERSION_GSSNEGOTIATE}, - {"IDN", CURL_VERSION_IDN}, - {"IPv6", CURL_VERSION_IPV6}, - {"krb4", CURL_VERSION_KERBEROS4}, - {"Largefile", CURL_VERSION_LARGEFILE}, - {"libz", CURL_VERSION_LIBZ}, - {"NTLM", CURL_VERSION_NTLM}, - {"NTLMWB", CURL_VERSION_NTLM_WB}, - {"SPNEGO", CURL_VERSION_SPNEGO}, - {"SSL", CURL_VERSION_SSL}, - {"SSPI", CURL_VERSION_SSPI}, - {"TLS-SRP", CURL_VERSION_TLSAUTH_SRP}, - {"HTTP2", CURL_VERSION_HTTP2}, - {"GSSAPI", CURL_VERSION_GSSAPI}, - {"KERBEROS5", CURL_VERSION_KERBEROS5}, - {"UNIX_SOCKETS", CURL_VERSION_UNIX_SOCKETS}, - {"PSL", CURL_VERSION_PSL}, - {"HTTPS_PROXY", CURL_VERSION_HTTPS_PROXY}, - {"MULTI_SSL", CURL_VERSION_MULTI_SSL}, - {"BROTLI", CURL_VERSION_BROTLI}, -#if LIBCURL_VERSION_NUM >= 0x074001 /* Available since 7.64.1 */ - {"ALTSVC", CURL_VERSION_ALTSVC}, -#endif -#if LIBCURL_VERSION_NUM >= 0x074200 /* Available since 7.66.0 */ - {"HTTP3", CURL_VERSION_HTTP3}, -#endif -#if LIBCURL_VERSION_NUM >= 0x074800 /* Available since 7.72.0 */ - {"UNICODE", CURL_VERSION_UNICODE}, - {"ZSTD", CURL_VERSION_ZSTD}, -#endif -#if LIBCURL_VERSION_NUM >= 0x074a00 /* Available since 7.74.0 */ - {"HSTS", CURL_VERSION_HSTS}, -#endif -#if LIBCURL_VERSION_NUM >= 0x074c00 /* Available since 7.76.0 */ - {"GSASL", CURL_VERSION_GSASL}, -#endif - {NULL, 0} - }; - php_info_print_table_row(1, "Features"); - for(i=0; ifeatures & feats[i].bitmask ? "Yes" : "No"); - } + for (i = 0; i < sizeof(php_curl_features) / sizeof(php_curl_features[0]); i++) { + php_info_print_table_row(2, php_curl_features[i].name, d->features & php_curl_features[i].bitmask ? "Yes" : "No"); } } @@ -483,6 +479,7 @@ static HashTable *curl_get_gc(zend_object *object, zval **table, int *n) zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.prereq); } #endif + #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ if (ZEND_FCC_INITIALIZED(curl->handlers.sshhostkey)) { zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.sshhostkey); @@ -685,8 +682,8 @@ static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_ // gets called. Return CURL_PREREQFUNC_OK immediately in this case to avoid // zend_call_known_fcc() with an uninitialized FCC. if (!ZEND_FCC_INITIALIZED(ch->handlers.prereq)) { - return rval; - } + return rval; + } #if PHP_CURL_DEBUG fprintf(stderr, "curl_prereqfunction() called\n"); @@ -881,42 +878,42 @@ static int curl_debug(CURL *handle, curl_infotype type, char *data, size_t size, { php_curl *ch = (php_curl *)clientp; - #if PHP_CURL_DEBUG - fprintf(stderr, "curl_debug() called\n"); - fprintf(stderr, "type = %d, data = %s\n", type, data); - #endif +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_debug() called\n"); + fprintf(stderr, "type = %d, data = %s\n", type, data); +#endif // Implicitly store the headers for compatibility with CURLINFO_HEADER_OUT // used as a Curl option. Previously, setting CURLINFO_HEADER_OUT set curl_debug // as the CURLOPT_DEBUGFUNCTION and stored the debug data when type is set to // CURLINFO_HEADER_OUT. For backward compatibility, we now store the headers // but also call the user-callback function if available. - if (type == CURLINFO_HEADER_OUT) { - if (ch->header.str) { - zend_string_release_ex(ch->header.str, 0); - } - ch->header.str = zend_string_init(data, size, 0); - } + if (type == CURLINFO_HEADER_OUT) { + if (ch->header.str) { + zend_string_release_ex(ch->header.str, 0); + } + ch->header.str = zend_string_init(data, size, 0); + } - if (!ZEND_FCC_INITIALIZED(ch->handlers.debug)) { - return 0; - } + if (!ZEND_FCC_INITIALIZED(ch->handlers.debug)) { + return 0; + } - zval args[3]; + zval args[3]; - GC_ADDREF(&ch->std); - ZVAL_OBJ(&args[0], &ch->std); - ZVAL_LONG(&args[1], type); - ZVAL_STRINGL(&args[2], data, size); + GC_ADDREF(&ch->std); + ZVAL_OBJ(&args[0], &ch->std); + ZVAL_LONG(&args[1], type); + ZVAL_STRINGL(&args[2], data, size); - ch->in_callback = true; - zend_call_known_fcc(&ch->handlers.debug, NULL, /* param_count */ 3, args, /* named_params */ NULL); - ch->in_callback = false; + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.debug, NULL, /* param_count */ 3, args, /* named_params */ NULL); + ch->in_callback = false; - zval_ptr_dtor(&args[0]); - zval_ptr_dtor(&args[2]); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[2]); - return 0; + return 0; } /* }}} */ @@ -969,62 +966,12 @@ PHP_FUNCTION(curl_version) CAAL("features", d->features); /* Add an array of features */ { - struct feat { - const char *name; - int bitmask; - }; - unsigned int i; zval feature_list; - array_init(&feature_list); - - /* Sync this list with PHP_MINFO_FUNCTION(curl) as well */ - static const struct feat feats[] = { - {"AsynchDNS", CURL_VERSION_ASYNCHDNS}, - {"CharConv", CURL_VERSION_CONV}, - {"Debug", CURL_VERSION_DEBUG}, - {"GSS-Negotiate", CURL_VERSION_GSSNEGOTIATE}, - {"IDN", CURL_VERSION_IDN}, - {"IPv6", CURL_VERSION_IPV6}, - {"krb4", CURL_VERSION_KERBEROS4}, - {"Largefile", CURL_VERSION_LARGEFILE}, - {"libz", CURL_VERSION_LIBZ}, - {"NTLM", CURL_VERSION_NTLM}, - {"NTLMWB", CURL_VERSION_NTLM_WB}, - {"SPNEGO", CURL_VERSION_SPNEGO}, - {"SSL", CURL_VERSION_SSL}, - {"SSPI", CURL_VERSION_SSPI}, - {"TLS-SRP", CURL_VERSION_TLSAUTH_SRP}, - {"HTTP2", CURL_VERSION_HTTP2}, - {"GSSAPI", CURL_VERSION_GSSAPI}, - {"KERBEROS5", CURL_VERSION_KERBEROS5}, - {"UNIX_SOCKETS", CURL_VERSION_UNIX_SOCKETS}, - {"PSL", CURL_VERSION_PSL}, - {"HTTPS_PROXY", CURL_VERSION_HTTPS_PROXY}, - {"MULTI_SSL", CURL_VERSION_MULTI_SSL}, - {"BROTLI", CURL_VERSION_BROTLI}, -#if LIBCURL_VERSION_NUM >= 0x074001 /* Available since 7.64.1 */ - {"ALTSVC", CURL_VERSION_ALTSVC}, -#endif -#if LIBCURL_VERSION_NUM >= 0x074200 /* Available since 7.66.0 */ - {"HTTP3", CURL_VERSION_HTTP3}, -#endif -#if LIBCURL_VERSION_NUM >= 0x074800 /* Available since 7.72.0 */ - {"UNICODE", CURL_VERSION_UNICODE}, - {"ZSTD", CURL_VERSION_ZSTD}, -#endif -#if LIBCURL_VERSION_NUM >= 0x074a00 /* Available since 7.74.0 */ - {"HSTS", CURL_VERSION_HSTS}, -#endif -#if LIBCURL_VERSION_NUM >= 0x074c00 /* Available since 7.76.0 */ - {"GSASL", CURL_VERSION_GSASL}, -#endif - }; - for(i = 0; i < sizeof(feats) / sizeof(feats[0]); i++) { - if (feats[i].name) { - add_assoc_bool(&feature_list, feats[i].name, d->features & feats[i].bitmask ? true : false); - } + array_init_size(&feature_list, sizeof(php_curl_features) / sizeof(php_curl_features[0])); + for (i = 0; i < sizeof(php_curl_features) / sizeof(php_curl_features[0]); i++) { + add_assoc_bool(&feature_list, php_curl_features[i].name, d->features & php_curl_features[i].bitmask ? true : false); } CAAZ("feature_list", &feature_list); @@ -1753,7 +1700,7 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue case CURLOPT_DNS_SHUFFLE_ADDRESSES: case CURLOPT_HAPROXYPROTOCOL: case CURLOPT_DISALLOW_USERNAME_IN_URL: -#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ case CURLOPT_UPKEEP_INTERVAL_MS: case CURLOPT_UPLOAD_BUFFERSIZE: #endif @@ -1917,7 +1864,7 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue case CURLOPT_DNS_LOCAL_IP6: case CURLOPT_XOAUTH2_BEARER: case CURLOPT_UNIX_SOCKET_PATH: -#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ case CURLOPT_DOH_URL: #endif #if LIBCURL_VERSION_NUM >= 0x074a00 /* Available since 7.74.0 */ @@ -2222,9 +2169,9 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue case CURLINFO_HEADER_OUT: if (ZEND_FCC_INITIALIZED(ch->handlers.debug)) { - zend_value_error("CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set"); - return FAILURE; - } + zend_value_error("CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set"); + return FAILURE; + } if (zend_is_true(zvalue)) { curl_easy_setopt(ch->cp, CURLOPT_DEBUGFUNCTION, curl_debug); @@ -2931,11 +2878,13 @@ static void _php_curl_reset_handlers(php_curl *ch) if (ZEND_FCC_INITIALIZED(ch->handlers.debug)) { zend_fcc_dtor(&ch->handlers.debug); } + #if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) { zend_fcc_dtor(&ch->handlers.prereq); } #endif + #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) { zend_fcc_dtor(&ch->handlers.sshhostkey); @@ -3042,7 +2991,7 @@ PHP_FUNCTION(curl_pause) } /* }}} */ -#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ /* {{{ perform connection upkeep checks */ PHP_FUNCTION(curl_upkeep) { @@ -3061,5 +3010,5 @@ PHP_FUNCTION(curl_upkeep) RETURN_BOOL(error == CURLE_OK); } -/*}}} */ +/* }}} */ #endif diff --git a/ext/curl/multi.c b/ext/curl/multi.c index a16155759aed..3e6142466e98 100644 --- a/ext/curl/multi.c +++ b/ext/curl/multi.c @@ -398,7 +398,6 @@ PHP_FUNCTION(curl_multi_strerror) } /* }}} */ - static int _php_server_push_callback(CURL *parent_ch, CURL *easy, size_t num_headers, struct curl_pushheaders *push_headers, void *userp) /* {{{ */ { php_curl *ch; diff --git a/ext/json/json.c b/ext/json/json.c index 9f91d39594ec..079f67a5c400 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -63,6 +63,8 @@ static PHP_GINIT_FUNCTION(json) #endif json_globals->encoder_depth = 0; json_globals->error_code = 0; + json_globals->error_line = 0; + json_globals->error_column = 0; json_globals->encode_max_depth = PHP_JSON_PARSER_DEFAULT_DEPTH; } /* }}} */ @@ -70,6 +72,8 @@ static PHP_GINIT_FUNCTION(json) static PHP_RINIT_FUNCTION(json) { JSON_G(error_code) = 0; + JSON_G(error_line) = 0; + JSON_G(error_column) = 0; return SUCCESS; } @@ -177,6 +181,18 @@ static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ } /* }}} */ +static zend_string *php_json_get_error_msg_with_location(php_json_error_code error_code, size_t line, size_t column) /* {{{ */ +{ + const char *base_msg = php_json_get_error_msg(error_code); + + if (line > 0 && column > 0) { + return zend_strpprintf(0, "%s near location %zu:%zu", base_msg, line, column); + } + + return zend_string_init(base_msg, strlen(base_msg), 0); +} +/* }}} */ + PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth) /* {{{ */ { php_json_parser parser; @@ -185,10 +201,17 @@ PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, if (php_json_yyparse(&parser)) { php_json_error_code error_code = php_json_parser_error_code(&parser); + size_t error_line = php_json_parser_error_line(&parser); + size_t error_column = php_json_parser_error_column(&parser); + if (!(options & PHP_JSON_THROW_ON_ERROR)) { JSON_G(error_code) = error_code; + JSON_G(error_line) = error_line; + JSON_G(error_column) = error_column; } else { - zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(error_code), error_code); + zend_string *error_msg = php_json_get_error_msg_with_location(error_code, error_line, error_column); + zend_throw_exception(php_json_exception_ce, ZSTR_VAL(error_msg), error_code); + zend_string_release(error_msg); } RETVAL_NULL(); return FAILURE; @@ -208,7 +231,12 @@ PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_lon if (php_json_yyparse(&parser)) { php_json_error_code error_code = php_json_parser_error_code(&parser); + size_t error_line = php_json_parser_error_line(&parser); + size_t error_column = php_json_parser_error_column(&parser); + JSON_G(error_code) = error_code; + JSON_G(error_line) = error_line; + JSON_G(error_column) = error_column; return false; } @@ -274,11 +302,15 @@ PHP_FUNCTION(json_decode) if (!(options & PHP_JSON_THROW_ON_ERROR)) { JSON_G(error_code) = PHP_JSON_ERROR_NONE; + JSON_G(error_line) = 0; + JSON_G(error_column) = 0; } if (!str_len) { if (!(options & PHP_JSON_THROW_ON_ERROR)) { JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX; + JSON_G(error_line) = 0; + JSON_G(error_column) = 0; } else { zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX); } @@ -331,10 +363,14 @@ PHP_FUNCTION(json_validate) if (!str_len) { JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX; + JSON_G(error_line) = 0; + JSON_G(error_column) = 0; RETURN_FALSE; } JSON_G(error_code) = PHP_JSON_ERROR_NONE; + JSON_G(error_line) = 0; + JSON_G(error_column) = 0; if (depth <= 0) { zend_argument_value_error(2, "must be greater than 0"); @@ -364,6 +400,10 @@ PHP_FUNCTION(json_last_error_msg) { ZEND_PARSE_PARAMETERS_NONE(); - RETURN_STRING(php_json_get_error_msg(JSON_G(error_code))); + RETVAL_STR(php_json_get_error_msg_with_location( + JSON_G(error_code), + JSON_G(error_line), + JSON_G(error_column) + )); } /* }}} */ diff --git a/ext/json/json_parser.y b/ext/json/json_parser.y index d570cddc91e4..2fd4edfe3693 100644 --- a/ext/json/json_parser.y +++ b/ext/json/json_parser.y @@ -41,6 +41,7 @@ int json_yydebug = 1; } +%locations %define api.prefix {php_json_yy} %define api.pure full %param { php_json_parser *parser } @@ -49,7 +50,6 @@ int json_yydebug = 1; zval value; } - %token PHP_JSON_T_NUL %token PHP_JSON_T_TRUE %token PHP_JSON_T_FALSE @@ -66,8 +66,8 @@ int json_yydebug = 1; %destructor { zval_ptr_dtor_nogc(&$$); } %code { -static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser); -static void php_json_yyerror(php_json_parser *parser, char const *msg); +static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser); +static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg); static int php_json_parser_array_create(php_json_parser *parser, zval *array); static int php_json_parser_object_create(php_json_parser *parser, zval *array); @@ -277,7 +277,7 @@ static int php_json_parser_object_update_validate(php_json_parser *parser, zval return SUCCESS; } -static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser) +static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser) { int token = php_json_scan(&parser->scanner); @@ -293,10 +293,15 @@ static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser) value->value = parser->scanner.value; } + location->first_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_column); + location->first_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_line); + location->last_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_column); + location->last_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_line); + return token; } -static void php_json_yyerror(php_json_parser *parser, char const *msg) +static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg) { if (!parser->scanner.errcode) { parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; @@ -308,6 +313,16 @@ PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parse return parser->scanner.errcode; } +PHP_JSON_API size_t php_json_parser_error_line(const php_json_parser *parser) +{ + return parser->scanner.errloc.first_line; +} + +PHP_JSON_API size_t php_json_parser_error_column(const php_json_parser *parser) +{ + return parser->scanner.errloc.first_column; +} + static const php_json_parser_methods default_parser_methods = { php_json_parser_array_create, diff --git a/ext/json/json_scanner.re b/ext/json/json_scanner.re index 0debb3b03cb2..d6eaaf65b2e1 100644 --- a/ext/json/json_scanner.re +++ b/ext/json/json_scanner.re @@ -52,6 +52,8 @@ #define PHP_JSON_INT_MAX_LENGTH (MAX_LENGTH_OF_LONG - 1) +#define PHP_JSON_TOKEN_LENGTH() ((size_t) (s->cursor - s->token)) +#define PHP_JSON_TOKEN_LOCATION(location) (s)->errloc.location static void php_json_scanner_copy_string(php_json_scanner *s, size_t esc_size) { @@ -96,6 +98,10 @@ void php_json_scanner_init(php_json_scanner *s, const char *str, size_t str_len, s->cursor = (php_json_ctype *) str; s->limit = (php_json_ctype *) str + str_len; s->options = options; + PHP_JSON_TOKEN_LOCATION(first_column) = 1; + PHP_JSON_TOKEN_LOCATION(first_line) = 1; + PHP_JSON_TOKEN_LOCATION(last_column) = 1; + PHP_JSON_TOKEN_LOCATION(last_line) = 1; PHP_JSON_CONDITION_SET(JS); } @@ -104,6 +110,8 @@ int php_json_scan(php_json_scanner *s) ZVAL_NULL(&s->value); std: + PHP_JSON_TOKEN_LOCATION(first_column) = s->errloc.last_column; + PHP_JSON_TOKEN_LOCATION(first_line) = s->errloc.last_line; s->token = s->cursor; /*!re2c @@ -149,27 +157,50 @@ std: UTF16_3 = UTFPREF ( ( ( HEXC | [efEF] ) HEX ) | ( [dD] HEX7 ) ) HEX{2} ; UTF16_4 = UTFPREF [dD] [89abAB] HEX{2} UTFPREF [dD] [c-fC-F] HEX{2} ; - "{" { return '{'; } - "}" { return '}'; } - "[" { return '['; } - "]" { return ']'; } - ":" { return ':'; } - "," { return ','; } + "{" { + PHP_JSON_TOKEN_LOCATION(last_column)++; + return '{'; + } + "}" { + PHP_JSON_TOKEN_LOCATION(last_column)++; + return '}'; + } + "[" { + PHP_JSON_TOKEN_LOCATION(last_column)++; + return '['; + } + "]" { + PHP_JSON_TOKEN_LOCATION(last_column)++; + return ']'; + } + ":" { + PHP_JSON_TOKEN_LOCATION(last_column)++; + return ':'; + } + "," { + PHP_JSON_TOKEN_LOCATION(last_column)++; + return ','; + } "null" { + PHP_JSON_TOKEN_LOCATION(last_column) += 4; ZVAL_NULL(&s->value); return PHP_JSON_T_NUL; } "true" { + PHP_JSON_TOKEN_LOCATION(last_column) += 4; ZVAL_TRUE(&s->value); return PHP_JSON_T_TRUE; } "false" { + PHP_JSON_TOKEN_LOCATION(last_column) += 5; ZVAL_FALSE(&s->value); return PHP_JSON_T_FALSE; } INT { bool bigint = 0, negative = s->token[0] == '-'; - size_t digits = (size_t) (s->cursor - s->token - negative); + size_t digits = PHP_JSON_TOKEN_LENGTH(); + PHP_JSON_TOKEN_LOCATION(last_column) += digits; + digits -= negative; if (digits >= PHP_JSON_INT_MAX_LENGTH) { if (digits == PHP_JSON_INT_MAX_LENGTH) { int cmp = strncmp((char *) (s->token + negative), LONG_MIN_DIGITS, PHP_JSON_INT_MAX_LENGTH); @@ -192,10 +223,19 @@ std: } } FLOAT|EXP { + PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH(); ZVAL_DOUBLE(&s->value, zend_strtod((char *) s->token, NULL)); return PHP_JSON_T_DOUBLE; } - NL|WS { goto std; } + NL { + PHP_JSON_TOKEN_LOCATION(last_line)++; + PHP_JSON_TOKEN_LOCATION(last_column) = 1; + goto std; + } + WS { + PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH(); + goto std; + } EOI { if (s->limit < s->cursor) { return PHP_JSON_T_EOI; @@ -205,6 +245,7 @@ std: } } ["] { + PHP_JSON_TOKEN_LOCATION(last_column)++; s->str_start = s->cursor; s->str_esc = 0; s->utf8_invalid = 0; @@ -229,18 +270,22 @@ std: return PHP_JSON_T_ERROR; } UTF16_1 { + PHP_JSON_TOKEN_LOCATION(last_column) += 1; s->str_esc += 5; PHP_JSON_CONDITION_GOTO(STR_P1); } UTF16_2 { + PHP_JSON_TOKEN_LOCATION(last_column) += 1; s->str_esc += 4; PHP_JSON_CONDITION_GOTO(STR_P1); } UTF16_3 { + PHP_JSON_TOKEN_LOCATION(last_column) += 1; s->str_esc += 3; PHP_JSON_CONDITION_GOTO(STR_P1); } UTF16_4 { + PHP_JSON_TOKEN_LOCATION(last_column) += 1; s->str_esc += 8; PHP_JSON_CONDITION_GOTO(STR_P1); } @@ -249,6 +294,7 @@ std: return PHP_JSON_T_ERROR; } ESC { + PHP_JSON_TOKEN_LOCATION(last_column) += 2; s->str_esc++; PHP_JSON_CONDITION_GOTO(STR_P1); } @@ -257,6 +303,7 @@ std: return PHP_JSON_T_ERROR; } ["] { + PHP_JSON_TOKEN_LOCATION(last_column)++; zend_string *str; size_t len = (size_t)(s->cursor - s->str_start - s->str_esc - 1 + s->utf8_invalid_count); if (len == 0) { @@ -277,7 +324,22 @@ std: return PHP_JSON_T_STRING; } } - UTF8 { PHP_JSON_CONDITION_GOTO(STR_P1); } + UTF8_1 { + PHP_JSON_TOKEN_LOCATION(last_column)++; + PHP_JSON_CONDITION_GOTO(STR_P1); + } + UTF8_2 { + PHP_JSON_TOKEN_LOCATION(last_column) += 1; + PHP_JSON_CONDITION_GOTO(STR_P1); + } + UTF8_3 { + PHP_JSON_TOKEN_LOCATION(last_column) += 1; + PHP_JSON_CONDITION_GOTO(STR_P1); + } + UTF8_4 { + PHP_JSON_TOKEN_LOCATION(last_column) += 1; + PHP_JSON_CONDITION_GOTO(STR_P1); + } ANY { if (s->options & (PHP_JSON_INVALID_UTF8_IGNORE | PHP_JSON_INVALID_UTF8_SUBSTITUTE)) { if (s->options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) { @@ -295,7 +357,6 @@ std: s->errcode = PHP_JSON_ERROR_UTF8; return PHP_JSON_T_ERROR; } - UTF16_1 { int utf16 = php_json_ucs2_to_int(s, 2); PHP_JSON_SCANNER_COPY_UTF(); diff --git a/ext/json/php_json.h b/ext/json/php_json.h index b79c7c836f7a..bbe8be9d60ad 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -86,6 +86,8 @@ ZEND_BEGIN_MODULE_GLOBALS(json) int encoder_depth; int encode_max_depth; php_json_error_code error_code; + size_t error_line; + size_t error_column; ZEND_END_MODULE_GLOBALS(json) PHP_JSON_API ZEND_EXTERN_MODULE_GLOBALS(json) diff --git a/ext/json/php_json_parser.h b/ext/json/php_json_parser.h index 8aedce9ac55d..8fee3d11c6bf 100644 --- a/ext/json/php_json_parser.h +++ b/ext/json/php_json_parser.h @@ -50,12 +50,20 @@ typedef struct _php_json_parser_methods { php_json_parser_func_object_end_t object_end; } php_json_parser_methods; + typedef struct _php_json_parser_location { + size_t first_line; + size_t first_column; + size_t last_line; + size_t last_column; +} php_json_parser_location; + struct _php_json_parser { php_json_scanner scanner; zval *return_value; int depth; int max_depth; php_json_parser_methods methods; + php_json_parser_location *location; }; PHP_JSON_API void php_json_parser_init_ex( @@ -77,6 +85,10 @@ PHP_JSON_API void php_json_parser_init( PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parser *parser); +PHP_JSON_API size_t php_json_parser_error_line(const php_json_parser *parser); + +PHP_JSON_API size_t php_json_parser_error_column(const php_json_parser *parser); + PHP_JSON_API int php_json_parse(php_json_parser *parser); int php_json_yyparse(php_json_parser *parser); diff --git a/ext/json/php_json_scanner.h b/ext/json/php_json_scanner.h index a49be68cd632..a6de149391dc 100644 --- a/ext/json/php_json_scanner.h +++ b/ext/json/php_json_scanner.h @@ -22,6 +22,17 @@ typedef unsigned char php_json_ctype; +typedef struct _php_json_error_location { + /** first column of the error */ + size_t first_column; + /** first line of the error */ + size_t first_line; + /** last column of the error */ + size_t last_column; + /** last line of the error */ + size_t last_line; +} php_json_error_location; + typedef struct _php_json_scanner { php_json_ctype *cursor; /* cursor position */ php_json_ctype *token; /* token position */ @@ -35,10 +46,12 @@ typedef struct _php_json_scanner { int state; /* condition state */ int options; /* options */ php_json_error_code errcode; /* error type if there is an error */ + php_json_error_location errloc; /* error location */ int utf8_invalid; /* whether utf8 is invalid */ int utf8_invalid_count; /* number of extra character for invalid utf8 */ } php_json_scanner; +#define PHP_JSON_SCANNER_LOCATION(scanner, slocation) (scanner).errloc.slocation void php_json_scanner_init(php_json_scanner *scanner, const char *str, size_t str_len, int options); int php_json_scan(php_json_scanner *s); diff --git a/ext/json/tests/007.phpt b/ext/json/tests/007.phpt index dea641317e97..9a5ae654d636 100644 --- a/ext/json/tests/007.phpt +++ b/ext/json/tests/007.phpt @@ -24,14 +24,14 @@ int(0) string(8) "No error" NULL int(1) -string(28) "Maximum stack depth exceeded" +string(46) "Maximum stack depth exceeded near location 1:2" NULL int(2) -string(42) "State mismatch (invalid or malformed JSON)" +string(60) "State mismatch (invalid or malformed JSON) near location 1:3" NULL int(3) -string(53) "Control character error, possibly incorrectly encoded" +string(71) "Control character error, possibly incorrectly encoded near location 1:2" NULL int(4) -string(12) "Syntax error" +string(30) "Syntax error near location 1:3" Done diff --git a/ext/json/tests/bug62010.phpt b/ext/json/tests/bug62010.phpt index 2591231dcdda..862d7dc7e2c0 100644 --- a/ext/json/tests/bug62010.phpt +++ b/ext/json/tests/bug62010.phpt @@ -10,4 +10,4 @@ var_dump(json_last_error_msg()); --EXPECT-- NULL bool(true) -string(50) "Single unpaired UTF-16 surrogate in unicode escape" +string(68) "Single unpaired UTF-16 surrogate in unicode escape near location 1:1" diff --git a/ext/json/tests/bug68546.phpt b/ext/json/tests/bug68546.phpt index 8835a72c5eac..1847eabf3a8e 100644 --- a/ext/json/tests/bug68546.phpt +++ b/ext/json/tests/bug68546.phpt @@ -5,7 +5,7 @@ Bug #68546 (json_decode() Fatal error: Cannot access property started with '\0') var_dump(json_decode('{"key": {"\u0000": "aa"}}')); var_dump(json_last_error() === JSON_ERROR_INVALID_PROPERTY_NAME); -var_dump(json_decode('[{"key1": 0, "\u0000": 1}]')); +var_dump(json_decode('[{"key1": 0, "\u1234": 1, "\u0000": 1}]')); var_dump(json_last_error() === JSON_ERROR_INVALID_PROPERTY_NAME); var_dump(json_last_error_msg()); @@ -16,5 +16,5 @@ NULL bool(true) NULL bool(true) -string(36) "The decoded property name is invalid" +string(55) "The decoded property name is invalid near location 1:27" Done diff --git a/ext/json/tests/json_decode_exceptions.phpt b/ext/json/tests/json_decode_exceptions.phpt index 7dc2e7408a02..d53941682e45 100644 --- a/ext/json/tests/json_decode_exceptions.phpt +++ b/ext/json/tests/json_decode_exceptions.phpt @@ -13,7 +13,7 @@ try { --EXPECTF-- object(JsonException)#1 (7) { ["message":protected]=> - string(12) "Syntax error" + string(30) "Syntax error near location 1:2" ["string":"Exception":private]=> string(0) "" ["code":protected]=> diff --git a/ext/json/tests/json_last_error_msg_error_location_001.phpt b/ext/json/tests/json_last_error_msg_error_location_001.phpt new file mode 100644 index 000000000000..e0553f9f7d65 --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_001.phpt @@ -0,0 +1,121 @@ +--TEST-- +json_last_error_msg() - Error location reporting with ASCII characters +--FILE-- + +--EXPECT-- +Testing errors at various locations with ASCII characters + +Error at position 1:1: +bool(false) +int(4) +string(30) "Syntax error near location 1:1" + +Error at position 1:10: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error at position 1:9: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + +Error at position 1:16: +bool(false) +int(4) +string(31) "Syntax error near location 1:16" + +Error at position 1:15: +bool(false) +int(4) +string(31) "Syntax error near location 1:15" + +Error at position 1:10: +bool(false) +int(4) +string(31) "Syntax error near location 1:10" + +Error at position 1:7: +bool(false) +int(4) +string(30) "Syntax error near location 1:7" + +Error at position 1:2: +bool(false) +int(4) +string(30) "Syntax error near location 1:2" + +Error at position 1:16: +bool(false) +int(4) +string(31) "Syntax error near location 1:16" + +Error at position 1:4: +bool(false) +int(4) +string(30) "Syntax error near location 1:4" + +Error at position 1:10: +bool(false) +int(4) +string(31) "Syntax error near location 1:10" + +Error at position 1:10: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + diff --git a/ext/json/tests/json_last_error_msg_error_location_002.phpt b/ext/json/tests/json_last_error_msg_error_location_002.phpt new file mode 100644 index 000000000000..df7fc981ccba --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_002.phpt @@ -0,0 +1,103 @@ +--TEST-- +json_last_error_msg() - Error location reporting with Unicode UTF-8 characters +--FILE-- + +--EXPECT-- +Testing error locations with Unicode UTF-8 characters + +Error after Japanese characters: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:12" + +Error after Russian characters: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:9" + +Error after Chinese characters: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:8" + +Error after Arabic characters: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:9" + +Error after Emoji: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:11" + +Error in mixed ASCII and UTF-8: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:27" + +Error with UTF-8 escaped sequences: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error in object with multiple UTF-8 keys: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:22" + +Error in array with UTF-8 strings: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:18" + +Error in nested object with UTF-8: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:15" + diff --git a/ext/json/tests/json_last_error_msg_error_location_003.phpt b/ext/json/tests/json_last_error_msg_error_location_003.phpt new file mode 100644 index 000000000000..ec5e6b9b4d65 --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_003.phpt @@ -0,0 +1,72 @@ +--TEST-- +json_last_error_msg() - Error location reporting with multi-line JSON +--FILE-- + +--EXPECT-- +Testing error locations in multi-line JSON + +Error on line 2, column 13: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 2:13" + +Error on line 3, column 12: +bool(false) +int(4) +string(31) "Syntax error near location 3:12" + +Error on line 5, column 26: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 5:26" + +Error on line 7, column 1: +bool(false) +int(4) +string(30) "Syntax error near location 7:1" + diff --git a/ext/json/tests/json_last_error_msg_error_location_004.phpt b/ext/json/tests/json_last_error_msg_error_location_004.phpt new file mode 100644 index 000000000000..165449600fb3 --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_004.phpt @@ -0,0 +1,93 @@ +--TEST-- +json_last_error_msg() - Error location reporting with deeply nested structures +--FILE-- + +--EXPECT-- +Testing error locations in deeply nested structures + +Error in deeply nested object: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:31" + +Error in deeply nested array: +bool(true) +int(0) +string(8) "No error" + +Error in mixed nested structures: +bool(true) +int(0) +string(8) "No error" + +Error at end of deep nesting: +bool(true) +int(0) +string(8) "No error" + +Error in middle of deep nesting: +bool(false) +int(4) +string(31) "Syntax error near location 1:21" + +Error in complex structure: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:93" + +Error in array of objects: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:68" + +Error in object with array values: +bool(false) +int(2) +string(61) "State mismatch (invalid or malformed JSON) near location 1:82" + diff --git a/ext/json/tests/json_last_error_msg_error_location_005.phpt b/ext/json/tests/json_last_error_msg_error_location_005.phpt new file mode 100644 index 000000000000..d12ce387e73e --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_005.phpt @@ -0,0 +1,103 @@ +--TEST-- +json_last_error_msg() - Error location reporting with UTF-16 surrogate pairs +--FILE-- + +--EXPECT-- +Testing error locations with UTF-16 surrogate pairs and escape sequences + +Error after UTF-16 escaped emoji: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:11" + +Error after multiple UTF-16 pairs: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error with mixed UTF-8 and UTF-16: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:11" + +Error with UTF-16 in key: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:9" + +Error with multiple UTF-16 keys: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:22" + +Error with BMP characters: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error with supplementary plane: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:11" + +Error in array with UTF-16: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:12" + +Error in nested structure with UTF-16: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:18" + +Error with UTF-16 and control chars: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + diff --git a/ext/json/tests/json_last_error_msg_error_location_006.phpt b/ext/json/tests/json_last_error_msg_error_location_006.phpt new file mode 100644 index 000000000000..e6aab1af8f27 --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_006.phpt @@ -0,0 +1,152 @@ +--TEST-- +json_last_error_msg() - Error location reporting edge cases +--FILE-- + +--EXPECT-- +Testing error location edge cases + +Error at position 1:1: +bool(false) +int(4) +string(12) "Syntax error" + +Error at position 1:1 with invalid char: +bool(false) +int(4) +string(30) "Syntax error near location 1:1" + +Error after leading whitespace: +bool(false) +int(4) +string(30) "Syntax error near location 1:5" + +Error with tabs and spaces: +bool(false) +int(4) +string(30) "Syntax error near location 2:3" + +Error after multiple newlines: +bool(false) +int(4) +string(30) "Syntax error near location 4:2" + +Error at end of long string: +bool(false) +int(4) +string(33) "Syntax error near location 1:1011" + +Error with very long key: +bool(false) +int(3) +string(73) "Control character error, possibly incorrectly encoded near location 1:506" + +Error after empty object: +bool(false) +int(4) +string(30) "Syntax error near location 1:3" + +Error after empty array: +bool(false) +int(4) +string(30) "Syntax error near location 1:3" + +Error with multiple root values: +bool(false) +int(4) +string(30) "Syntax error near location 1:3" + +Error after valid number: +bool(false) +int(4) +string(30) "Syntax error near location 1:4" + +Error after valid boolean: +bool(false) +int(4) +string(30) "Syntax error near location 1:5" + +Error after valid null: +bool(false) +int(4) +string(30) "Syntax error near location 1:5" + +Error after valid string: +bool(false) +int(4) +string(30) "Syntax error near location 1:7" + +Error with mixed whitespace: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 3:2" + diff --git a/ext/json/tests/json_last_error_msg_error_location_007.phpt b/ext/json/tests/json_last_error_msg_error_location_007.phpt new file mode 100644 index 000000000000..0e24889bbbbe --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_007.phpt @@ -0,0 +1,178 @@ +--TEST-- +json_last_error_msg() - Error location with various error types +--FILE-- + +--EXPECT-- +Testing error locations with different error types + +State mismatch - expected value: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + +State mismatch - expected key: +bool(false) +int(4) +string(30) "Syntax error near location 1:2" + +State mismatch - trailing comma in object: +bool(false) +int(4) +string(31) "Syntax error near location 1:17" + +State mismatch - trailing comma in array: +bool(false) +int(4) +string(31) "Syntax error near location 1:10" + +Invalid number format - leading zero: +bool(false) +int(4) +string(31) "Syntax error near location 1:10" + +Invalid number format - multiple decimals: +bool(false) +int(4) +string(31) "Syntax error near location 1:12" + +Invalid number format - incomplete exponent: +bool(false) +int(4) +string(31) "Syntax error near location 1:10" + +Invalid number format - double sign: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + +Unclosed string: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:9" + +Invalid escape sequence: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + +Incomplete unicode escape: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + +Invalid unicode escape: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + +Invalid true keyword: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + +Invalid false keyword: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + +Invalid null keyword: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + +Mismatched brackets - ] instead of }: +bool(false) +int(2) +string(61) "State mismatch (invalid or malformed JSON) near location 1:14" + +Mismatched brackets - } instead of ]: +bool(false) +int(2) +string(60) "State mismatch (invalid or malformed JSON) near location 1:7" + +Extra closing bracket: +bool(false) +int(4) +string(31) "Syntax error near location 1:15" + +Missing comma between elements: +bool(false) +int(4) +string(30) "Syntax error near location 1:4" + +Missing comma between object properties: +bool(false) +int(4) +string(30) "Syntax error near location 1:9" + diff --git a/ext/json/tests/json_last_error_msg_error_location_008.phpt b/ext/json/tests/json_last_error_msg_error_location_008.phpt new file mode 100644 index 000000000000..4d8a1012316b --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_008.phpt @@ -0,0 +1,182 @@ +--TEST-- +json_last_error_msg() - Error location with mixed UTF-8 multi-byte characters +--FILE-- + +--EXPECT-- +Testing error locations with various UTF-8 multi-byte character widths + +Error with 2-byte UTF-8 (Latin Extended): +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error with 2-byte UTF-8 (Greek): +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:14" + +Error with 2-byte UTF-8 (Cyrillic): +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:12" + +Error with 3-byte UTF-8 (Chinese): +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:8" + +Error with 3-byte UTF-8 (Japanese Hiragana): +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error with 3-byte UTF-8 (Japanese Katakana): +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error with 3-byte UTF-8 (Korean): +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:8" + +Error with 4-byte UTF-8 (Emoji faces): +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:11" + +Error with 4-byte UTF-8 (Emoji objects): +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:13" + +Error with 4-byte UTF-8 (Mathematical symbols): +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error with mixed 1-2-3 byte UTF-8: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:9" + +Error with mixed 2-3-4 byte UTF-8: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:9" + +Error with all byte widths: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:9" + +Error with UTF-8 key at start: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:7" + +Error with multiple UTF-8 keys: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:35" + +Error in array with mixed UTF-8: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:25" + +Error in nested structure with various UTF-8: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:22" + +Error with combining diacritical marks: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error with Hebrew: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + +Error with Arabic with diacritics: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:11" + diff --git a/ext/json/tests/json_last_error_msg_error_location_009.phpt b/ext/json/tests/json_last_error_msg_error_location_009.phpt new file mode 100644 index 000000000000..406179693ef6 --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_009.phpt @@ -0,0 +1,110 @@ +--TEST-- +json_last_error_msg() - Error location with depth errors and complex nesting +--FILE-- + +--EXPECT-- +Testing error locations with depth-related issues + +Max depth error at specific location: +bool(false) +int(1) +string(47) "Maximum stack depth exceeded near location 1:21" + +Max depth error in array: +bool(false) +int(1) +string(46) "Maximum stack depth exceeded near location 1:5" + +Max depth error with mixed structures: +bool(false) +int(1) +string(46) "Maximum stack depth exceeded near location 1:7" + +Syntax error at deep nesting level: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:31" + +Syntax error in deep array: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:6" + +Error after valid deep structure: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:48" + +Error in middle of nested structure: +bool(false) +int(4) +string(31) "Syntax error near location 1:29" + +Error in array with nested objects: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:30" + +Error in deep UTF-8 structure: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:16" + +Valid deep structure within limit: +bool(true) +int(0) +string(8) "No error" + diff --git a/ext/json/tests/json_last_error_msg_error_location_010.phpt b/ext/json/tests/json_last_error_msg_error_location_010.phpt new file mode 100644 index 000000000000..108570205838 --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_010.phpt @@ -0,0 +1,164 @@ +--TEST-- +json_last_error_msg() - Error location with whitespace variations +--FILE-- + +--EXPECT-- +Testing error locations with various whitespace patterns + +Error after multiple spaces: +bool(false) +int(4) +string(30) "Syntax error near location 1:7" + +Error after tabs: +bool(false) +int(4) +string(30) "Syntax error near location 1:5" + +Error after mixed whitespace: +bool(false) +int(4) +string(30) "Syntax error near location 1:7" + +Error on second line: +bool(false) +int(4) +string(30) "Syntax error near location 2:2" + +Error with CRLF line endings: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 2:8" + +Error after blank lines: +bool(false) +int(4) +string(30) "Syntax error near location 4:2" + +Error in string with spaces: +bool(false) +int(3) +string(71) "Control character error, possibly incorrectly encoded near location 1:9" + +Error with whitespace around colon: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:12" + +Error with whitespace around comma: +bool(true) +int(0) +string(8) "No error" + +Error in pretty printed JSON: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 4:11" + +Error in heavily indented JSON: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 5:26" + +Error with mixed tabs and spaces: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 3:12" + +Error after whitespace-only line: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 3:12" + +Error in compact JSON: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:22" + +Error with regular spaces: +bool(false) +int(3) +string(72) "Control character error, possibly incorrectly encoded near location 1:10" + diff --git a/ext/json/tests/json_validate_002.phpt b/ext/json/tests/json_validate_002.phpt index 53f4e4f2c2ea..423564c4ad7c 100644 --- a/ext/json/tests/json_validate_002.phpt +++ b/ext/json/tests/json_validate_002.phpt @@ -23,13 +23,13 @@ int(4) string(12) "Syntax error" bool(false) int(4) -string(12) "Syntax error" +string(30) "Syntax error near location 1:1" bool(false) int(4) string(12) "Syntax error" bool(false) int(1) -string(28) "Maximum stack depth exceeded" +string(46) "Maximum stack depth exceeded near location 1:1" bool(true) int(0) string(8) "No error" @@ -44,7 +44,7 @@ int(0) string(8) "No error" bool(false) int(4) -string(12) "Syntax error" +string(30) "Syntax error near location 1:1" bool(true) int(0) string(8) "No error" diff --git a/ext/json/tests/json_validate_004.phpt b/ext/json/tests/json_validate_004.phpt index d8a798d94327..bd807defa140 100644 --- a/ext/json/tests/json_validate_004.phpt +++ b/ext/json/tests/json_validate_004.phpt @@ -23,16 +23,16 @@ json_validate_trycatchdump("[\"\xc1\xc1\",\"a\"]", 512, JSON_INVALID_UTF8_IGNORE Testing Invalid UTF-8 bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(74) "Malformed UTF-8 characters, possibly incorrectly encoded near location 1:1" bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(74) "Malformed UTF-8 characters, possibly incorrectly encoded near location 1:1" bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(74) "Malformed UTF-8 characters, possibly incorrectly encoded near location 1:1" bool(false) int(5) -string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" +string(74) "Malformed UTF-8 characters, possibly incorrectly encoded near location 1:2" bool(true) int(0) string(8) "No error"