diff --git a/.gdbinit b/.gdbinit
index c4705b2f59a95..4477828265e0e 100644
--- a/.gdbinit
+++ b/.gdbinit
@@ -42,7 +42,7 @@ define print_cvs
printf "Compiled variables count: %d\n\n", $cv_count
while $cv_idx < $cv_count
- printf "[%d] '%s'\n", $cv_idx, $cv[$cv_idx].val
+ printf "[%d] '$%s'\n", $cv_idx, $cv[$cv_idx].val@$cv[$cv_idx].len
set $zvalue = ((zval *) $cv_ex_ptr) + $callFrameSize + $cv_idx
printzv $zvalue
set $cv_idx = $cv_idx + 1
diff --git a/NEWS b/NEWS
index 98862eefb5c89..1634189bde10f 100644
--- a/NEWS
+++ b/NEWS
@@ -11,4 +11,8 @@ PHP NEWS
with a given skeleton, locale, collapse type and identity fallback.
(BogdanUngureanu)
+- Standard:
+ . Fixed bug GH-19926 (reset internal pointer earlier while splicing array
+ while COW violation flag is still set). (alexandre-daubois)
+
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>
diff --git a/ext/standard/array.c b/ext/standard/array.c
index 5958540106d89..4097d71899011 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -3489,6 +3489,12 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H
HT_SET_ITERATORS_COUNT(in_hash, 0);
in_hash->pDestructor = NULL;
+ /* Set internal pointer to 0 directly instead of calling zend_hash_internal_pointer_reset().
+ * This avoids the COW violation assertion and delays advancing to the first valid position
+ * until after we've switched to the new array structure (out_hash). The iterator will be
+ * advanced when actually accessed, at which point it will find valid indexes in the new array. */
+ in_hash->nInternalPointer = 0;
+
if (UNEXPECTED(GC_DELREF(in_hash) == 0)) {
/* Array was completely deallocated during the operation */
zend_array_destroy(in_hash);
@@ -3507,8 +3513,6 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H
in_hash->nNextFreeElement = out_hash.nNextFreeElement;
in_hash->arData = out_hash.arData;
in_hash->pDestructor = out_hash.pDestructor;
-
- zend_hash_internal_pointer_reset(in_hash);
}
/* }}} */
diff --git a/ext/standard/tests/array/gh19926.phpt b/ext/standard/tests/array/gh19926.phpt
new file mode 100644
index 0000000000000..b714db9eb2b32
--- /dev/null
+++ b/ext/standard/tests/array/gh19926.phpt
@@ -0,0 +1,20 @@
+--TEST--
+GH-19926 (Assertion failure zend_hash_internal_pointer_reset_ex)
+--FILE--
+
+--EXPECT--
+Exception caught, no assertion failure
diff --git a/ext/standard/tests/array/gh19926_pointer.phpt b/ext/standard/tests/array/gh19926_pointer.phpt
new file mode 100644
index 0000000000000..c134f3b594b3d
--- /dev/null
+++ b/ext/standard/tests/array/gh19926_pointer.phpt
@@ -0,0 +1,19 @@
+--TEST--
+GH-19926 (internal pointer behavior after array_splice)
+--FILE--
+
+--EXPECT--
+Before array_splice: int(3)
+After array_splice: int(999)
diff --git a/ext/standard/tests/streams/gh19570.phpt b/ext/standard/tests/streams/gh19570.phpt
new file mode 100644
index 0000000000000..e3d486ea83cf9
--- /dev/null
+++ b/ext/standard/tests/streams/gh19570.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-19570 (unable to fseek in /dev/zero and /dev/null)
+--SKIPIF--
+
+--FILE--
+
+--EXPECT--
+int(0)
+int(0)
+int(0)
diff --git a/ext/uri/config.m4 b/ext/uri/config.m4
index c8a179c96ef27..99e0d6b476779 100644
--- a/ext/uri/config.m4
+++ b/ext/uri/config.m4
@@ -33,7 +33,7 @@ if test "$PHP_EXTERNAL_URIPARSER" = "no"; then
$URIPARSER_DIR/src/UriSetScheme.c $URIPARSER_DIR/src/UriSetUserInfo.c $URIPARSER_DIR/src/UriShorten.c $URIPARSER_DIR/src/UriVersion.c"
URI_CFLAGS="-DURI_STATIC_BUILD"
else
- PKG_CHECK_MODULES([LIBURIPARSER], [liburiparser >= 0.9.9])
+ PKG_CHECK_MODULES([LIBURIPARSER], [liburiparser >= 0.9.10])
PHP_EVAL_LIBLINE([$LIBURIPARSER_LIBS], [URI_SHARED_LIBADD])
PHP_EVAL_INCLINE([$LIBURIPARSER_CFLAGS])
fi
diff --git a/ext/uri/uriparser/include/uriparser/Uri.h b/ext/uri/uriparser/include/uriparser/Uri.h
index 94441a2dc8899..fbdb3d9a3798e 100644
--- a/ext/uri/uriparser/include/uriparser/Uri.h
+++ b/ext/uri/uriparser/include/uriparser/Uri.h
@@ -137,7 +137,7 @@ typedef struct URI_TYPE(HostDataStruct) {
URI_TYPE(TextRange) ipFuture; /**< IPvFuture address
@note
With non-NULL members in UriUriStructA.hostData context,
- this text range's pointers must be idential to those
+ this text range's pointers must be identical to those
of UriUriStructA.hostText at all times. */
} URI_TYPE(HostData); /**< @copydoc UriHostDataStructA */
@@ -1420,7 +1420,7 @@ URI_PUBLIC UriBool URI_FUNC(IsWellFormedHostRegName)(const URI_CHAR * first, con
*
* @param first IN: Pointer to first character
* @param afterLast IN: Pointer to character after the last one still in
- * @param hasHost IN: Wether the target %URI has a non-NULL host set or not
+ * @param hasHost IN: Whether the target %URI has a non-NULL host set or not
* @return URI_TRUE if non-NULL and well-formed, else URI_FALSE
*
* @see uriIsWellFormedFragmentA
@@ -2415,7 +2415,7 @@ URI_PUBLIC int URI_FUNC(SetUserInfoMm)(URI_TYPE(Uri) * uri,
* or even fully custom patches. As a result, the version string
* returned serves as nothing more than "based on that version",
* it does not guarantee equivalence to vanilla upstream releases
- * or absence of additinal downstream patches.
+ * or absence of additional downstream patches.
* It is nothing more than "a hint" and MUST NEVER be used to
* make decisions on in application code at runtime.
*
diff --git a/ext/uri/uriparser/include/uriparser/UriBase.h b/ext/uri/uriparser/include/uriparser/UriBase.h
index 6b01bb79e9f86..92883278cff26 100644
--- a/ext/uri/uriparser/include/uriparser/UriBase.h
+++ b/ext/uri/uriparser/include/uriparser/UriBase.h
@@ -187,7 +187,7 @@ typedef struct UriIp6Struct {
} UriIp6; /**< @copydoc UriIp6Struct */
-struct UriMemoryManagerStruct; /* foward declaration to break loop */
+struct UriMemoryManagerStruct; /* forward declaration to break loop */
/**
@@ -287,28 +287,34 @@ typedef enum UriResolutionOptionsEnum {
/**
- * Wraps a memory manager backend that only provides malloc and free
- * to make a complete memory manager ready to be used.
+ * Wraps a memory manager backend that only provides malloc(3) and
+ * free(3) to make a complete memory manager ready to be used.
*
* The core feature of this wrapper is that you don't need to implement
- * realloc if you don't want to. The wrapped memory manager uses
- * backend->malloc, memcpy, and backend->free and soieof(size_t) extra
- * bytes per allocation to emulate fallback realloc for you.
+ * realloc(3) if you don't want to. The wrapped memory manager uses
+ * backend->malloc, memcpy(3), and backend->free and
+ * (at least) sizeof(size_t) extra bytes per allocation to emulate
+ * fallback realloc(3) for you.
*
- * memory->calloc is uriEmulateCalloc.
- * memory->free uses backend->free and handles the size header.
- * memory->malloc uses backend->malloc and adds a size header.
- * memory->realloc uses memory->malloc, memcpy, and memory->free and reads
- * the size header.
- * memory->reallocarray is uriEmulateReallocarray.
+ *
+ * - memory->calloc is uriEmulateCalloc.
+ * - memory->free uses backend->free,
+ * and handles the size header.
+ * - memory->malloc uses backend->malloc,
+ * and adds a size header.
+ * - memory->realloc uses memory->malloc,
+ * memcpy(3) and memory->free,
+ * and reads the size header.
+ * - memory->reallocarray is uriEmulateReallocarray.
+ *
*
- * The internal workings behind memory->free, memory->malloc, and
- * memory->realloc may change so the functions exposed by these function
- * pointer sshould be consided internal and not public API.
+ * The internal workings behind memory->free, memory->malloc,
+ * and memory->realloc may change, and the functions exposed by these
+ * function pointers should be considered internal and not public API.
*
* @param memory OUT: Where to write the wrapped memory manager to
* @param backend IN: Memory manager to use as a backend
- * @return Error code or 0 on success
+ * @return Error code or 0 on success
*
* @see uriEmulateCalloc
* @see uriEmulateReallocarray
@@ -321,7 +327,7 @@ URI_PUBLIC int uriCompleteMemoryManager(UriMemoryManager * memory,
/**
- * Offers emulation of calloc(3) based on memory->malloc and memset.
+ * Offers emulation of calloc(3) based on memory->malloc and memset.
* See "man 3 calloc" as well.
*
* @param memory IN: Memory manager to use, should not be NULL
@@ -340,11 +346,11 @@ URI_PUBLIC void * uriEmulateCalloc(UriMemoryManager * memory,
/**
- * Offers emulation of reallocarray(3) based on memory->realloc.
+ * Offers emulation of reallocarray(3) based on memory->realloc.
* See "man 3 reallocarray" as well.
*
* @param memory IN: Memory manager to use, should not be NULL
- * @param ptr IN: Pointer allocated using memory->malloc/... or NULL
+ * @param ptr IN: Pointer allocated using memory->malloc/... or NULL
* @param nmemb IN: Number of elements to allocate
* @param size IN: Size in bytes per element
* @return Pointer to allocated memory or NULL
@@ -369,7 +375,11 @@ URI_PUBLIC void * uriEmulateReallocarray(UriMemoryManager * memory,
* 5. and frees that memory.
*
* It is recommended to compile with AddressSanitizer enabled
- * to take full advantage of uriTestMemoryManager.
+ * to take full advantage of uriTestMemoryManager.
+ *
+ * For backwards-compatibility, uriTestMemoryManager
+ * does not challenge pointer alignment; please see
+ * uriTestMemoryManagerEx for that feature.
*
* @param memory IN: Memory manager to use, should not be NULL
* @return Error code or 0 on success
@@ -377,10 +387,40 @@ URI_PUBLIC void * uriEmulateReallocarray(UriMemoryManager * memory,
* @see uriEmulateCalloc
* @see uriEmulateReallocarray
* @see UriMemoryManager
+ * @see uriTestMemoryManagerEx
* @since 0.9.0
*/
URI_PUBLIC int uriTestMemoryManager(UriMemoryManager * memory);
+/**
+ * Run multiple tests against a given memory manager.
+ * For example, one test
+ * 1. allocates a small amount of memory,
+ * 2. writes some magic bytes to it,
+ * 3. reallocates it,
+ * 4. checks that previous values are still present,
+ * 5. and frees that memory.
+ *
+ * It is recommended to compile with both AddressSanitizer and
+ * UndefinedBehaviorSanitizer enabled to take full advantage of
+ * uriTestMemoryManagerEx. Note that environment variable
+ * UBSAN_OPTIONS may need adjustment to make UndefinedBehaviorSanitizer
+ * fatal (which by default it is not).
+ *
+ * @param memory IN: Memory manager to use, should not be NULL
+ * @param challengeAlignment IN: Whether to challenge pointer alignment
+ * @return Error code or 0 on success
+ *
+ * @see uriEmulateCalloc
+ * @see uriEmulateReallocarray
+ * @see UriMemoryManager
+ * @see uriTestMemoryManager
+ * @since 0.9.10
+ */
+URI_PUBLIC int uriTestMemoryManagerEx(UriMemoryManager * memory, UriBool challengeAlignment);
+
+
+
#endif /* URI_BASE_H */
diff --git a/ext/uri/uriparser/src/UriCommon.c b/ext/uri/uriparser/src/UriCommon.c
index 2d4e947088bc6..a644eec475367 100644
--- a/ext/uri/uriparser/src/UriCommon.c
+++ b/ext/uri/uriparser/src/UriCommon.c
@@ -208,14 +208,25 @@ UriBool URI_FUNC(RemoveDotSegmentsEx)(URI_TYPE(Uri) * uri,
*
* For example, changing "./http://foo" into "http://foo" would change semantics
* and hence the dot segment is essential to that case and cannot be removed.
+ *
+ * Other examples that would change semantics are:
+ * - cutting "/.//" down to "//"
+ * - cutting "scheme:/.//" down to "scheme://".
*/
removeSegment = URI_TRUE;
- if (relative && (walker == uri->pathHead) && (walker->next != NULL)) {
- const URI_CHAR * ch = walker->next->text.first;
- for (; ch < walker->next->text.afterLast; ch++) {
- if (*ch == _UT(':')) {
- removeSegment = URI_FALSE;
- break;
+ if ((walker == uri->pathHead) && (walker->next != NULL)) {
+ /* Detect case "/.//" (with or without scheme) */
+ if ((walker->next->text.first == walker->next->text.afterLast)
+ && (URI_FUNC(HasHost)(uri) == URI_FALSE)) {
+ removeSegment = URI_FALSE;
+ /* Detect case "./withcolon:" */
+ } else if (relative) {
+ const URI_CHAR * ch = walker->next->text.first;
+ for (; ch < walker->next->text.afterLast; ch++) {
+ if (*ch == _UT(':')) {
+ removeSegment = URI_FALSE;
+ break;
+ }
}
}
}
@@ -358,7 +369,7 @@ UriBool URI_FUNC(RemoveDotSegmentsEx)(URI_TYPE(Uri) * uri,
}
memory->free(memory, walker);
} else {
- /* Re-use segment for "" path segment to represent trailing slash, update tail */
+ /* Reuse segment for "" path segment to represent trailing slash, update tail */
URI_TYPE(PathSegment) * const segment = walker;
if (pathOwned && (segment->text.first != segment->text.afterLast)) {
memory->free(memory, (URI_CHAR *)segment->text.first);
@@ -403,7 +414,7 @@ UriBool URI_FUNC(RemoveDotSegmentsEx)(URI_TYPE(Uri) * uri,
* NEW: tail -> NULL */
uri->pathTail = NULL;
} else {
- /* Re-use segment for "" path segment to represent trailing slash,
+ /* Reuse segment for "" path segment to represent trailing slash,
* then update head and tail */
if (pathOwned && (walker->text.first != walker->text.afterLast)) {
memory->free(memory, (URI_CHAR *)walker->text.first);
@@ -696,7 +707,7 @@ static UriBool URI_FUNC(PrependNewDotSegment)(URI_TYPE(Uri) * uri, UriMemoryMana
/* When dropping a scheme from a URI without a host and with a colon (":")
* in the first path segment, a consecutive reparse would rightfully
* mis-classify the first path segment as a scheme due to the colon.
- * To protect against this case, we prepend an artifical "." segment
+ * To protect against this case, we prepend an artificial "." segment
* to the path in here; the function is called after the scheme has
* just been dropped.
*
@@ -751,7 +762,7 @@ UriBool URI_FUNC(FixPathNoScheme)(URI_TYPE(Uri) * uri,
/* When dropping a host from a URI without a scheme, an absolute path
* and and empty first path segment, a consecutive reparse would rightfully
* mis-classify the first path segment as a host marker due to the "//".
- * To protect against this case, we prepend an artifical "." segment
+ * To protect against this case, we prepend an artificial "." segment
* to the path in here; the function is called after the host has
* just been dropped.
*
diff --git a/ext/uri/uriparser/src/UriMemory.c b/ext/uri/uriparser/src/UriMemory.c
index 916d7cea8308a..503b9f9787ff4 100644
--- a/ext/uri/uriparser/src/UriMemory.c
+++ b/ext/uri/uriparser/src/UriMemory.c
@@ -64,6 +64,15 @@
+#define URI_MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+/* NOTE: This intends to mimic MALLOC_ALIGNMENT of glibc */
+#define URI_MALLOC_ALIGNMENT URI_MAX(2 * sizeof(size_t), sizeof(long double))
+
+#define URI_MALLOC_PADDING (URI_MALLOC_ALIGNMENT - sizeof(size_t))
+
+
+
#define URI_CHECK_ALLOC_OVERFLOW(total_size, nmemb, size) \
do { \
/* check for unsigned overflow */ \
@@ -170,7 +179,7 @@ void * uriEmulateReallocarray(UriMemoryManager * memory,
static void * uriDecorateMalloc(UriMemoryManager * memory,
size_t size) {
UriMemoryManager * backend;
- const size_t extraBytes = sizeof(size_t);
+ const size_t extraBytes = sizeof(size_t) + URI_MALLOC_PADDING;
void * buffer;
if (memory == NULL) {
@@ -225,7 +234,7 @@ static void * uriDecorateRealloc(UriMemoryManager * memory,
return NULL;
}
- prevSize = *((size_t *)((char *)ptr - sizeof(size_t)));
+ prevSize = *((size_t *)((char *)ptr - sizeof(size_t) - URI_MALLOC_PADDING));
/* Anything to do? */
if (size <= prevSize) {
@@ -259,7 +268,7 @@ static void uriDecorateFree(UriMemoryManager * memory, void * ptr) {
return;
}
- backend->free(backend, (char *)ptr - sizeof(size_t));
+ backend->free(backend, (char *)ptr - sizeof(size_t) - URI_MALLOC_PADDING);
}
@@ -288,7 +297,7 @@ int uriCompleteMemoryManager(UriMemoryManager * memory,
-int uriTestMemoryManager(UriMemoryManager * memory) {
+int uriTestMemoryManagerEx(UriMemoryManager * memory, UriBool challengeAlignment) {
const size_t mallocSize = 7;
const size_t callocNmemb = 3;
const size_t callocSize = 5;
@@ -456,11 +465,41 @@ int uriTestMemoryManager(UriMemoryManager * memory) {
buffer = NULL;
}
+ /* challenge pointer alignment */
+ if (challengeAlignment == URI_TRUE) {
+ long double * ptr = memory->malloc(memory, 4 * sizeof(long double));
+ if (ptr != NULL) {
+ ptr[0] = 0.0L;
+ ptr[1] = 1.1L;
+ ptr[2] = 2.2L;
+ ptr[3] = 3.3L;
+
+ {
+ long double * const ptrNew = memory->realloc(memory, ptr, 8 * sizeof(long double));
+ if (ptrNew != NULL) {
+ ptr = ptrNew;
+ ptr[4] = 4.4L;
+ ptr[5] = 5.5L;
+ ptr[6] = 6.6L;
+ ptr[7] = 7.7L;
+ }
+ }
+
+ memory->free(memory, ptr);
+ }
+ }
+
return URI_SUCCESS;
}
+int uriTestMemoryManager(UriMemoryManager * memory) {
+ return uriTestMemoryManagerEx(memory, /*challengeAlignment=*/ URI_FALSE);
+}
+
+
+
/*extern*/ UriMemoryManager defaultMemoryManager = {
uriDefaultMalloc,
uriDefaultCalloc,
diff --git a/main/debug_gdb_scripts.c b/main/debug_gdb_scripts.c
index e3c522bc04843..7a4bf00980c3e 100644
--- a/main/debug_gdb_scripts.c
+++ b/main/debug_gdb_scripts.c
@@ -55,7 +55,7 @@ asm(
".ascii \"\\n\"\n"
".ascii \" printf \\\"Compiled variables count: %d\\\\\\\\n\\\\\\\\n\\\", $cv_count\\n\"\n"
".ascii \" while $cv_idx < $cv_count\\n\"\n"
- ".ascii \" printf \\\"[%d] \\\\'%s\\\\'\\\\\\\\n\\\", $cv_idx, $cv[$cv_idx].val\\n\"\n"
+ ".ascii \" printf \\\"[%d] \\\\'$%s\\\\'\\\\\\\\n\\\", $cv_idx, $cv[$cv_idx].val@$cv[$cv_idx].len\\n\"\n"
".ascii \" set $zvalue = ((zval *) $cv_ex_ptr) + $callFrameSize + $cv_idx\\n\"\n"
".ascii \" printzv $zvalue\\n\"\n"
".ascii \" set $cv_idx = $cv_idx + 1\\n\"\n"
diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c
index 829c8d74dc88c..688d271db8147 100644
--- a/main/streams/plain_wrapper.c
+++ b/main/streams/plain_wrapper.c
@@ -43,6 +43,10 @@
# include
#endif
+#ifdef __linux__
+# include
+#endif
+
#define php_stream_fopen_from_fd_int(fd, mode, persistent_id) _php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_CC)
#define php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id) _php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_REL_CC)
#define php_stream_fopen_from_file_int(file, mode) _php_stream_fopen_from_file_int((file), (mode) STREAMS_CC)
@@ -255,7 +259,28 @@ PHPAPI php_stream *_php_stream_fopen_tmpfile(int dummy STREAMS_DC)
static void detect_is_seekable(php_stdio_stream_data *self) {
#if defined(S_ISFIFO) && defined(S_ISCHR)
if (self->fd >= 0 && do_fstat(self, 0) == 0) {
+#ifdef __linux__
+ if (S_ISCHR(self->sb.st_mode)) {
+ /* Some character devices are exceptions, check their major/minor ID
+ * https://www.kernel.org/doc/Documentation/admin-guide/devices.txt */
+ if (major(self->sb.st_rdev) == 1) {
+ unsigned m = minor(self->sb.st_rdev);
+ self->is_seekable =
+ m == 1 || /* /dev/mem */
+ m == 2 || /* /dev/kmem */
+ m == 3 || /* /dev/null */
+ m == 4 || /* /dev/port (seekable, offset = I/O port) */
+ m == 5 || /* /dev/zero */
+ m == 7; /* /dev/full */
+ } else {
+ self->is_seekable = false;
+ }
+ } else {
+ self->is_seekable = !S_ISFIFO(self->sb.st_mode);
+ }
+#else
self->is_seekable = !(S_ISFIFO(self->sb.st_mode) || S_ISCHR(self->sb.st_mode));
+#endif
self->is_pipe = S_ISFIFO(self->sb.st_mode);
}
#elif defined(PHP_WIN32)