diff --git a/.github/actions/apk/action.yml b/.github/actions/apk/action.yml index 0cda4963a6a9..8d0fee161187 100644 --- a/.github/actions/apk/action.yml +++ b/.github/actions/apk/action.yml @@ -28,7 +28,6 @@ runs: curl-dev \ freetype-dev \ gettext-dev \ - gnu-libiconv-dev \ gmp-dev \ icu-dev \ icu-data-full \ diff --git a/.github/actions/configure-alpine/action.yml b/.github/actions/configure-alpine/action.yml index fe02dacfcdaf..d1b4cfea0412 100644 --- a/.github/actions/configure-alpine/action.yml +++ b/.github/actions/configure-alpine/action.yml @@ -43,7 +43,7 @@ runs: --enable-pcntl \ --with-readline \ --enable-mbstring \ - --with-iconv=/usr \ + --with-iconv \ --with-curl \ --with-gettext \ --enable-sockets \ diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index e269ed336408..b7609608f8be 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -573,10 +573,6 @@ jobs: php -r '$c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); $c = str_replace("public function testSanitizeDeepNestedString()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testSanitizeDeepNestedString()", $c); file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c);' # Buggy FFI test in Symfony, see https://github.com/symfony/symfony/issues/47668 php -r '$c = file_get_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php"); $c = str_replace("public function testCastNonTrailingCharPointer()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testCastNonTrailingCharPointer()", $c); file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c);' - # Causes massive amounts of system calls with USE_ZEND_ALLOC=0, exceeding the timeout - if [ -e 'src/Symfony/Component/Console/Tests/Helper/FileInputHelperTest.php' ]; then - php -r '$c = file_get_contents("src/Symfony/Component/Console/Tests/Helper/FileInputHelperTest.php"); $c = str_replace("public function testReadWithPasteDetectionAbortsBeyondMaxBytes()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testReadWithPasteDetectionAbortsBeyondMaxBytes()", $c); file_put_contents("src/Symfony/Component/Console/Tests/Helper/FileInputHelperTest.php", $c);' - fi export SYMFONY_DEPRECATIONS_HELPER=max[total]=999 X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do diff --git a/NEWS b/NEWS index 7105b8d44f5b..8c29466dcc64 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,10 @@ PHP NEWS . Fixed bug GH-15672 and GH-15911 (Stack overflow when an internal function recurses through zend_call_function, such as a self-attached SPL iterator). (iliaal) + . Lock unmodified readonly properties for modification after clone-with. + (NickSdot) + . abort() instead of exit() on hard OOM. (realFlowControl) + . perf: ZTS: move AG and SCNG into native __thread storage. (henderkes) - Calendar: . Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with @@ -62,6 +66,10 @@ PHP NEWS - JSON: . Report unterminated JSON strings as syntax errors. (timwolla) + . Improve performance error position tracking during JSON decoding. + (henderkes) + . Fixed bug GH-22514 (Incorrect error column in PHP 8.6 JSON parser). + (henderkes, timwolla) - Opcache: . Fixed bug GH-21770 (Infinite recursion in property hook getter in opcache diff --git a/TSRM/TSRM.c b/TSRM/TSRM.c index 4222e88755d6..a5032e456aae 100644 --- a/TSRM/TSRM.c +++ b/TSRM/TSRM.c @@ -41,6 +41,8 @@ typedef struct { ts_allocate_ctor ctor; ts_allocate_dtor dtor; ptrdiff_t fast_offset; + /* When set, storage comes from __thread memory instead of being allocated by TSRM. */ + void *(*tls_addr)(void); int done; } tsrm_resource_type; @@ -164,14 +166,20 @@ TSRM_API bool tsrm_startup(int expected_threads, int expected_resources, int deb static void ts_free_resources(tsrm_tls_entry *thread_resources) { + bool own_thread = thread_resources->thread_id == tsrm_thread_id(); + /* Need to destroy in reverse order to respect dependencies. */ for (int i = thread_resources->count - 1; i >= 0; i--) { if (!resource_types_table[i].done) { + /* A __thread block of a foreign thread is inaccessible. */ + if (resource_types_table[i].tls_addr && !own_thread) { + continue; + } if (resource_types_table[i].dtor) { resource_types_table[i].dtor(thread_resources->storage[i]); } - if (!resource_types_table[i].fast_offset) { + if (!resource_types_table[i].fast_offset && !resource_types_table[i].tls_addr) { free(thread_resources->storage[i]); } } @@ -180,7 +188,9 @@ static void ts_free_resources(tsrm_tls_entry *thread_resources) free(thread_resources->storage); } -/* Shutdown TSRM (call once for the entire process) */ +/* Shutdown TSRM (call once for the entire process). Tears down every thread left + * in the table. For resources allocated with ts_allocate_tls_id(), only the dtor + * of the calling thread is invoked. */ TSRM_API void tsrm_shutdown(void) {/*{{{*/ if (is_thread_shutdown) { @@ -258,7 +268,10 @@ static void tsrm_update_active_threads(void) p->storage = (void *) realloc(p->storage, sizeof(void *)*id_count); for (j=p->count; jthread_id == tsrm_thread_id()); + p->storage[j] = resource_types_table[j].tls_addr(); + } else if (resource_types_table[j].fast_offset) { p->storage[j] = (void *) (((char*)p) + resource_types_table[j].fast_offset); } else { p->storage[j] = (void *) malloc(resource_types_table[j].size); @@ -303,6 +316,7 @@ TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].fast_offset = 0; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].tls_addr = NULL; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0; tsrm_update_active_threads(); @@ -381,6 +395,7 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].fast_offset = *offset; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].tls_addr = NULL; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0; tsrm_update_active_threads(); @@ -390,6 +405,41 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, return *rsrc_id; } +/* allocates a resource id whose per-thread storage is a native __thread block */ +TSRM_API ts_rsrc_id ts_allocate_tls_id(ts_rsrc_id *rsrc_id, void *(*tls_addr)(void), size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor) +{ + TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new TLS resource id, %d bytes", size)); + + tsrm_mutex_lock(tsmm_mutex); + + *rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++); + + if (resource_types_table_size < id_count) { + tsrm_resource_type *_tmp; + _tmp = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count); + if (!_tmp) { + TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate storage for resource")); + *rsrc_id = 0; + tsrm_mutex_unlock(tsmm_mutex); + return 0; + } + resource_types_table = _tmp; + resource_types_table_size = id_count; + } + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].size = size; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].fast_offset = 0; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].tls_addr = tls_addr; + resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0; + + tsrm_update_active_threads(); + tsrm_mutex_unlock(tsmm_mutex); + + TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Successfully allocated new TLS resource id %d", *rsrc_id)); + return *rsrc_id; +} + static void set_thread_local_storage_resource_to(tsrm_tls_entry *thread_resource) { tsrm_tls_set(thread_resource); @@ -422,7 +472,9 @@ static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_ if (resource_types_table[i].done) { (*thread_resources_ptr)->storage[i] = NULL; } else { - if (resource_types_table[i].fast_offset) { + if (resource_types_table[i].tls_addr) { + (*thread_resources_ptr)->storage[i] = resource_types_table[i].tls_addr(); + } else if (resource_types_table[i].fast_offset) { (*thread_resources_ptr)->storage[i] = (void *) (((char*)(*thread_resources_ptr)) + resource_types_table[i].fast_offset); } else { (*thread_resources_ptr)->storage[i] = (void *) malloc(resource_types_table[i].size); @@ -510,7 +562,9 @@ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id) /* In case that extensions don't use the pointer passed from the dtor, but incorrectly * use the global pointer, we need to setup the global pointer temporarily here. */ set_thread_local_storage_resource_to(thread_resources); - /* Free up the old resource from the old thread instance */ + /* Dead thread with a recycled id: its __thread blocks are gone, and this + * thread's blocks were never constructed, so keep tls dtors from running. */ + thread_resources->thread_id = 0; ts_free_resources(thread_resources); free((char *) thread_resources - tsrm_reserved_front); /* Allocate a new resource at the same point in the linked list, and relink the next pointer */ @@ -584,7 +638,7 @@ void ts_free_id(ts_rsrc_id id) if (resource_types_table[rsrc_id].dtor) { resource_types_table[rsrc_id].dtor(p->storage[rsrc_id]); } - if (!resource_types_table[rsrc_id].fast_offset) { + if (!resource_types_table[rsrc_id].fast_offset && !resource_types_table[rsrc_id].tls_addr) { free(p->storage[rsrc_id]); } } diff --git a/TSRM/TSRM.h b/TSRM/TSRM.h index 2e8cbddfcda7..752ea1803f9c 100644 --- a/TSRM/TSRM.h +++ b/TSRM/TSRM.h @@ -100,6 +100,10 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, siz TSRM_API void tsrm_reserve_fast_front(size_t size); TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, ptrdiff_t fixed_offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor); +/* Resource whose per-thread storage is a native __thread block. + * Must be called at startup before any other thread exists. */ +TSRM_API ts_rsrc_id ts_allocate_tls_id(ts_rsrc_id *rsrc_id, void *(*tls_addr)(void), size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor); + /* fetches the requested resource for the current thread */ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id); #define ts_resource(id) ts_resource_ex(id, NULL) diff --git a/UPGRADING b/UPGRADING index a809687d1961..729dfed0363e 100644 --- a/UPGRADING +++ b/UPGRADING @@ -533,6 +533,10 @@ PHP 8.6 UPGRADE NOTES 13. Other Changes ======================================== +- Core: + . In case of a hard OOM PHP now calls abort() instead of exit(1), changing + the exit code to 134 and possibly creating a core dump. + ======================================== 14. Performance Improvements ======================================== diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 7775c9316040..462e567f3f36 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -235,3 +235,11 @@ PHP 8.6 INTERNALS UPGRADE NOTES ======================== 5. SAPI changes ======================== + +- SAPIs should explicitly release a thread's resources by calling + ts_free_thread() before terminating it. tsrm_shutdown() can only release the + resources of the calling thread, for resources allocated with + ts_allocate_tls_id(). + +- AG and SCNG are now allocated with ts_allocate_tls_id() and live in native + __thread storage on ZTS builds. diff --git a/Zend/tests/clone/clone_with_014.phpt b/Zend/tests/clone/clone_with_014.phpt new file mode 100644 index 000000000000..bfbee40e163c --- /dev/null +++ b/Zend/tests/clone/clone_with_014.phpt @@ -0,0 +1,41 @@ +--TEST-- +Properties are still readonly after clone-with +--FILE-- +a = 1; + $this->b = 2; + } +} + +$test = clone(new Test(), ['a' => 3]); +var_dump($test); + +try { + $test->b = 4; +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +var_dump($test); + +?> +--EXPECT-- +object(Test)#2 (2) { + ["a"]=> + int(3) + ["b"]=> + int(2) +} +Error: Cannot modify readonly property Test::$b +object(Test)#2 (2) { + ["a"]=> + int(3) + ["b"]=> + int(2) +} diff --git a/Zend/zend.c b/Zend/zend.c index 9411b92a2018..07692db85196 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -54,6 +54,9 @@ ZEND_API int compiler_globals_id; ZEND_API int executor_globals_id; ZEND_API size_t compiler_globals_offset; ZEND_API size_t executor_globals_offset; +/* ts_allocate_tls_id takes a callback so each thread resolves its own block. + * A plain &language_scanner_globals would capture only the registering thread's address. */ +static void *language_scanner_globals_tls_addr(void) { return &language_scanner_globals; } static HashTable *global_function_table = NULL; static HashTable *global_class_table = NULL; static HashTable *global_constants_table = NULL; @@ -1021,10 +1024,9 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ #ifdef ZTS ts_allocate_fast_id_at(&compiler_globals_id, &compiler_globals_offset, ZEND_CG_OFFSET, sizeof(zend_compiler_globals), (ts_allocate_ctor) compiler_globals_ctor, (ts_allocate_dtor) compiler_globals_dtor); ts_allocate_fast_id_at(&executor_globals_id, &executor_globals_offset, ZEND_EG_OFFSET, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor); - ts_allocate_fast_id_at(&language_scanner_globals_id, &language_scanner_globals_offset, ZEND_SCNG_OFFSET, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL); + ts_allocate_tls_id(&language_scanner_globals_id, language_scanner_globals_tls_addr, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL); ZEND_ASSERT(compiler_globals_offset == ZEND_CG_OFFSET); ZEND_ASSERT(executor_globals_offset == ZEND_EG_OFFSET); - ZEND_ASSERT(language_scanner_globals_offset == ZEND_SCNG_OFFSET); ts_allocate_fast_id(&ini_scanner_globals_id, &ini_scanner_globals_offset, sizeof(zend_ini_scanner_globals), (ts_allocate_ctor) ini_scanner_globals_ctor, NULL); compiler_globals = ts_resource(compiler_globals_id); executor_globals = ts_resource(executor_globals_id); diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 0b040743abf1..fc7bc1f4d9d4 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2613,13 +2613,12 @@ typedef struct _zend_alloc_globals { #ifdef ZTS static int alloc_globals_id; -static size_t alloc_globals_offset; -# define ZEND_AG_OFFSET (ZEND_SCNG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_alloc_globals))) -# define AG(v) ZEND_TSRMG_FAST(ZEND_AG_OFFSET, zend_alloc_globals *, v) +static TSRM_TLS TSRM_TLS_MODEL_ATTR zend_alloc_globals alloc_globals; +static void *alloc_globals_tls_addr(void) { return &alloc_globals; } #else -# define AG(v) (alloc_globals.v) static zend_alloc_globals alloc_globals; #endif +#define AG(v) (alloc_globals.v) ZEND_API bool is_zend_mm(void) { @@ -2974,7 +2973,7 @@ ZEND_API void refresh_memory_manager(void) static ZEND_COLD ZEND_NORETURN void zend_out_of_memory(void) { fprintf(stderr, "Out of memory\n"); - exit(1); + abort(); } #if ZEND_MM_CUSTOM @@ -3336,8 +3335,7 @@ ZEND_API void start_memory_manager(void) # endif #endif #ifdef ZTS - ts_allocate_fast_id_at(&alloc_globals_id, &alloc_globals_offset, ZEND_AG_OFFSET, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor); - ZEND_ASSERT(alloc_globals_offset == ZEND_AG_OFFSET); + ts_allocate_tls_id(&alloc_globals_id, alloc_globals_tls_addr, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor); #else alloc_globals_ctor(&alloc_globals); #endif @@ -3583,9 +3581,3 @@ ZEND_API char * __zend_strdup(const char *s) zend_out_of_memory(); } -#ifdef ZTS -size_t zend_mm_globals_size(void) -{ - return sizeof(zend_alloc_globals); -} -#endif diff --git a/Zend/zend_alloc.h b/Zend/zend_alloc.h index ff51c4fe8652..7e3ea031dc72 100644 --- a/Zend/zend_alloc.h +++ b/Zend/zend_alloc.h @@ -403,10 +403,6 @@ static void apc_init_heap(void) */ -#ifdef ZTS -size_t zend_mm_globals_size(void); -#endif - END_EXTERN_C() #endif diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 61499c0cc23d..f78567cfaa74 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -330,11 +330,9 @@ struct _zend_executor_globals { }; #ifdef ZTS -/* Compile-time offsets of the hot globals, in a reserved region just before the - * cache pointer. ZEND_AG_OFFSET is furthest, in zend_alloc.c. */ +/* Compile-time offsets of the hot globals, in a reserved region just before *_tsrm_ls_cache. */ # define ZEND_CG_OFFSET (-(ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals))) # define ZEND_EG_OFFSET (ZEND_CG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals))) -# define ZEND_SCNG_OFFSET (ZEND_EG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals))) #endif #define EG_FLAGS_INITIAL (0) diff --git a/Zend/zend_globals_macros.h b/Zend/zend_globals_macros.h index 2d2948e50a86..adb3913ece71 100644 --- a/Zend/zend_globals_macros.h +++ b/Zend/zend_globals_macros.h @@ -48,12 +48,20 @@ extern ZEND_API zend_executor_globals executor_globals; /* Language Scanner */ #ifdef ZTS -# define LANG_SCNG(v) ZEND_TSRMG_FAST(ZEND_SCNG_OFFSET, zend_php_scanner_globals *, v) extern ZEND_API ts_rsrc_id language_scanner_globals_id; -extern ZEND_API size_t language_scanner_globals_offset; +# if defined(ZEND_WIN32) && !defined(LIBZEND_EXPORTS) +# define LANG_SCNG(v) TSRMG(language_scanner_globals_id, zend_php_scanner_globals *, v) +# else +# ifdef ZEND_WIN32 +extern TSRM_TLS zend_php_scanner_globals language_scanner_globals; +# else +extern ZEND_API TSRM_TLS TSRM_TLS_MODEL_ATTR zend_php_scanner_globals language_scanner_globals; +# endif +# define LANG_SCNG(v) (language_scanner_globals.v) +# endif #else -# define LANG_SCNG(v) (language_scanner_globals.v) extern ZEND_API zend_php_scanner_globals language_scanner_globals; +# define LANG_SCNG(v) (language_scanner_globals.v) #endif diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 07f2d44cb5c6..1d7e41a2a7d3 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -84,7 +84,11 @@ #define SCNG LANG_SCNG #ifdef ZTS ZEND_API ts_rsrc_id language_scanner_globals_id; -ZEND_API size_t language_scanner_globals_offset; +# ifdef ZEND_WIN32 +TSRM_TLS zend_php_scanner_globals language_scanner_globals; +# else +ZEND_API TSRM_TLS TSRM_TLS_MODEL_ATTR zend_php_scanner_globals language_scanner_globals; +# endif #else ZEND_API zend_php_scanner_globals language_scanner_globals; #endif diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 2fc264742cd1..474157e73d39 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -321,6 +321,14 @@ ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const } ZEND_HASH_FOREACH_END(); EG(fake_scope) = old_scope; + + /* Lock readonly properties once more. */ + if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) { + for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) { + zval* prop = OBJ_PROP_NUM(new_object, i); + Z_PROP_FLAG_P(prop) &= ~IS_PROP_REINITABLE; + } + } } return new_object; diff --git a/ext/gd/tests/gh19666.phpt b/ext/gd/tests/gh19666.phpt index ba9bf54f5661..469cd88302f2 100644 --- a/ext/gd/tests/gh19666.phpt +++ b/ext/gd/tests/gh19666.phpt @@ -2,6 +2,10 @@ GH-19666 (Unexpected nan value in imageconvolution) --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- --FILE-- --EXPECTF-- Notice: iconv(): Detected an illegal character in input string in %s on line %d @@ -20,8 +44,8 @@ bool(false) string(10) "aa%C3%B8aa" Notice: iconv(): Detected an incomplete multibyte character in input string in %s on line %d -string(0) "" +bool(false) string(8) "%C3%B8aa" Notice: iconv(): Detected an incomplete multibyte character in input string in %s on line %d -string(0) "" +bool(false) diff --git a/ext/json/json.c b/ext/json/json.c index ac033c057ac4..04a62f52152f 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -184,7 +184,7 @@ static zend_string *php_json_get_error_msg_with_location(const php_json_error_de const char *base_msg = php_json_get_error_msg(details->code); if (details->line > 0 && details->column > 0) { - return zend_strpprintf(0, "%s near location %zu:%zu", base_msg, details->line, details->column); + return zend_strpprintf(0, "%s near location %" PRIu64 ":%" PRIu64, base_msg, details->line, details->column); } return zend_string_init(base_msg, strlen(base_msg), 0); diff --git a/ext/json/json_parser.y b/ext/json/json_parser.y index 0d3b90b29e1e..0fd3e2c4e364 100644 --- a/ext/json/json_parser.y +++ b/ext/json/json_parser.y @@ -39,7 +39,6 @@ int json_yydebug = 1; } -%locations %define api.prefix {php_json_yy} %define api.pure full %param { php_json_parser *parser } @@ -64,8 +63,8 @@ int json_yydebug = 1; %destructor { zval_ptr_dtor_nogc(&$$); } %code { -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_yylex(union YYSTYPE *value, php_json_parser *parser); +static void php_json_yyerror(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); @@ -275,7 +274,7 @@ static int php_json_parser_object_update_validate(php_json_parser *parser, zval return SUCCESS; } -static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser) +static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser) { int token = php_json_scan(&parser->scanner); @@ -291,15 +290,10 @@ static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_pars 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(YYLTYPE *location, php_json_parser *parser, char const *msg) +static void php_json_yyerror(php_json_parser *parser, char const *msg) { if (!parser->scanner.errcode) { parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; @@ -311,11 +305,28 @@ PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parse return parser->scanner.errcode; } +static uint64_t php_json_compute_error_column(const php_json_scanner *s) +{ + const php_json_ctype *p = s->line_start; + const php_json_ctype *end = s->token; + /* Count characters from the start of the line to the failing token, + * folding UTF-8 continuation bytes into their leading byte. */ + uint64_t column = 1; + + while (p < end) { + if ((*p & 0b11000000) != 0b10000000) { + column++; + } + p++; + } + return column; +} + PHP_JSON_API void php_json_parser_error_details(const php_json_parser *parser, php_json_error_details *out) { out->code = parser->scanner.errcode; - out->line = parser->scanner.errloc.first_line; - out->column = parser->scanner.errloc.first_column; + out->line = parser->scanner.line; + out->column = php_json_compute_error_column(&parser->scanner); } static const php_json_parser_methods default_parser_methods = diff --git a/ext/json/json_scanner.re b/ext/json/json_scanner.re index 0c64a6423baf..be62875a00e0 100644 --- a/ext/json/json_scanner.re +++ b/ext/json/json_scanner.re @@ -51,7 +51,6 @@ #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,10 +95,8 @@ 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; + s->line = 1; + s->line_start = (php_json_ctype *) str; PHP_JSON_CONDITION_SET(JS); } @@ -108,8 +105,6 @@ 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 @@ -155,49 +150,27 @@ 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} ; - "{" { - 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 ','; - } + "{" { return '{'; } + "}" { return '}'; } + "[" { return '['; } + "]" { return ']'; } + ":" { return ':'; } + "," { 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 = 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) { @@ -221,19 +194,15 @@ 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 { - 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(); + s->line++; + s->line_start = s->cursor; goto std; } + WS { goto std; } EOI { if (s->limit < s->cursor) { return PHP_JSON_T_EOI; @@ -243,7 +212,6 @@ std: } } ["] { - PHP_JSON_TOKEN_LOCATION(last_column)++; s->str_start = s->cursor; s->str_esc = 0; s->utf8_invalid = 0; @@ -275,22 +243,18 @@ 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); } @@ -299,7 +263,6 @@ std: return PHP_JSON_T_ERROR; } ESC { - PHP_JSON_TOKEN_LOCATION(last_column) += 2; s->str_esc++; PHP_JSON_CONDITION_GOTO(STR_P1); } @@ -308,7 +271,6 @@ 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) { @@ -329,22 +291,7 @@ std: return PHP_JSON_T_STRING; } } - 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); - } + UTF8 { 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) { diff --git a/ext/json/php_json.h b/ext/json/php_json.h index f34684e149d8..f20b20964a71 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -54,8 +54,8 @@ typedef enum { typedef struct php_json_error_details { php_json_error_code code; - size_t line; - size_t column; + uint64_t line; + uint64_t column; } php_json_error_details; static inline void php_json_error_details_clear(php_json_error_details *out) { diff --git a/ext/json/php_json_parser.h b/ext/json/php_json_parser.h index 888a0d317fe0..ae927d880aeb 100644 --- a/ext/json/php_json_parser.h +++ b/ext/json/php_json_parser.h @@ -48,20 +48,12 @@ 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( diff --git a/ext/json/php_json_scanner.h b/ext/json/php_json_scanner.h index 90460cf9952a..f432f66b6d78 100644 --- a/ext/json/php_json_scanner.h +++ b/ext/json/php_json_scanner.h @@ -20,17 +20,6 @@ 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 */ @@ -39,18 +28,17 @@ typedef struct _php_json_scanner { php_json_ctype *ctxmarker; /* marker position for context backtracking */ php_json_ctype *str_start; /* start position of the string */ php_json_ctype *pstr; /* string pointer for escapes conversion */ + php_json_ctype *line_start; /* start position of the current line */ + uint64_t line; /* current line number (1-based) */ zval value; /* value */ int str_esc; /* number of extra characters for escaping */ 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/bug68546.phpt b/ext/json/tests/bug68546.phpt index 1847eabf3a8e..40ea085ba23b 100644 --- a/ext/json/tests/bug68546.phpt +++ b/ext/json/tests/bug68546.phpt @@ -16,5 +16,5 @@ NULL bool(true) NULL bool(true) -string(55) "The decoded property name is invalid near location 1:27" +string(55) "The decoded property name is invalid near location 1:37" Done diff --git a/ext/json/tests/gh22514.phpt b/ext/json/tests/gh22514.phpt new file mode 100644 index 000000000000..9e7d2adfff8a --- /dev/null +++ b/ext/json/tests/gh22514.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-22514: Incorrect error column in JSON parser after escape sequences +--FILE-- + +--EXPECT-- +NULL +string(31) "Syntax error near location 2:10" +NULL +string(30) "Syntax error near location 2:5" 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 index ee1800eb8273..ca798fcb01fe 100644 --- a/ext/json/tests/json_last_error_msg_error_location_005.phpt +++ b/ext/json/tests/json_last_error_msg_error_location_005.phpt @@ -19,8 +19,8 @@ json_validate_trycatchdump('{"test": "\uD83D\uDE00\uD83C\uDF89}'); echo "\nError with mixed UTF-8 and UTF-16:\n"; json_validate_trycatchdump('{"mixed": "Hello \u4E16\u754C world}'); -// UTF-16 in key and value -echo "\nError with UTF-16 in key:\n"; +// Missing closing quote after a UTF-16 key +echo "\nError on unterminated value after UTF-16 key:\n"; json_validate_trycatchdump('{"\u30D7\u30EC\u30B9": "value}'); // Multiple keys with UTF-16 @@ -66,15 +66,15 @@ bool(false) int(4) string(31) "Syntax error near location 1:11" -Error with UTF-16 in key: +Error on unterminated value after UTF-16 key: bool(false) int(4) -string(30) "Syntax error near location 1:9" +string(31) "Syntax error near location 1:24" Error with multiple UTF-16 keys: bool(false) int(4) -string(31) "Syntax error near location 1:22" +string(31) "Syntax error near location 1:42" Error with BMP characters: bool(false) @@ -89,12 +89,12 @@ string(31) "Syntax error near location 1:11" Error in array with UTF-16: bool(false) int(4) -string(31) "Syntax error near location 1:12" +string(31) "Syntax error near location 1:22" Error in nested structure with UTF-16: bool(false) int(4) -string(31) "Syntax error near location 1:18" +string(31) "Syntax error near location 1:28" Error with UTF-16 and control chars: bool(false) diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 9befe134cded..7a089625aa48 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -2202,6 +2202,8 @@ PHP_FUNCTION(ldap_explode_dn) zend_long with_attrib; char *dn, **ldap_value; size_t dn_len; + int i, count; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pl", &dn, &dn_len, &with_attrib) != SUCCESS) { RETURN_THROWS(); @@ -2213,11 +2215,15 @@ PHP_FUNCTION(ldap_explode_dn) } array_init(return_value); - int i; - for (i = 0; ldap_value[i] != NULL; i++) { + i = 0; + while (ldap_value[i] != NULL) i++; + count = i; + + add_assoc_long(return_value, "count", count); + + for (i = 0; i < count; i++) { add_index_string(return_value, i, ldap_value[i]); } - add_assoc_long(return_value, "count", i); ldap_memvfree((void **)ldap_value); } diff --git a/ext/ldap/tests/ldap_explode_dn.phpt b/ext/ldap/tests/ldap_explode_dn.phpt index 047078c7beb7..2012506c3ef0 100644 --- a/ext/ldap/tests/ldap_explode_dn.phpt +++ b/ext/ldap/tests/ldap_explode_dn.phpt @@ -34,16 +34,18 @@ echo "Done\n"; ?> --EXPECT-- array(4) { + ["count"]=> + int(3) [0]=> string(6) "cn=bob" [1]=> string(10) "dc=example" [2]=> string(6) "dc=com" - ["count"]=> - int(3) } array(5) { + ["count"]=> + int(4) [0]=> string(6) "cn=bob" [1]=> @@ -52,20 +54,20 @@ array(5) { string(10) "dc=example" [3]=> string(6) "dc=com" - ["count"]=> - int(4) } array(4) { + ["count"]=> + int(3) [0]=> string(3) "bob" [1]=> string(7) "example" [2]=> string(3) "com" - ["count"]=> - int(3) } array(5) { + ["count"]=> + int(4) [0]=> string(3) "bob" [1]=> @@ -74,8 +76,6 @@ array(5) { string(7) "example" [3]=> string(3) "com" - ["count"]=> - int(4) } bool(false) bool(false) diff --git a/main/main.c b/main/main.c index 48e4a757513b..9e77c99a45df 100644 --- a/main/main.c +++ b/main/main.c @@ -2819,13 +2819,11 @@ PHPAPI void php_reserve_tsrm_memory(void) tsrm_reserve( TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals)) + TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals)) + - TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals)) + TSRM_ALIGNED_SIZE(sizeof(zend_ini_scanner_globals)) + TSRM_ALIGNED_SIZE(sizeof(virtual_cwd_globals)) + #ifdef ZEND_SIGNALS TSRM_ALIGNED_SIZE(sizeof(zend_signal_globals_t)) + #endif - TSRM_ALIGNED_SIZE(zend_mm_globals_size()) + TSRM_ALIGNED_SIZE(zend_gc_globals_size()) + TSRM_ALIGNED_SIZE(sizeof(php_core_globals)) + TSRM_ALIGNED_SIZE(sizeof(sapi_globals_struct)) + @@ -2845,9 +2843,7 @@ PHPAPI bool php_tsrm_startup_ex(int expected_threads) /* Must cover the total size of every ZEND_*_OFFSET global, or the furthest underflows the block. */ tsrm_reserve_fast_front( TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals)) + - TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals)) + - TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals)) + - TSRM_ALIGNED_SIZE(zend_mm_globals_size())); // AG size, exposed through function call + TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals))); (void)ts_resource(0); return ret; }