From 71ffa9596a9c7b5c58f6da625fb3fe44cfce2778 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Wed, 7 May 2025 14:24:07 +0200 Subject: [PATCH 01/62] bump zip extension version to 1.22.6 --- ext/zip/php_zip.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h index 1a1ffb1da3b8a..99409d3fc89d4 100644 --- a/ext/zip/php_zip.h +++ b/ext/zip/php_zip.h @@ -39,7 +39,7 @@ extern zend_module_entry zip_module_entry; /* Additionnal flags not from libzip */ #define ZIP_FL_OPEN_FILE_NOW (1u<<30) -#define PHP_ZIP_VERSION "1.22.5" +#define PHP_ZIP_VERSION "1.22.6" #ifdef HAVE_LIBZIP_VERSION #define LIBZIP_VERSION_STR zip_libzip_version() From c6a9beebffe8af58eeb0fb42af7f56455ae02ac3 Mon Sep 17 00:00:00 2001 From: Jorg Adam Sowa Date: Thu, 8 May 2025 13:00:50 +0200 Subject: [PATCH 02/62] ext/standard/md5: Minor refactorings (#18518) - Use size_t type instead of int type - Use false instead of 0 - Remove wrapping comments --- ext/standard/md5.c | 19 +++++++------------ ext/standard/md5.h | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/ext/standard/md5.c b/ext/standard/md5.c index 899ff6aaeecb0..9d0f5f886a6bb 100644 --- a/ext/standard/md5.c +++ b/ext/standard/md5.c @@ -19,32 +19,29 @@ #include "php.h" #include "md5.h" -PHPAPI void make_digest(char *md5str, const unsigned char *digest) /* {{{ */ +PHPAPI void make_digest(char *md5str, const unsigned char *digest) { make_digest_ex(md5str, digest, 16); } -/* }}} */ -PHPAPI void make_digest_ex(char *md5str, const unsigned char *digest, int len) /* {{{ */ +PHPAPI void make_digest_ex(char *md5str, const unsigned char *digest, size_t len) { static const char hexits[17] = "0123456789abcdef"; - int i; - for (i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { md5str[i * 2] = hexits[digest[i] >> 4]; md5str[(i * 2) + 1] = hexits[digest[i] & 0x0F]; } md5str[len * 2] = '\0'; } -/* }}} */ -/* {{{ Calculate the md5 hash of a string */ +/* Calculate the md5 hash of a string */ PHP_FUNCTION(md5) { zend_string *arg; - bool raw_output = 0; PHP_MD5_CTX context; unsigned char digest[16]; + bool raw_output = false; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(arg) @@ -63,14 +60,13 @@ PHP_FUNCTION(md5) } } -/* }}} */ -/* {{{ Calculate the md5 hash of given filename */ +/* Calculate the md5 hash of given filename */ PHP_FUNCTION(md5_file) { char *arg; size_t arg_len; - bool raw_output = 0; + bool raw_output = false; unsigned char buf[1024]; unsigned char digest[16]; PHP_MD5_CTX context; @@ -113,7 +109,6 @@ PHP_FUNCTION(md5_file) make_digest_ex(Z_STRVAL_P(return_value), digest, 16); } } -/* }}} */ /* * This is an OpenSSL-compatible implementation of the RSA Data Security, diff --git a/ext/standard/md5.h b/ext/standard/md5.h index c488b5c534e36..5444abf46aec6 100644 --- a/ext/standard/md5.h +++ b/ext/standard/md5.h @@ -19,7 +19,7 @@ #define MD5_H PHPAPI void make_digest(char *md5str, const unsigned char *digest); -PHPAPI void make_digest_ex(char *md5str, const unsigned char *digest, int len); +PHPAPI void make_digest_ex(char *md5str, const unsigned char *digest, size_t len); /* * This is an OpenSSL-compatible implementation of the RSA Data Security, From 4527bafad07d3e365887fcf3d99c55b40bb2df59 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:08:18 -0700 Subject: [PATCH 03/62] gen_stub: break up closing tag in DOMCdataSection Otherwise GitHub's syntax highlighting treats it as the end of the code and stops highlighting --- build/gen_stub.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 1919d2e702a27..d15313d5c097d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1975,12 +1975,16 @@ private function getExampleSection(DOMDocument $doc, string $id): DOMElement { $prog = $doc->createElement('programlisting'); $prog->setAttribute('role', 'php'); + // So that GitHub syntax highlighting doesn't treat the closing tag + // in the DOMCdataSection as indication that it should stop syntax + // highlighting, break it up + $empty = ''; $code = new DOMCdataSection( << +?$empty> CODE_EXAMPLE ); From bfa2b92ca626fea32fa3a1c720617af739e0b117 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:14:22 -0700 Subject: [PATCH 04/62] gen_stub: add `ExposedDocComment::getInitCode()` Deduplicates the setting up of the `zend_string_init_interned()` call, removes the need for `ExposedDocComment::getLength()` and so that method is removed. --- build/gen_stub.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index d15313d5c097d..dd29d6ff539f8 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -2728,9 +2728,8 @@ private function getClassConstDeclaration(EvaluatedValue $value, array $allConst if ($this->exposedDocComment) { $commentCode = "const_{$constName}_comment"; - $escapedComment = $this->exposedDocComment->escape(); - $escapedCommentLength = $this->exposedDocComment->getLength(); - $code .= "\tzend_string *$commentCode = zend_string_init_interned(\"$escapedComment\", $escapedCommentLength, 1);\n"; + $escapedCommentInit = $this->exposedDocComment->getInitCode(); + $code .= "\tzend_string *$commentCode = $escapedCommentInit\n"; } else { $commentCode = "NULL"; } @@ -3056,9 +3055,8 @@ public function getDeclaration(array $allConstInfos): string { if ($this->exposedDocComment) { $commentCode = "property_{$propertyName}_comment"; - $escapedComment = $this->exposedDocComment->escape(); - $escapedCommentLength = $this->exposedDocComment->getLength(); - $code .= "\tzend_string *$commentCode = zend_string_init_interned(\"$escapedComment\", $escapedCommentLength, 1);\n"; + $escapedCommentInit = $this->exposedDocComment->getInitCode(); + $code .= "\tzend_string *$commentCode = $escapedCommentInit\n"; } else { $commentCode = "NULL"; } @@ -3450,7 +3448,7 @@ public function getRegistration(array $allConstInfos): string $code .= "#if (PHP_VERSION_ID >= " . PHP_84_VERSION_ID . ")\n"; } - $code .= "\tclass_entry->doc_comment = zend_string_init_interned(\"" . $this->exposedDocComment->escape() . "\", " . $this->exposedDocComment->getLength() . ", 1);\n"; + $code .= "\tclass_entry->doc_comment = " . $this->exposedDocComment->getInitCode() . "\n"; if (!$php84MinimumCompatibility) { $code .= "#endif\n"; @@ -4278,8 +4276,8 @@ public function escape(): string { return str_replace("\n", '\n', addslashes($this->docComment)); } - public function getLength(): int { - return strlen($this->docComment); + public function getInitCode(): string { + return "zend_string_init_interned(\"" . $this->escape() . "\", " . strlen($this->docComment) . ", 1);"; } /** @param array $comments */ From 45d313bbd761a80ae745f483e3f2b2c2c4b4678f Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:33:41 -0700 Subject: [PATCH 05/62] gen_stub: simplify `generateVersionDependentFlagCode()` * Return a string rather than an array, all callers just immediately used `implode()` to join the elements in the array with nothing between them * In the callers, inline some single-use variables with the template for the version-dependent code * Remove the callback to `array_filter` specifying that only items that are not `empty()` be removed - this is the default behavior --- build/gen_stub.php | 51 ++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index dd29d6ff539f8..9d21269af5e3a 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1396,7 +1396,7 @@ public function getFunctionEntry(): string { $flagsByPhpVersions, $this->minimumPhpVersionIdCompatibility ); - $functionEntryCode = rtrim(implode("", $flagsCode)); + $functionEntryCode = rtrim($flagsCode); } } } @@ -1439,25 +1439,21 @@ public function getFunctionEntry(): string { $docComment = $this->exposedDocComment ? '"' . $this->exposedDocComment->escape() . '"' : "NULL"; $framelessFuncInfosName = !empty($this->framelessFunctionInfos) ? $this->getFramelessFunctionInfosName() : "NULL"; - $template = "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s, $framelessFuncInfosName, $docComment)\n"; - $flagsCode = generateVersionDependentFlagCode( - $template, + $code .= generateVersionDependentFlagCode( + "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s, $framelessFuncInfosName, $docComment)\n", $php84AndAboveFlags, PHP_84_VERSION_ID ); - $code .= implode("", $flagsCode); if (!$php84MinimumCompatibility) { $code .= "#else\n"; $flags = array_slice($flagsByPhpVersions, 0, 4, true); - $template = "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s)\n"; - $flagsCode = generateVersionDependentFlagCode( - $template, + $code .= generateVersionDependentFlagCode( + "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s)\n", $flags, $this->minimumPhpVersionIdCompatibility ); - $code .= implode("", $flagsCode); $code .= "#endif\n"; } @@ -2750,12 +2746,11 @@ private function getClassConstDeclaration(EvaluatedValue $value, array $allConst } $template .= "zend_declare_typed_class_constant(class_entry, $nameCode, &const_{$constName}_value, %s, $commentCode, $typeCode);\n"; - $flagsCode = generateVersionDependentFlagCode( + $code .= generateVersionDependentFlagCode( $template, $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility ); - $code .= implode("", $flagsCode); } if ($this->type && !$php83MinimumCompatibility) { @@ -2769,12 +2764,11 @@ private function getClassConstDeclaration(EvaluatedValue $value, array $allConst $template = "\t"; } $template .= "zend_declare_class_constant_ex(class_entry, $nameCode, &const_{$constName}_value, %s, $commentCode);\n"; - $flagsCode = generateVersionDependentFlagCode( + $code .= generateVersionDependentFlagCode( $template, $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility ); - $code .= implode("", $flagsCode); } if ($this->type && !$php83MinimumCompatibility) { @@ -3074,12 +3068,11 @@ public function getDeclaration(array $allConstInfos): string { $template .= "zend_declare_property_ex(class_entry, $nameCode, &$zvalName, %s, $commentCode);\n"; } - $flagsCode = generateVersionDependentFlagCode( + $code .= generateVersionDependentFlagCode( $template, $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility ); - $code .= implode("", $flagsCode); $code .= $stringRelease; @@ -3396,8 +3389,7 @@ public function getRegistration(array $allConstInfos): string $code .= "{\n"; - $flagCodes = generateVersionDependentFlagCode("%s", $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility); - $flags = implode("", $flagCodes); + $flags = generateVersionDependentFlagCode("%s", $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility); $classMethods = ($this->funcInfos === []) ? 'NULL' : "class_{$escapedName}_methods"; if ($this->type === "enum") { @@ -5403,9 +5395,9 @@ function generateOptimizerInfo(array $funcMap): string { /** * @param array $flagsByPhpVersions - * @return string[] + * @return string */ -function generateVersionDependentFlagCode(string $codeTemplate, array $flagsByPhpVersions, ?int $phpVersionIdMinimumCompatibility): array +function generateVersionDependentFlagCode(string $codeTemplate, array $flagsByPhpVersions, ?int $phpVersionIdMinimumCompatibility): string { $phpVersions = ALL_PHP_VERSION_IDS; sort($phpVersions); @@ -5414,10 +5406,10 @@ function generateVersionDependentFlagCode(string $codeTemplate, array $flagsByPh // No version compatibility is needed if ($phpVersionIdMinimumCompatibility === null) { if (empty($flagsByPhpVersions[$currentPhpVersion])) { - return []; + return ''; } - return [sprintf($codeTemplate, implode("|", $flagsByPhpVersions[$currentPhpVersion]))]; + return sprintf($codeTemplate, implode("|", $flagsByPhpVersions[$currentPhpVersion])); } // Remove flags which depend on a PHP version below the minimally supported one @@ -5429,15 +5421,11 @@ function generateVersionDependentFlagCode(string $codeTemplate, array $flagsByPh $flagsByPhpVersions = array_slice($flagsByPhpVersions, $index, null, true); // Remove empty version-specific flags - $flagsByPhpVersions = array_filter( - $flagsByPhpVersions, - static function (array $value): bool { - return !empty($value); - }); + $flagsByPhpVersions = array_filter($flagsByPhpVersions); // There are no version-specific flags if (empty($flagsByPhpVersions)) { - return []; + return ''; } // Remove version-specific flags which don't differ from the previous one @@ -5457,16 +5445,14 @@ static function (array $value): bool { reset($flagsByPhpVersions); $firstVersion = key($flagsByPhpVersions); if ($firstVersion === $phpVersionIdMinimumCompatibility) { - return [sprintf($codeTemplate, implode("|", reset($flagsByPhpVersions)))]; + return sprintf($codeTemplate, implode("|", reset($flagsByPhpVersions))); } } // Add the necessary conditions around the code using the version-specific flags - $result = []; + $code = ''; $i = 0; foreach (array_reverse($flagsByPhpVersions, true) as $version => $versionFlags) { - $code = ""; - $if = $i === 0 ? "#if" : "#elif"; $endif = $i === $flagCount - 1 ? "#endif\n" : ""; @@ -5475,11 +5461,10 @@ static function (array $value): bool { $code .= sprintf($codeTemplate, implode("|", $versionFlags)); $code .= $endif; - $result[] = $code; $i++; } - return $result; + return $code; } /** From 0d79039027b648c91212baee5a715d014a36509e Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:39:00 -0700 Subject: [PATCH 06/62] gen_stub: simplify `ArgInfo::getDefaultValueAsMethodSynopsisString()` There is no need to add special handling for the default value of `null`, since it is not loosely-equals to any of the strings 'UNKNOWN', 'false', 'true', or 'null' it will just be returned directly anyway. --- build/gen_stub.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 9d21269af5e3a..4643a424f9356 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -848,10 +848,6 @@ public function getDefaultValueAsArginfoString(): string { } public function getDefaultValueAsMethodSynopsisString(): ?string { - if ($this->defaultValue === null) { - return null; - } - switch ($this->defaultValue) { case 'UNKNOWN': return null; From 24b7c7a36533db0319c9de3b2c03f4f806fdc6cd Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:55:03 -0700 Subject: [PATCH 07/62] gen_stub: add `ArgInfo::toZendInfo()` Move the logic out of `funcInfoToCode()` and update it. In the process, make `ArgInfo::getDefaultValueAsArginfoString()` private. --- build/gen_stub.php | 92 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 4643a424f9356..81fd0ccece072 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -839,7 +839,7 @@ public function hasProperDefaultValue(): bool { return $this->defaultValue !== null && $this->defaultValue !== "UNKNOWN"; } - public function getDefaultValueAsArginfoString(): string { + private function getDefaultValueAsArginfoString(): string { if ($this->hasProperDefaultValue()) { return '"' . addslashes($this->defaultValue) . '"'; } @@ -859,6 +859,50 @@ public function getDefaultValueAsMethodSynopsisString(): ?string { return $this->defaultValue; } + + public function toZendInfo(): string { + $argKind = $this->isVariadic ? "ARG_VARIADIC" : "ARG"; + $argDefaultKind = $this->hasProperDefaultValue() ? "_WITH_DEFAULT_VALUE" : ""; + $argType = $this->type; + if ($argType !== null) { + if (null !== $simpleArgType = $argType->tryToSimpleType()) { + if ($simpleArgType->isBuiltin) { + return sprintf( + "\tZEND_%s_TYPE_INFO%s(%s, %s, %s, %d%s)\n", + $argKind, $argDefaultKind, $this->sendBy, $this->name, + $simpleArgType->toTypeCode(), $argType->isNullable(), + $this->hasProperDefaultValue() ? ", " . $this->getDefaultValueAsArginfoString() : "" + ); + } + return sprintf( + "\tZEND_%s_OBJ_INFO%s(%s, %s, %s, %d%s)\n", + $argKind, $argDefaultKind, $this->sendBy, $this->name, + $simpleArgType->toEscapedName(), $argType->isNullable(), + $this->hasProperDefaultValue() ? ", " . $this->getDefaultValueAsArginfoString() : "" + ); + } + $arginfoType = $argType->toArginfoType(); + if ($arginfoType->hasClassType()) { + return sprintf( + "\tZEND_%s_OBJ_TYPE_MASK(%s, %s, %s, %s%s)\n", + $argKind, $this->sendBy, $this->name, + $arginfoType->toClassTypeString(), $arginfoType->toTypeMask(), + !$this->isVariadic ? ", " . $this->getDefaultValueAsArginfoString() : "" + ); + } + return sprintf( + "\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n", + $argKind, $this->sendBy, $this->name, + $arginfoType->toTypeMask(), + $this->getDefaultValueAsArginfoString() + ); + } + return sprintf( + "\tZEND_%s_INFO%s(%s, %s%s)\n", + $argKind, $argDefaultKind, $this->sendBy, $this->name, + $this->hasProperDefaultValue() ? ", " . $this->getDefaultValueAsArginfoString() : "" + ); + } } interface VariableLikeName { @@ -5029,51 +5073,7 @@ function funcInfoToCode(FileInfo $fileInfo, FuncInfo $funcInfo): string { } foreach ($funcInfo->args as $argInfo) { - $argKind = $argInfo->isVariadic ? "ARG_VARIADIC" : "ARG"; - $argDefaultKind = $argInfo->hasProperDefaultValue() ? "_WITH_DEFAULT_VALUE" : ""; - $argType = $argInfo->type; - if ($argType !== null) { - if (null !== $simpleArgType = $argType->tryToSimpleType()) { - if ($simpleArgType->isBuiltin) { - $code .= sprintf( - "\tZEND_%s_TYPE_INFO%s(%s, %s, %s, %d%s)\n", - $argKind, $argDefaultKind, $argInfo->sendBy, $argInfo->name, - $simpleArgType->toTypeCode(), $argType->isNullable(), - $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" - ); - } else { - $code .= sprintf( - "\tZEND_%s_OBJ_INFO%s(%s, %s, %s, %d%s)\n", - $argKind, $argDefaultKind, $argInfo->sendBy, $argInfo->name, - $simpleArgType->toEscapedName(), $argType->isNullable(), - $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" - ); - } - } else { - $arginfoType = $argType->toArginfoType(); - if ($arginfoType->hasClassType()) { - $code .= sprintf( - "\tZEND_%s_OBJ_TYPE_MASK(%s, %s, %s, %s%s)\n", - $argKind, $argInfo->sendBy, $argInfo->name, - $arginfoType->toClassTypeString(), $arginfoType->toTypeMask(), - !$argInfo->isVariadic ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" - ); - } else { - $code .= sprintf( - "\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n", - $argKind, $argInfo->sendBy, $argInfo->name, - $arginfoType->toTypeMask(), - $argInfo->getDefaultValueAsArginfoString() - ); - } - } - } else { - $code .= sprintf( - "\tZEND_%s_INFO%s(%s, %s%s)\n", - $argKind, $argDefaultKind, $argInfo->sendBy, $argInfo->name, - $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" - ); - } + $code .= $argInfo->toZendInfo(); } $code .= "ZEND_END_ARG_INFO()"; From b5361d75e0997035ded5fe2a89c3ece447ce52bd Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 22:44:45 -0700 Subject: [PATCH 08/62] gen_stub: add `ReturnInfo::beginArgInfo()` The vast majority of the decisions about the use of `ZEND_BEGIN_ARG_INFO_EX` or one of its variations are based on the return information of the function - is the type builtin, is the return information tentative, does it include an object mask, etc. Accordingly, move the logic into the `ReturnInfo` class. The logic is actually moved into two methods, `ReturnInfo::beginArgInfo()`, which needs to handle the case of tentative returns being used when PHP < 8.1 is supported, and `::beginArgInfoCompatible()`, which can assume that PHP 8.1+ is supported and thus make use of early returns and guard clauses. Further improvements to the logic will be made in a subsequent commit. In the process, make `ReturnInfo::$byRef` private. --- build/gen_stub.php | 129 ++++++++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 81fd0ccece072..c8a2e7a3fb48d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1146,7 +1146,7 @@ class ReturnInfo { self::REFCOUNT_N, ]; - public /* readonly */ bool $byRef; + private /* readonly */ bool $byRef; // NOT readonly - gets removed when discarding info for older PHP versions public ?Type $type; public /* readonly */ ?Type $phpDocType; @@ -1193,6 +1193,69 @@ private function setRefcount(?string $refcount): void $this->refcount = $refcount; } + + public function beginArgInfo(string $funcInfoName, int $minArgs, bool $php81MinimumCompatibility): string { + $code = $this->beginArgInfoCompatible($funcInfoName, $minArgs); + if ($this->type !== null && $this->tentativeReturnType && !$php81MinimumCompatibility) { + $realCode = "#if (PHP_VERSION_ID >= " . PHP_81_VERSION_ID . ")\n"; + $realCode .= $code; + $realCode .= sprintf( + "#else\nZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n#endif\n", + $funcInfoName, $this->byRef, $minArgs + ); + return $realCode; + } + return $code; + } + + /** + * Assumes PHP 8.1 compatibility, if that is not the case the caller is + * responsible for making the use of a tentative return type conditional + * based on the PHP version. Separate to allow using early returns + */ + private function beginArgInfoCompatible(string $funcInfoName, int $minArgs): string { + if ($this->type !== null) { + if (null !== $simpleReturnType = $this->type->tryToSimpleType()) { + if ($simpleReturnType->isBuiltin) { + return sprintf( + "%s(%s, %d, %d, %s, %d)\n", + $this->tentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX", + $funcInfoName, $this->byRef, + $minArgs, + $simpleReturnType->toTypeCode(), $this->type->isNullable() + ); + } + return sprintf( + "%s(%s, %d, %d, %s, %d)\n", + $this->tentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX", + $funcInfoName, $this->byRef, + $minArgs, + $simpleReturnType->toEscapedName(), $this->type->isNullable() + ); + } + $arginfoType = $this->type->toArginfoType(); + if ($arginfoType->hasClassType()) { + return sprintf( + "%s(%s, %d, %d, %s, %s)\n", + $this->tentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX", + $funcInfoName, $this->byRef, + $minArgs, + $arginfoType->toClassTypeString(), $arginfoType->toTypeMask() + ); + } + return sprintf( + "%s(%s, %d, %d, %s)\n", + $this->tentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX", + $funcInfoName, $this->byRef, + $minArgs, + $arginfoType->toTypeMask() + ); + } + return sprintf( + "ZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n", + $funcInfoName, $this->byRef, $minArgs + ); + } } class FuncInfo { @@ -5012,65 +5075,11 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string { } function funcInfoToCode(FileInfo $fileInfo, FuncInfo $funcInfo): string { - $code = ''; - $returnType = $funcInfo->return->type; - $isTentativeReturnType = $funcInfo->return->tentativeReturnType; - $php81MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_81_VERSION_ID; - - if ($returnType !== null) { - if ($isTentativeReturnType && !$php81MinimumCompatibility) { - $code .= "#if (PHP_VERSION_ID >= " . PHP_81_VERSION_ID . ")\n"; - } - if (null !== $simpleReturnType = $returnType->tryToSimpleType()) { - if ($simpleReturnType->isBuiltin) { - $code .= sprintf( - "%s(%s, %d, %d, %s, %d)\n", - $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, - $funcInfo->numRequiredArgs, - $simpleReturnType->toTypeCode(), $returnType->isNullable() - ); - } else { - $code .= sprintf( - "%s(%s, %d, %d, %s, %d)\n", - $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, - $funcInfo->numRequiredArgs, - $simpleReturnType->toEscapedName(), $returnType->isNullable() - ); - } - } else { - $arginfoType = $returnType->toArginfoType(); - if ($arginfoType->hasClassType()) { - $code .= sprintf( - "%s(%s, %d, %d, %s, %s)\n", - $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, - $funcInfo->numRequiredArgs, - $arginfoType->toClassTypeString(), $arginfoType->toTypeMask() - ); - } else { - $code .= sprintf( - "%s(%s, %d, %d, %s)\n", - $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, - $funcInfo->numRequiredArgs, - $arginfoType->toTypeMask() - ); - } - } - if ($isTentativeReturnType && !$php81MinimumCompatibility) { - $code .= sprintf( - "#else\nZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n#endif\n", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, $funcInfo->numRequiredArgs - ); - } - } else { - $code .= sprintf( - "ZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, $funcInfo->numRequiredArgs - ); - } + $code = $funcInfo->return->beginArgInfo( + $funcInfo->getArgInfoName(), + $funcInfo->numRequiredArgs, + $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_81_VERSION_ID + ); foreach ($funcInfo->args as $argInfo) { $code .= $argInfo->toZendInfo(); From 39a6d6086e0f39aef9efadf6f40e8b33df70b0bd Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 22:51:16 -0700 Subject: [PATCH 09/62] gen_stub: move `funcInfoToCode()` into `FuncInfo` Reduce the number of global functions by moving it to instance method `FuncInfo::toArgInfoCode()`. In the process, make `FuncInfo::$numRequiredArgs` private. --- build/gen_stub.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index c8a2e7a3fb48d..2c08c564c3523 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1270,7 +1270,7 @@ class FuncInfo { /** @var ArgInfo[] */ public /* readonly */ array $args; public /* readonly */ ReturnInfo $return; - public /* readonly */ int $numRequiredArgs; + private /* readonly */ int $numRequiredArgs; public /* readonly */ ?string $cond; public bool $isUndocumentable; private ?int $minimumPhpVersionIdCompatibility; @@ -2228,6 +2228,21 @@ public function findEquivalent(array $generatedFuncInfos): ?FuncInfo { return null; } + public function toArgInfoCode(?int $minPHPCompatability): string { + $code = $this->return->beginArgInfo( + $this->getArgInfoName(), + $this->numRequiredArgs, + $minPHPCompatability === null || $minPHPCompatability >= PHP_81_VERSION_ID + ); + + foreach ($this->args as $argInfo) { + $code .= $argInfo->toZendInfo(); + } + + $code .= "ZEND_END_ARG_INFO()"; + return $code . "\n"; + } + public function __clone() { foreach ($this->args as $key => $argInfo) { @@ -5074,21 +5089,6 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string { return $fileInfo; } -function funcInfoToCode(FileInfo $fileInfo, FuncInfo $funcInfo): string { - $code = $funcInfo->return->beginArgInfo( - $funcInfo->getArgInfoName(), - $funcInfo->numRequiredArgs, - $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_81_VERSION_ID - ); - - foreach ($funcInfo->args as $argInfo) { - $code .= $argInfo->toZendInfo(); - } - - $code .= "ZEND_END_ARG_INFO()"; - return $code . "\n"; -} - /** * @template T * @param iterable $infos @@ -5168,7 +5168,7 @@ static function (FuncInfo $funcInfo) use (&$generatedFuncInfos, $fileInfo) { $funcInfo->getArgInfoName(), $generatedFuncInfo->getArgInfoName() ); } else { - $code = funcInfoToCode($fileInfo, $funcInfo); + $code = $funcInfo->toArgInfoCode($fileInfo->getMinimumPhpVersionIdCompatibility()); } $generatedFuncInfos[] = $funcInfo; From 48613915015b89f5be9df09bc12eb1c35a9f8d60 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 23:35:20 -0700 Subject: [PATCH 10/62] gen_stub: inline some single-use variables --- build/gen_stub.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 2c08c564c3523..adcdfd5b2607d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -710,9 +710,7 @@ public function getTypeForDoc(DOMDocument $doc): DOMElement { } } else { $type = $this->types[0]; - $name = $type->name; - - $typeElement = $doc->createElement('type', $name); + $typeElement = $doc->createElement('type', $type->name); } return $typeElement; @@ -1937,9 +1935,8 @@ private function getReturnValueSection(DOMDocument $doc): DOMElement { $returnDescriptionPara->appendChild(new DOMText("Description.")); } else if (count($returnType->types) === 1) { $type = $returnType->types[0]; - $name = $type->name; - switch ($name) { + switch ($type->name) { case 'void': $descriptionNode = $doc->createEntityReference('return.void'); break; From 722eba20aefa23c2db0ed707e7cc2ae4acdf6eea Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 17 Mar 2025 00:27:58 -0700 Subject: [PATCH 11/62] gen_stub: drop unused parameters The following parameters were either unused before this commit or became unused as part of updating callers to stop passing unused parameters to other functions updated in this commit: * `FuncInfo::getMethodSynopsisDocument()` - `$funcMap`, `$aliasMap` * `FuncInfo::getMethodSynopsisElement()` - `$funcMap`, `$aliasMap` * `ConstInfo::getGlobalConstDeclaration()` - `$allConstInfos` * `generateMethodSynopses()` - `$aliasMap` * `replaceMethodSynopses()` - `$aliasMap` --- build/gen_stub.php | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index adcdfd5b2607d..dfbd2d6a92f63 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1684,11 +1684,9 @@ private function generateRefSect1(DOMDocument $doc, string $role): DOMElement { } /** - * @param array $funcMap - * @param array $aliasMap * @throws Exception */ - public function getMethodSynopsisDocument(array $funcMap, array $aliasMap): ?string { + public function getMethodSynopsisDocument(): ?string { $REFSEC1_SEPERATOR = "\n\n "; $doc = new DOMDocument("1.0", "utf-8"); @@ -1733,7 +1731,7 @@ public function getMethodSynopsisDocument(array $funcMap, array $aliasMap): ?str /* Creation of */ $descriptionRefSec = $this->generateRefSect1($doc, 'description'); - $methodSynopsis = $this->getMethodSynopsisElement($funcMap, $aliasMap, $doc); + $methodSynopsis = $this->getMethodSynopsisElement($doc); if (!$methodSynopsis) { return null; } @@ -2117,11 +2115,9 @@ private function getExampleSection(DOMDocument $doc, string $id): DOMElement { } /** - * @param array $funcMap - * @param array $aliasMap * @throws Exception */ - public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDocument $doc): ?DOMElement { + public function getMethodSynopsisElement(DOMDocument $doc): ?DOMElement { if ($this->hasParamWithUnknownDefaultValue()) { return null; } @@ -2773,7 +2769,7 @@ public function getDeclaration(array $allConstInfos): string if ($this->name->isClassConst()) { $code .= $this->getClassConstDeclaration($value, $allConstInfos); } else { - $code .= $this->getGlobalConstDeclaration($value, $allConstInfos); + $code .= $this->getGlobalConstDeclaration($value); } $code .= $this->getValueAssertion($value); @@ -2784,8 +2780,7 @@ public function getDeclaration(array $allConstInfos): string return $code; } - /** @param array $allConstInfos */ - private function getGlobalConstDeclaration(EvaluatedValue $value, array $allConstInfos): string + private function getGlobalConstDeclaration(EvaluatedValue $value): string { $constName = str_replace('\\', '\\\\', $this->name->__toString()); $constValue = $value->value; @@ -5783,14 +5778,13 @@ function getReplacedSynopsisXml(string $xml): string /** * @param array $funcMap - * @param array $aliasMap * @return array */ -function generateMethodSynopses(array $funcMap, array $aliasMap): array { +function generateMethodSynopses(array $funcMap): array { $result = []; foreach ($funcMap as $funcInfo) { - $methodSynopsis = $funcInfo->getMethodSynopsisDocument($funcMap, $aliasMap); + $methodSynopsis = $funcInfo->getMethodSynopsisDocument(); if ($methodSynopsis !== null) { $result[$funcInfo->name->getMethodSynopsisFilename() . ".xml"] = $methodSynopsis; } @@ -5801,7 +5795,6 @@ function generateMethodSynopses(array $funcMap, array $aliasMap): array { /** * @param array $funcMap - * @param array $aliasMap * @param array $methodSynopsisWarnings * @param array $undocumentedFuncMap * @return array @@ -5809,7 +5802,6 @@ function generateMethodSynopses(array $funcMap, array $aliasMap): array { function replaceMethodSynopses( string $targetDirectory, array $funcMap, - array $aliasMap, bool $isVerifyManual, array &$methodSynopsisWarnings, array &$undocumentedFuncMap @@ -5908,7 +5900,7 @@ function replaceMethodSynopses( $funcInfo = $funcMap[$funcName]; $documentedFuncMap[$funcInfo->name->__toString()] = $funcInfo->name->__toString(); - $newMethodSynopsis = $funcInfo->getMethodSynopsisElement($funcMap, $aliasMap, $doc); + $newMethodSynopsis = $funcInfo->getMethodSynopsisElement($doc); if ($newMethodSynopsis === null) { continue; } @@ -6326,7 +6318,7 @@ function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc } if ($generateMethodSynopses) { - $methodSynopses = generateMethodSynopses($funcMap, $aliasMap); + $methodSynopses = generateMethodSynopses($funcMap); if (!file_exists($manualTarget)) { mkdir($manualTarget); } @@ -6345,7 +6337,7 @@ function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc } if ($replaceMethodSynopses || $verifyManual) { - $methodSynopses = replaceMethodSynopses($manualTarget, $funcMap, $aliasMap, $verifyManual, $methodSynopsisWarnings, $undocumentedFuncMap); + $methodSynopses = replaceMethodSynopses($manualTarget, $funcMap, $verifyManual, $methodSynopsisWarnings, $undocumentedFuncMap); if ($replaceMethodSynopses) { foreach ($methodSynopses as $filename => $content) { From ec3ecdc2c810b71ecc4d1f1627a81f5def3e6d6a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 12:10:28 -0700 Subject: [PATCH 12/62] gen_stub: documentation updates * Use `@param` instead of `@var` for parameters * Fix type of `$attributeGroups` in `AttributeInfo::createFromGroups()` * Remove extra documentation of `$allConstInfo` for `ClassInfo::getClassSynopsisDocument()`, it is already documented under the correct name `$allConstInfos` * Remove unneeded `@throws` --- build/gen_stub.php | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index dfbd2d6a92f63..9d4a9138d0502 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1683,9 +1683,6 @@ private function generateRefSect1(DOMDocument $doc, string $role): DOMElement { return $refSec; } - /** - * @throws Exception - */ public function getMethodSynopsisDocument(): ?string { $REFSEC1_SEPERATOR = "\n\n "; @@ -2114,9 +2111,6 @@ private function getExampleSection(DOMDocument $doc, string $id): DOMElement { return $refSec; } - /** - * @throws Exception - */ public function getMethodSynopsisElement(DOMDocument $doc): ?DOMElement { if ($this->hasParamWithUnknownDefaultValue()) { return null; @@ -2438,7 +2432,7 @@ abstract class VariableLike protected /* readonly */ ?ExposedDocComment $exposedDocComment; /** - * @var AttributeInfo[] $attributes + * @param AttributeInfo[] $attributes */ public function __construct( int $flags, @@ -2621,7 +2615,7 @@ class ConstInfo extends VariableLike private /* readonly */ bool $isFileCacheAllowed; /** - * @var AttributeInfo[] $attributes + * @param AttributeInfo[] $attributes */ public function __construct( AbstractConstName $name, @@ -3075,7 +3069,7 @@ class PropertyInfo extends VariableLike ]; /** - * @var AttributeInfo[] $attributes + * @param AttributeInfo[] $attributes */ public function __construct( PropertyName $name, @@ -3373,7 +3367,7 @@ public function generateCode(string $invocation, string $nameSuffix, array $allC } /** - * @param array> $attributeGroups + * @param AttributeGroup[] $attributeGroups * @return AttributeInfo[] */ public static function createFromGroups(array $attributeGroups): array { @@ -3733,7 +3727,6 @@ public function discardInfoForOldPhpVersions(?int $phpVersionIdMinimumCompatibil /** * @param array $classMap * @param array $allConstInfos - * @param iterable $allConstInfo */ public function getClassSynopsisDocument(array $classMap, array $allConstInfos): ?string { $doc = new DOMDocument(); From 1c9b6b84df98cbea4d80f508dfa2d54a0ce0e7e2 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 12:56:29 -0700 Subject: [PATCH 13/62] gen_stub: move `handleStatements()` into `FileInfo` Reduce the number of global functions by moving it to instance method `FileInfo::handleStatements()`. --- build/gen_stub.php | 290 ++++++++++++++++++++++----------------------- 1 file changed, 145 insertions(+), 145 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 9d4a9138d0502..b31af1d58c908 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -4298,6 +4298,150 @@ public function getMinimumPhpVersionIdCompatibility(): ?int { public function shouldGenerateLegacyArginfo(): bool { return $this->minimumPhpVersionIdCompatibility !== null && $this->minimumPhpVersionIdCompatibility < PHP_80_VERSION_ID; } + + public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { + $conds = []; + foreach ($stmts as $stmt) { + $cond = handlePreprocessorConditions($conds, $stmt); + + if ($stmt instanceof Stmt\Nop) { + continue; + } + + if ($stmt instanceof Stmt\Namespace_) { + $this->handleStatements($stmt->stmts, $prettyPrinter); + continue; + } + + if ($stmt instanceof Stmt\Const_) { + foreach ($stmt->consts as $const) { + $this->constInfos[] = parseConstLike( + $prettyPrinter, + new ConstName($const->namespacedName, $const->name->toString()), + $const, + 0, + null, + $stmt->getComments(), + $cond, + $this->isUndocumentable, + $this->getMinimumPhpVersionIdCompatibility(), + [] + ); + } + continue; + } + + if ($stmt instanceof Stmt\Function_) { + $this->funcInfos[] = parseFunctionLike( + $prettyPrinter, + new FunctionName($stmt->namespacedName), + 0, + 0, + $stmt, + $cond, + $this->isUndocumentable, + $this->getMinimumPhpVersionIdCompatibility() + ); + continue; + } + + if ($stmt instanceof Stmt\ClassLike) { + $className = $stmt->namespacedName; + $constInfos = []; + $propertyInfos = []; + $methodInfos = []; + $enumCaseInfos = []; + foreach ($stmt->stmts as $classStmt) { + $cond = handlePreprocessorConditions($conds, $classStmt); + if ($classStmt instanceof Stmt\Nop) { + continue; + } + + $classFlags = $stmt instanceof Class_ ? $stmt->flags : 0; + $abstractFlag = $stmt instanceof Stmt\Interface_ ? Modifiers::ABSTRACT : 0; + + if ($classStmt instanceof Stmt\ClassConst) { + foreach ($classStmt->consts as $const) { + $constInfos[] = parseConstLike( + $prettyPrinter, + new ClassConstName($className, $const->name->toString()), + $const, + $classStmt->flags, + $classStmt->type, + $classStmt->getComments(), + $cond, + $this->isUndocumentable, + $this->getMinimumPhpVersionIdCompatibility(), + AttributeInfo::createFromGroups($classStmt->attrGroups) + ); + } + } else if ($classStmt instanceof Stmt\Property) { + if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { + throw new Exception("Visibility modifier is required"); + } + foreach ($classStmt->props as $property) { + $propertyInfos[] = parseProperty( + $className, + $classFlags, + $classStmt->flags, + $property, + $classStmt->type, + $classStmt->getComments(), + $prettyPrinter, + $this->getMinimumPhpVersionIdCompatibility(), + AttributeInfo::createFromGroups($classStmt->attrGroups) + ); + } + } else if ($classStmt instanceof Stmt\ClassMethod) { + if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { + throw new Exception("Visibility modifier is required"); + } + $methodInfos[] = parseFunctionLike( + $prettyPrinter, + new MethodName($className, $classStmt->name->toString()), + $classFlags, + $classStmt->flags | $abstractFlag, + $classStmt, + $cond, + $this->isUndocumentable, + $this->getMinimumPhpVersionIdCompatibility() + ); + } else if ($classStmt instanceof Stmt\EnumCase) { + $enumCaseInfos[] = new EnumCaseInfo( + $classStmt->name->toString(), $classStmt->expr); + } else { + throw new Exception("Not implemented {$classStmt->getType()}"); + } + } + + $this->classInfos[] = parseClass( + $className, + $stmt, + $constInfos, + $propertyInfos, + $methodInfos, + $enumCaseInfos, + $cond, + $this->getMinimumPhpVersionIdCompatibility(), + $this->isUndocumentable + ); + continue; + } + + if ($stmt instanceof Stmt\Expression) { + $expr = $stmt->expr; + if ($expr instanceof Expr\Include_) { + $this->dependencies[] = (string)EvaluatedValue::createFromExpression($expr->expr, null, null, [])->value; + continue; + } + } + + throw new Exception("Unexpected node {$stmt->getType()}"); + } + if (!empty($conds)) { + throw new Exception("Unterminated preprocessor conditions"); + } + } } class DocCommentTag { @@ -4910,150 +5054,6 @@ function getFileDocComments(array $stmts): array { ); } -function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstract $prettyPrinter) { - $conds = []; - foreach ($stmts as $stmt) { - $cond = handlePreprocessorConditions($conds, $stmt); - - if ($stmt instanceof Stmt\Nop) { - continue; - } - - if ($stmt instanceof Stmt\Namespace_) { - handleStatements($fileInfo, $stmt->stmts, $prettyPrinter); - continue; - } - - if ($stmt instanceof Stmt\Const_) { - foreach ($stmt->consts as $const) { - $fileInfo->constInfos[] = parseConstLike( - $prettyPrinter, - new ConstName($const->namespacedName, $const->name->toString()), - $const, - 0, - null, - $stmt->getComments(), - $cond, - $fileInfo->isUndocumentable, - $fileInfo->getMinimumPhpVersionIdCompatibility(), - [] - ); - } - continue; - } - - if ($stmt instanceof Stmt\Function_) { - $fileInfo->funcInfos[] = parseFunctionLike( - $prettyPrinter, - new FunctionName($stmt->namespacedName), - 0, - 0, - $stmt, - $cond, - $fileInfo->isUndocumentable, - $fileInfo->getMinimumPhpVersionIdCompatibility() - ); - continue; - } - - if ($stmt instanceof Stmt\ClassLike) { - $className = $stmt->namespacedName; - $constInfos = []; - $propertyInfos = []; - $methodInfos = []; - $enumCaseInfos = []; - foreach ($stmt->stmts as $classStmt) { - $cond = handlePreprocessorConditions($conds, $classStmt); - if ($classStmt instanceof Stmt\Nop) { - continue; - } - - $classFlags = $stmt instanceof Class_ ? $stmt->flags : 0; - $abstractFlag = $stmt instanceof Stmt\Interface_ ? Modifiers::ABSTRACT : 0; - - if ($classStmt instanceof Stmt\ClassConst) { - foreach ($classStmt->consts as $const) { - $constInfos[] = parseConstLike( - $prettyPrinter, - new ClassConstName($className, $const->name->toString()), - $const, - $classStmt->flags, - $classStmt->type, - $classStmt->getComments(), - $cond, - $fileInfo->isUndocumentable, - $fileInfo->getMinimumPhpVersionIdCompatibility(), - AttributeInfo::createFromGroups($classStmt->attrGroups) - ); - } - } else if ($classStmt instanceof Stmt\Property) { - if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { - throw new Exception("Visibility modifier is required"); - } - foreach ($classStmt->props as $property) { - $propertyInfos[] = parseProperty( - $className, - $classFlags, - $classStmt->flags, - $property, - $classStmt->type, - $classStmt->getComments(), - $prettyPrinter, - $fileInfo->getMinimumPhpVersionIdCompatibility(), - AttributeInfo::createFromGroups($classStmt->attrGroups) - ); - } - } else if ($classStmt instanceof Stmt\ClassMethod) { - if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { - throw new Exception("Visibility modifier is required"); - } - $methodInfos[] = parseFunctionLike( - $prettyPrinter, - new MethodName($className, $classStmt->name->toString()), - $classFlags, - $classStmt->flags | $abstractFlag, - $classStmt, - $cond, - $fileInfo->isUndocumentable, - $fileInfo->getMinimumPhpVersionIdCompatibility() - ); - } else if ($classStmt instanceof Stmt\EnumCase) { - $enumCaseInfos[] = new EnumCaseInfo( - $classStmt->name->toString(), $classStmt->expr); - } else { - throw new Exception("Not implemented {$classStmt->getType()}"); - } - } - - $fileInfo->classInfos[] = parseClass( - $className, - $stmt, - $constInfos, - $propertyInfos, - $methodInfos, - $enumCaseInfos, - $cond, - $fileInfo->getMinimumPhpVersionIdCompatibility(), - $fileInfo->isUndocumentable - ); - continue; - } - - if ($stmt instanceof Stmt\Expression) { - $expr = $stmt->expr; - if ($expr instanceof Expr\Include_) { - $fileInfo->dependencies[] = (string)EvaluatedValue::createFromExpression($expr->expr, null, null, [])->value; - continue; - } - } - - throw new Exception("Unexpected node {$stmt->getType()}"); - } - if (!empty($conds)) { - throw new Exception("Unterminated preprocessor conditions"); - } -} - function parseStubFile(string $code): FileInfo { $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative()); $nodeTraverser = new PhpParser\NodeTraverser; @@ -5070,7 +5070,7 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string { $fileTags = parseDocComments(getFileDocComments($stmts)); $fileInfo = new FileInfo($fileTags); - handleStatements($fileInfo, $stmts, $prettyPrinter); + $fileInfo->handleStatements($stmts, $prettyPrinter); return $fileInfo; } From ce3990c1d3976951c1fe78ef6b7f33afbd63d83b Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 12:58:27 -0700 Subject: [PATCH 14/62] gen_stub: move `handlePreprocessorConditions()` into `FileInfo()` Reduce the number of global functions by moving it to static method `FileInfo::handlePreprocessorConditions()`. Since it is only used by `FileInfo::handleStatements()`, also make it private. --- build/gen_stub.php | 60 +++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index b31af1d58c908..d95c32a549c59 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -4302,7 +4302,7 @@ public function shouldGenerateLegacyArginfo(): bool { public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { $conds = []; foreach ($stmts as $stmt) { - $cond = handlePreprocessorConditions($conds, $stmt); + $cond = self::handlePreprocessorConditions($conds, $stmt); if ($stmt instanceof Stmt\Nop) { continue; @@ -4352,7 +4352,7 @@ public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrin $methodInfos = []; $enumCaseInfos = []; foreach ($stmt->stmts as $classStmt) { - $cond = handlePreprocessorConditions($conds, $classStmt); + $cond = self::handlePreprocessorConditions($conds, $classStmt); if ($classStmt instanceof Stmt\Nop) { continue; } @@ -4442,6 +4442,34 @@ public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrin throw new Exception("Unterminated preprocessor conditions"); } } + + private static function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string { + foreach ($stmt->getComments() as $comment) { + $text = trim($comment->getText()); + if (preg_match('/^#\s*if\s+(.+)$/', $text, $matches)) { + $conds[] = $matches[1]; + } else if (preg_match('/^#\s*ifdef\s+(.+)$/', $text, $matches)) { + $conds[] = "defined($matches[1])"; + } else if (preg_match('/^#\s*ifndef\s+(.+)$/', $text, $matches)) { + $conds[] = "!defined($matches[1])"; + } else if (preg_match('/^#\s*else$/', $text)) { + if (empty($conds)) { + throw new Exception("Encountered else without corresponding #if"); + } + $cond = array_pop($conds); + $conds[] = "!($cond)"; + } else if (preg_match('/^#\s*endif$/', $text)) { + if (empty($conds)) { + throw new Exception("Encountered #endif without corresponding #if"); + } + array_pop($conds); + } else if ($text[0] === '#') { + throw new Exception("Unrecognized preprocessor directive \"$text\""); + } + } + + return empty($conds) ? null : implode(' && ', $conds); + } } class DocCommentTag { @@ -5014,34 +5042,6 @@ function parseClass( ); } -function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string { - foreach ($stmt->getComments() as $comment) { - $text = trim($comment->getText()); - if (preg_match('/^#\s*if\s+(.+)$/', $text, $matches)) { - $conds[] = $matches[1]; - } else if (preg_match('/^#\s*ifdef\s+(.+)$/', $text, $matches)) { - $conds[] = "defined($matches[1])"; - } else if (preg_match('/^#\s*ifndef\s+(.+)$/', $text, $matches)) { - $conds[] = "!defined($matches[1])"; - } else if (preg_match('/^#\s*else$/', $text)) { - if (empty($conds)) { - throw new Exception("Encountered else without corresponding #if"); - } - $cond = array_pop($conds); - $conds[] = "!($cond)"; - } else if (preg_match('/^#\s*endif$/', $text)) { - if (empty($conds)) { - throw new Exception("Encountered #endif without corresponding #if"); - } - array_pop($conds); - } else if ($text[0] === '#') { - throw new Exception("Unrecognized preprocessor directive \"$text\""); - } - } - - return empty($conds) ? null : implode(' && ', $conds); -} - /** @return DocComment[] */ function getFileDocComments(array $stmts): array { if (empty($stmts)) { From d42bac2866a2554cdc04b13ac91a0c89b80e53db Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 13:06:46 -0700 Subject: [PATCH 15/62] gen_stub: move `parseDocComments()` into `DocCommentTag` Reduce the number of global functions by moving it to static method `DocCommentTag::parseDocComments()`. --- build/gen_stub.php | 48 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index d95c32a549c59..64c22f1873aac 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -4528,6 +4528,25 @@ public function getVariableName(): string { return $matches["name"]; } + + /** @return DocCommentTag[] */ + public static function parseDocComments(array $comments): array { + $tags = []; + foreach ($comments as $comment) { + if (!($comment instanceof DocComment)) { + continue; + } + $commentText = substr($comment->getText(), 2, -2); + foreach (explode("\n", $commentText) as $commentLine) { + $regex = '/^\*\s*@([a-z-]+)(?:\s+(.+))?$/'; + if (preg_match($regex, trim($commentLine), $matches)) { + $tags[] = new DocCommentTag($matches[1], $matches[2] ?? null); + } + } + } + + return $tags; + } } // Instances of ExposedDocComment are immutable and do not need to be cloned @@ -4571,25 +4590,6 @@ public static function extractExposedComment(array $comments): ?ExposedDocCommen } } -/** @return DocCommentTag[] */ -function parseDocComments(array $comments): array { - $tags = []; - foreach ($comments as $comment) { - if (!($comment instanceof DocComment)) { - continue; - } - $commentText = substr($comment->getText(), 2, -2); - foreach (explode("\n", $commentText) as $commentLine) { - $regex = '/^\*\s*@([a-z-]+)(?:\s+(.+))?$/'; - if (preg_match($regex, trim($commentLine), $matches)) { - $tags[] = new DocCommentTag($matches[1], $matches[2] ?? null); - } - } - } - - return $tags; -} - // Instances of FramelessFunctionInfo are immutable and do not need to be cloned // when held by an object that is cloned class FramelessFunctionInfo { @@ -4628,7 +4628,7 @@ function parseFunctionLike( $framelessFunctionInfos = []; if ($comments) { - $tags = parseDocComments($comments); + $tags = DocCommentTag::parseDocComments($comments); foreach ($tags as $tag) { switch ($tag->name) { @@ -4817,7 +4817,7 @@ function parseConstLike( $link = null; $isFileCacheAllowed = true; if ($comments) { - $tags = parseDocComments($comments); + $tags = DocCommentTag::parseDocComments($comments); foreach ($tags as $tag) { if ($tag->name === 'var') { $phpDocType = $tag->getType(); @@ -4891,7 +4891,7 @@ function parseProperty( $link = null; if ($comments) { - $tags = parseDocComments($comments); + $tags = DocCommentTag::parseDocComments($comments); foreach ($tags as $tag) { if ($tag->name === 'var') { $phpDocType = $tag->getType(); @@ -4962,7 +4962,7 @@ function parseClass( $allowsDynamicProperties = false; if ($comments) { - $tags = parseDocComments($comments); + $tags = DocCommentTag::parseDocComments($comments); foreach ($tags as $tag) { if ($tag->name === 'alias') { $alias = $tag->getValue(); @@ -5067,7 +5067,7 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string { $stmts = $parser->parse($code); $nodeTraverser->traverse($stmts); - $fileTags = parseDocComments(getFileDocComments($stmts)); + $fileTags = DocCommentTag::parseDocComments(getFileDocComments($stmts)); $fileInfo = new FileInfo($fileTags); $fileInfo->handleStatements($stmts, $prettyPrinter); From bb555926c4bbfb93002459c76494cd6780ae303d Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 14:14:45 -0700 Subject: [PATCH 16/62] gen_stub: deduplicate and simplify DocCommentTag processing For a lot of the structures, the parsing of doc comment tags is based on if a specific tag is present, or the value that it has if it is. Add a new helper method, `DocCommentTag::makeTagMap()`, that turns an array of tag instances into a map from tag name to value (the last value, if there are multiple uses of the same tag name). Then, for the simple cases where just a tag's presence is all that is checked, or just the (last) value is used, check the map instead of using a loop through all of the tags present. --- build/gen_stub.php | 125 +++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 72 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 64c22f1873aac..b9a8941d7c99d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -4547,6 +4547,19 @@ public static function parseDocComments(array $comments): array { return $tags; } + + /** + * @param DocCommentTag[] $tags + * @return array Mapping tag names to the value (or null), + * if a tag is present multiple times the last value is used + */ + public static function makeTagMap(array $tags): array { + $map = []; + foreach ($tags as $tag) { + $map[$tag->name] = $tag->value; + } + return $map; + } } // Instances of ExposedDocComment are immutable and do not need to be cloned @@ -4629,6 +4642,13 @@ function parseFunctionLike( if ($comments) { $tags = DocCommentTag::parseDocComments($comments); + $tagMap = DocCommentTag::makeTagMap($tags); + + $isDeprecated = array_key_exists('deprecated', $tagMap); + $verify = !array_key_exists('no-verify', $tagMap); + $tentativeReturnType = array_key_exists('tentative-return-type', $tagMap); + $supportsCompileTimeEval = array_key_exists('compile-time-eval', $tagMap); + $isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap); foreach ($tags as $tag) { switch ($tag->name) { @@ -4643,18 +4663,6 @@ function parseFunctionLike( } break; - case 'deprecated': - $isDeprecated = true; - break; - - case 'no-verify': - $verify = false; - break; - - case 'tentative-return-type': - $tentativeReturnType = true; - break; - case 'return': $docReturnType = $tag->getType(); break; @@ -4667,10 +4675,6 @@ function parseFunctionLike( $refcount = $tag->getValue(); break; - case 'compile-time-eval': - $supportsCompileTimeEval = true; - break; - case 'prefer-ref': $varName = $tag->getVariableName(); if (!isset($paramMeta[$varName])) { @@ -4679,10 +4683,6 @@ function parseFunctionLike( $paramMeta[$varName][$tag->name] = true; break; - case 'undocumentable': - $isUndocumentable = true; - break; - case 'frameless-function': $framelessFunctionInfos[] = new FramelessFunctionInfo($tag->getValue()); break; @@ -4812,26 +4812,19 @@ function parseConstLike( array $attributes ): ConstInfo { $phpDocType = null; - $deprecated = false; - $cValue = null; - $link = null; - $isFileCacheAllowed = true; - if ($comments) { - $tags = DocCommentTag::parseDocComments($comments); - foreach ($tags as $tag) { - if ($tag->name === 'var') { - $phpDocType = $tag->getType(); - } elseif ($tag->name === 'deprecated') { - $deprecated = true; - } elseif ($tag->name === 'cvalue') { - $cValue = $tag->value; - } elseif ($tag->name === 'undocumentable') { - $isUndocumentable = true; - } elseif ($tag->name === 'link') { - $link = $tag->value; - } elseif ($tag->name === 'no-file-cache') { - $isFileCacheAllowed = false; - } + + $tags = DocCommentTag::parseDocComments($comments); + $tagMap = DocCommentTag::makeTagMap($tags); + + $deprecated = array_key_exists('deprecated', $tagMap); + $isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap); + $isFileCacheAllowed = !array_key_exists('no-file-cache', $tagMap); + $cValue = $tagMap['cvalue'] ?? null; + $link = $tagMap['link'] ?? null; + + foreach ($tags as $tag) { + if ($tag->name === 'var') { + $phpDocType = $tag->getType(); } } @@ -4886,22 +4879,17 @@ function parseProperty( array $attributes ): PropertyInfo { $phpDocType = null; - $isDocReadonly = false; - $isVirtual = false; - $link = null; - if ($comments) { - $tags = DocCommentTag::parseDocComments($comments); - foreach ($tags as $tag) { - if ($tag->name === 'var') { - $phpDocType = $tag->getType(); - } elseif ($tag->name === 'readonly') { - $isDocReadonly = true; - } elseif ($tag->name === 'link') { - $link = $tag->value; - } elseif ($tag->name === 'virtual') { - $isVirtual = true; - } + $tags = DocCommentTag::parseDocComments($comments); + $tagMap = DocCommentTag::makeTagMap($tags); + + $isDocReadonly = array_key_exists('readonly', $tagMap); + $link = $tagMap['link'] ?? null; + $isVirtual = array_key_exists('virtual', $tagMap); + + foreach ($tags as $tag) { + if ($tag->name === 'var') { + $phpDocType = $tag->getType(); } } @@ -4956,25 +4944,18 @@ function parseClass( ): ClassInfo { $comments = $class->getComments(); $alias = null; - $isDeprecated = false; - $isStrictProperties = false; - $isNotSerializable = false; $allowsDynamicProperties = false; - if ($comments) { - $tags = DocCommentTag::parseDocComments($comments); - foreach ($tags as $tag) { - if ($tag->name === 'alias') { - $alias = $tag->getValue(); - } else if ($tag->name === 'deprecated') { - $isDeprecated = true; - } else if ($tag->name === 'strict-properties') { - $isStrictProperties = true; - } else if ($tag->name === 'not-serializable') { - $isNotSerializable = true; - } else if ($tag->name === 'undocumentable') { - $isUndocumentable = true; - } + $tags = DocCommentTag::parseDocComments($comments); + $tagMap = DocCommentTag::makeTagMap($tags); + + $isDeprecated = array_key_exists('deprecated', $tagMap); + $isStrictProperties = array_key_exists('strict-properties', $tagMap); + $isNotSerializable = array_key_exists('not-serializable', $tagMap); + $isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap); + foreach ($tags as $tag) { + if ($tag->name === 'alias') { + $alias = $tag->getValue(); } } From c89d7a7426a2e8d90ed064880f3ba743ab56b9b9 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 8 May 2025 12:11:40 -0700 Subject: [PATCH 17/62] gen_stub: add and use `FileInfo::getLegacyVersion()` Separate out the creation of a legacy version of a FileInfo object, which has information for old versions of PHP discarded, from its subsequent use in `processStubFile()`. In the process, make `FileInfo::$legacyArginfoGeneration` private, and inline the single use of `FileInfo::getAllClassInfos()`, removing that method. --- build/gen_stub.php | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index b9a8941d7c99d..9754e6bf673f2 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -133,19 +133,7 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly = } if ($fileInfo->shouldGenerateLegacyArginfo()) { - $legacyFileInfo = clone $fileInfo; - $legacyFileInfo->legacyArginfoGeneration = true; - $phpVersionIdMinimumCompatibility = $legacyFileInfo->getMinimumPhpVersionIdCompatibility(); - - foreach ($legacyFileInfo->getAllFuncInfos() as $funcInfo) { - $funcInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); - } - foreach ($legacyFileInfo->getAllClassInfos() as $classInfo) { - $classInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); - } - foreach ($legacyFileInfo->getAllConstInfos() as $constInfo) { - $constInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); - } + $legacyFileInfo = $fileInfo->getLegacyVersion(); $arginfoCode = generateArgInfoCode( basename($stubFilenameWithoutExtension), @@ -4199,7 +4187,7 @@ class FileInfo { public string $declarationPrefix = ""; public bool $generateClassEntries = false; public bool $isUndocumentable = false; - public bool $legacyArginfoGeneration = false; + private bool $legacyArginfoGeneration = false; private ?int $minimumPhpVersionIdCompatibility = null; /** @param array $fileTags */ @@ -4259,15 +4247,6 @@ public function getAllConstInfos(): array { return $result; } - /** - * @return iterable - */ - public function getAllClassInfos(): iterable { - foreach ($this->classInfos as $classInfo) { - yield $classInfo; - } - } - public function __clone() { foreach ($this->constInfos as $key => $constInfo) { @@ -4299,6 +4278,23 @@ public function shouldGenerateLegacyArginfo(): bool { return $this->minimumPhpVersionIdCompatibility !== null && $this->minimumPhpVersionIdCompatibility < PHP_80_VERSION_ID; } + public function getLegacyVersion(): FileInfo { + $legacyFileInfo = clone $this; + $legacyFileInfo->legacyArginfoGeneration = true; + $phpVersionIdMinimumCompatibility = $legacyFileInfo->getMinimumPhpVersionIdCompatibility(); + + foreach ($legacyFileInfo->getAllFuncInfos() as $funcInfo) { + $funcInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); + } + foreach ($legacyFileInfo->classInfos as $classInfo) { + $classInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); + } + foreach ($legacyFileInfo->getAllConstInfos() as $constInfo) { + $constInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); + } + return $legacyFileInfo; + } + public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { $conds = []; foreach ($stmts as $stmt) { From 05dbf0707aa8f1cdf89f6f678e036a60ae543a60 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 14:35:40 -0700 Subject: [PATCH 18/62] gen_stub: further reduce the number of public properties The following properties are made private: * `ArgInfo::$phpDocType` * `ClassInfo::$flags`, `::$attributes`, `::$extends`, `::$implements` * `FileInfo::$isUndocumentable` The following are made protected: * `VariableLike::$flags` --- build/gen_stub.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 9754e6bf673f2..6ec974c05a22d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -775,7 +775,7 @@ class ArgInfo { public /* readonly */ string $sendBy; public /* readonly */ bool $isVariadic; public ?Type $type; - public /* readonly */ ?Type $phpDocType; + private /* readonly */ ?Type $phpDocType; public ?string $defaultValue; /** @var AttributeInfo[] */ public array $attributes; @@ -2410,7 +2410,7 @@ public function getCExpr(): ?string abstract class VariableLike { - public int $flags; + protected int $flags; public ?Type $type; public /* readonly */ ?Type $phpDocType; private /* readonly */ ?string $link; @@ -3373,20 +3373,20 @@ public static function createFromGroups(array $attributeGroups): array { class ClassInfo { public /* readonly */ Name $name; - public int $flags; + private int $flags; public string $type; public /* readonly */ ?string $alias; private /* readonly */ ?SimpleType $enumBackingType; private /* readonly */ bool $isDeprecated; private bool $isStrictProperties; /** @var AttributeInfo[] */ - public array $attributes; + private array $attributes; private ?ExposedDocComment $exposedDocComment; private bool $isNotSerializable; /** @var Name[] */ - public /* readonly */ array $extends; + private /* readonly */ array $extends; /** @var Name[] */ - public /* readonly */ array $implements; + private /* readonly */ array $implements; /** @var ConstInfo[] */ public /* readonly */ array $constInfos; /** @var PropertyInfo[] */ @@ -4186,7 +4186,7 @@ class FileInfo { public bool $generateFunctionEntries = false; public string $declarationPrefix = ""; public bool $generateClassEntries = false; - public bool $isUndocumentable = false; + private bool $isUndocumentable = false; private bool $legacyArginfoGeneration = false; private ?int $minimumPhpVersionIdCompatibility = null; From 2b0cb760d41c1a531449b5df0733e32b1fa4c82b Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 8 May 2025 12:12:12 -0700 Subject: [PATCH 19/62] gen_stub: move `parseStubFile()` into `FileInfo` Reduce the number of global functions by moving it to static method `FileInfo::parseStubFile()`. Additionally, make `FileInfo::handleStatements()` private now that the only caller is part of the class. --- build/gen_stub.php | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 6ec974c05a22d..1d2fa79a0666c 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -95,7 +95,7 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly = if (!$fileInfo = $context->parsedFiles[$stubFile] ?? null) { initPhpParser(); $stubContent = $stubCode ?? file_get_contents($stubFile); - $fileInfo = parseStubFile($stubContent); + $fileInfo = FileInfo::parseStubFile($stubContent); $context->parsedFiles[$stubFile] = $fileInfo; foreach ($fileInfo->dependencies as $dependency) { @@ -4295,7 +4295,27 @@ public function getLegacyVersion(): FileInfo { return $legacyFileInfo; } - public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { + public static function parseStubFile(string $code): FileInfo { + $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative()); + $nodeTraverser = new PhpParser\NodeTraverser; + $nodeTraverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); + $prettyPrinter = new class extends Standard { + protected function pName_FullyQualified(Name\FullyQualified $node): string { + return implode('\\', $node->getParts()); + } + }; + + $stmts = $parser->parse($code); + $nodeTraverser->traverse($stmts); + + $fileTags = DocCommentTag::parseDocComments(getFileDocComments($stmts)); + $fileInfo = new FileInfo($fileTags); + + $fileInfo->handleStatements($stmts, $prettyPrinter); + return $fileInfo; + } + + private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { $conds = []; foreach ($stmts as $stmt) { $cond = self::handlePreprocessorConditions($conds, $stmt); @@ -5031,26 +5051,6 @@ function getFileDocComments(array $stmts): array { ); } -function parseStubFile(string $code): FileInfo { - $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative()); - $nodeTraverser = new PhpParser\NodeTraverser; - $nodeTraverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); - $prettyPrinter = new class extends Standard { - protected function pName_FullyQualified(Name\FullyQualified $node): string { - return implode('\\', $node->getParts()); - } - }; - - $stmts = $parser->parse($code); - $nodeTraverser->traverse($stmts); - - $fileTags = DocCommentTag::parseDocComments(getFileDocComments($stmts)); - $fileInfo = new FileInfo($fileTags); - - $fileInfo->handleStatements($stmts, $prettyPrinter); - return $fileInfo; -} - /** * @template T * @param iterable $infos From 84f82d0a1c755a707811d3d195335810cc5f33b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 9 May 2025 13:33:09 +0200 Subject: [PATCH 20/62] gen_stub: Fix `ce_flags` generation for compatibility mode (#18507) * gen_stub: Fix `ce_flags` generation for compatibility mode Fixes php/php-src#18506 * gen_stub: Improve output for ce_flags compatibility --- build/gen_stub.php | 24 +++++++++++------------- ext/zend_test/test.c | 3 +++ ext/zend_test/test.stub.php | 7 +++++++ ext/zend_test/test_arginfo.h | 21 ++++++++++++++++++++- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 45641f4061b9c..f1d8b43862e62 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -3282,18 +3282,13 @@ public function getRegistration(array $allConstInfos): string $code .= "{\n"; - $flagCodes = generateVersionDependentFlagCode("%s", $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility); - $flags = implode("", $flagCodes); - $classMethods = ($this->funcInfos === []) ? 'NULL' : "class_{$escapedName}_methods"; if ($this->type === "enum") { $name = addslashes((string) $this->name); $backingType = $this->enumBackingType ? $this->enumBackingType->toTypeCode() : "IS_UNDEF"; $code .= "\tzend_class_entry *class_entry = zend_register_internal_enum(\"$name\", $backingType, $classMethods);\n"; - if ($flags !== "") { - $code .= "\tclass_entry->ce_flags |= $flags\n"; - } + $code .= implode("", generateVersionDependentFlagCode("\tclass_entry->ce_flags = %s;\n", $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility)); } else { $code .= "\tzend_class_entry ce, *class_entry;\n\n"; if (count($this->name->getParts()) > 1) { @@ -3310,22 +3305,25 @@ public function getRegistration(array $allConstInfos): string $code .= "#if (PHP_VERSION_ID >= " . PHP_84_VERSION_ID . ")\n"; } - $code .= "\tclass_entry = zend_register_internal_class_with_flags(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ", " . ($flags ?: 0) . ");\n"; + $template = "\tclass_entry = zend_register_internal_class_with_flags(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ", %s);\n"; + $entries = generateVersionDependentFlagCode($template, $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility ? max($this->phpVersionIdMinimumCompatibility, PHP_84_VERSION_ID) : null); + if ($entries !== []) { + $code .= implode("", $entries); + } else { + $code .= sprintf($template, "0"); + } if (!$php84MinimumCompatibility) { $code .= "#else\n"; $code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n"; - if ($flags !== "") { - $code .= "\tclass_entry->ce_flags |= $flags;\n"; - } + $code .= implode("", generateVersionDependentFlagCode("\tclass_entry->ce_flags |= %s;\n", $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility)); $code .= "#endif\n"; } } else { $code .= "\tclass_entry = zend_register_internal_interface(&ce);\n"; - if ($flags !== "") { - $code .= "\tclass_entry->ce_flags |= $flags\n"; - } + $code .= implode("", generateVersionDependentFlagCode("\tclass_entry->ce_flags |= %s;\n", $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility)); + } } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index dbad83fb0e843..d52b8aa0cd172 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -54,6 +54,7 @@ ZEND_DECLARE_MODULE_GLOBALS(zend_test) static zend_class_entry *zend_test_interface; static zend_class_entry *zend_test_class; static zend_class_entry *zend_test_child_class; +static zend_class_entry *zend_test_gen_stub_flag_compatibility_test; static zend_class_entry *zend_attribute_test_class; static zend_class_entry *zend_test_trait; static zend_class_entry *zend_test_attribute; @@ -1271,6 +1272,8 @@ PHP_MINIT_FUNCTION(zend_test) memcpy(&zend_test_class_handlers, &std_object_handlers, sizeof(zend_object_handlers)); zend_test_class_handlers.get_method = zend_test_class_method_get; + zend_test_gen_stub_flag_compatibility_test = register_class_ZendTestGenStubFlagCompatibilityTest(); + zend_attribute_test_class = register_class_ZendAttributeTest(); zend_test_trait = register_class__ZendTestTrait(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index fbfd93da40975..213c1b3f57476 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -85,6 +85,13 @@ class _ZendTestChildClass extends _ZendTestClass public function returnsThrowable(): Exception {} } + /** + * @not-serializable + */ + final class ZendTestGenStubFlagCompatibilityTest { + + } + class ZendAttributeTest { /** @var int */ #[ZendTestRepeatableAttribute] diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index eb83d34fa1f59..9888ba39c14e4 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a7b5de3e4868f9a4aef78da6b98bbce882e129a9 */ + * Stub hash: 20a58913caf419fb60a3bc9cf275db605e2c3b0f */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -774,6 +774,25 @@ static zend_class_entry *register_class__ZendTestChildClass(zend_class_entry *cl return class_entry; } +static zend_class_entry *register_class_ZendTestGenStubFlagCompatibilityTest(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ZendTestGenStubFlagCompatibilityTest", NULL); +#if (PHP_VERSION_ID >= 80400) + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NOT_SERIALIZABLE); +#else + class_entry = zend_register_internal_class_ex(&ce, NULL); +#if (PHP_VERSION_ID >= 80100) + class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NOT_SERIALIZABLE; +#elif (PHP_VERSION_ID >= 80000) + class_entry->ce_flags |= ZEND_ACC_FINAL; +#endif +#endif + + return class_entry; +} + static zend_class_entry *register_class_ZendAttributeTest(void) { zend_class_entry ce, *class_entry; From e11a702c0569a2b5a2282fcba4862731ebfb91a3 Mon Sep 17 00:00:00 2001 From: Jorg Adam Sowa Date: Fri, 9 May 2025 14:08:58 +0200 Subject: [PATCH 21/62] ext/hash: tests for md5 and sha1 compatibility (#18525) Add test cases to check compatibility between the `hash("algo")` and `md5()`/`sha1()` functions. --- ext/hash/tests/md5.phpt | 2 ++ ext/hash/tests/sha1.phpt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ext/hash/tests/md5.phpt b/ext/hash/tests/md5.phpt index b03efadd132e3..2a51146f9777e 100644 --- a/ext/hash/tests/md5.phpt +++ b/ext/hash/tests/md5.phpt @@ -6,9 +6,11 @@ echo hash('md5', '') . "\n"; echo hash('md5', 'a') . "\n"; echo hash('md5', '012345678901234567890123456789012345678901234567890123456789') . "\n"; echo hash('md5', str_repeat('a', 1000000)) . "\n"; +var_dump(hash('md5', 'string') === md5('string')); ?> --EXPECT-- d41d8cd98f00b204e9800998ecf8427e 0cc175b9c0f1b6a831c399e269772661 1ced811af47ead374872fcca9d73dd71 7707d6ae4e027c70eea2a935c2296f21 +bool(true) diff --git a/ext/hash/tests/sha1.phpt b/ext/hash/tests/sha1.phpt index ef7ed6e64ce73..cd22f35ea4015 100644 --- a/ext/hash/tests/sha1.phpt +++ b/ext/hash/tests/sha1.phpt @@ -10,6 +10,7 @@ echo hash('sha1', '012345678901234567890123456789012345678901234567890123456789' echo hash('sha1', 'abc') . "\n"; echo hash('sha1', 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq') . "\n"; echo hash('sha1', str_repeat('a', 1000000)) . "\n"; +var_dump(hash('sha1', 'string') === sha1('string')) . "\n"; ?> --EXPECT-- da39a3ee5e6b4b0d3255bfef95601890afd80709 @@ -18,3 +19,4 @@ f52e3c2732de7bea28f216d877d78dae1aa1ac6a a9993e364706816aba3e25717850c26c9cd0d89d 84983e441c3bd26ebaae4aa1f95129e5e54670f1 34aa973cd4c4daa4f61eeb2bdbad27316534016f +bool(true) From 331ac35f5802145648fefa6312aa54c7306b93d6 Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Sun, 11 May 2025 08:53:56 -0500 Subject: [PATCH 22/62] Fix visibility of whitespace in config output (#18527) When a config var has whitespace (especially trailing whitespace) it is hard to see. This commit wraps the values (if they exist) in double quotes, so the difference is visually observable: Before: ``` $ export PHP_INI_SCAN_DIR="/opt/homebrew/etc/php/8.4/conf.d " $ ./sapi/cli/php --ini Configuration File (php.ini) Path: /usr/local/lib Loaded Configuration File: /opt/homebrew/etc/php/8.4/conf.d Scan for additional .ini files in: (none) Additional .ini files parsed: (none) ``` > Note > The above output has trailing whitespace that is not visible, you can see it if you copy it into an editor: After: ``` $ ./sapi/cli/php --ini Configuration File (php.ini) Path: "/usr/local/lib" Loaded Configuration File: "/opt/homebrew/etc/php/8.4/conf.d " Scan for additional .ini files in: (none) Additional .ini files parsed: (none) ``` Above the whitespace is now visible `/opt/homebrew/etc/php/8.4/conf.d `. Close #18390 --- sapi/cli/php_cli.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 91dd0864c2fbb..8a8f13895340f 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -1106,9 +1106,17 @@ static int do_cli(int argc, char **argv) /* {{{ */ case PHP_CLI_MODE_SHOW_INI_CONFIG: { - zend_printf("Configuration File (php.ini) Path: %s\n", PHP_CONFIG_FILE_PATH); - zend_printf("Loaded Configuration File: %s\n", php_ini_opened_path ? php_ini_opened_path : "(none)"); - zend_printf("Scan for additional .ini files in: %s\n", php_ini_scanned_path ? php_ini_scanned_path : "(none)"); + zend_printf("Configuration File (php.ini) Path: \"%s\"\n", PHP_CONFIG_FILE_PATH); + if (php_ini_scanned_path) { + zend_printf("Loaded Configuration File: \"%s\"\n", php_ini_scanned_path); + } else { + zend_printf("Loaded Configuration File: (none)\n"); + } + if (php_ini_opened_path) { + zend_printf("Scan for additional .ini files in: \"%s\"\n", php_ini_opened_path); + } else { + zend_printf("Scan for additional .ini files in: (none)\n"); + } zend_printf("Additional .ini files parsed: %s\n", php_ini_scanned_files ? php_ini_scanned_files : "(none)"); break; } From 8d2682fc50a8fd72ca5ebcd4b91e1cb5794bb394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 12 May 2025 08:44:46 +0200 Subject: [PATCH 23/62] standard: Take `zend.assertions` into account for dynamic calls to `assert()` (#18521) Fixes php/php-src#18509. --- NEWS | 2 ++ ext/standard/assert.c | 6 +++++- ext/standard/tests/assert/gh18509.phpt | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/assert/gh18509.phpt diff --git a/NEWS b/NEWS index 15b8fa714223b..f5dd8fe320839 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,8 @@ PHP NEWS - Standard: . Fixed bug GH-17403 (Potential deadlock when putenv fails). (nielsdos) + . Fixed bug GH-18509 (Dynamic calls to assert() ignore zend.assertions). + (timwolla) - Windows: . Fix leak+crash with sapi_windows_set_ctrl_handler(). (nielsdos) diff --git a/ext/standard/assert.c b/ext/standard/assert.c index f0b6adadbe17d..258447576e1f4 100644 --- a/ext/standard/assert.c +++ b/ext/standard/assert.c @@ -178,7 +178,11 @@ PHP_FUNCTION(assert) zend_string *description_str = NULL; zend_object *description_obj = NULL; - if (!ASSERTG(active)) { + /* EG(assertions) <= 0 is only reachable by dynamic calls to assert(), + * since calls known at compile time will skip the entire call when + * assertions are disabled. + */ + if (!ASSERTG(active) || EG(assertions) <= 0) { RETURN_TRUE; } diff --git a/ext/standard/tests/assert/gh18509.phpt b/ext/standard/tests/assert/gh18509.phpt new file mode 100644 index 0000000000000..2bf1a04c87c69 --- /dev/null +++ b/ext/standard/tests/assert/gh18509.phpt @@ -0,0 +1,23 @@ +--TEST-- +GH-18509: Dynamic calls to assert() ignore zend.assertions +--INI-- +zend.assertions=0 +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + bool(true) + [1]=> + bool(true) + [2]=> + bool(true) +} From 5e65d8e126ba2674f9e0713cf305f40c5a8a7805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 12 May 2025 10:53:51 +0200 Subject: [PATCH 24/62] standard: Remove `php_std_date()` C API (#18522) This function is unused and trivially replaced by `php_format_date()` (which is already used to format date headers in the CLI server and ext/session). Remove it to slim down the codebase, allowing to remove an entire header (and a source file once the deprecated `strptime()` userland function is removed). --- UPGRADING.INTERNALS | 2 ++ ext/standard/datetime.c | 38 ------------------------------------- ext/standard/datetime.h | 23 ---------------------- ext/standard/php_standard.h | 1 - 4 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 ext/standard/datetime.h diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 7c7f093e50cce..2ed9c2510521c 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -64,6 +64,8 @@ PHP 8.5 INTERNALS UPGRADE NOTES - ext/standard . Added php_url_decode_ex() and php_raw_url_decode_ex() that unlike their non-ex counterparts do not work in-place. + . The php_std_date() function has been removed. Use php_format_date() with + the "D, d M Y H:i:s \\G\\M\\T" format instead. ======================== 4. OpCode changes diff --git a/ext/standard/datetime.c b/ext/standard/datetime.c index 24569f8f6a390..93f47f0f858bc 100644 --- a/ext/standard/datetime.c +++ b/ext/standard/datetime.c @@ -17,50 +17,12 @@ */ #include "php.h" -#include "zend_operators.h" -#include "datetime.h" #include "php_globals.h" #include #ifdef HAVE_SYS_TIME_H # include #endif -#include - -static const char * const mon_short_names[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" -}; - -static const char * const day_short_names[] = { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" -}; - -/* {{{ PHPAPI char *php_std_date(time_t t) - Return date string in standard format for http headers */ -PHPAPI char *php_std_date(time_t t) -{ - struct tm *tm1, tmbuf; - char *str; - - tm1 = php_gmtime_r(&t, &tmbuf); - str = emalloc(81); - str[0] = '\0'; - - if (!tm1) { - return str; - } - - snprintf(str, 80, "%s, %02d %s %04d %02d:%02d:%02d GMT", - day_short_names[tm1->tm_wday], - tm1->tm_mday, - mon_short_names[tm1->tm_mon], - tm1->tm_year + 1900, - tm1->tm_hour, tm1->tm_min, tm1->tm_sec); - - str[79] = 0; - return (str); -} -/* }}} */ #ifdef HAVE_STRPTIME #ifndef HAVE_DECL_STRPTIME diff --git a/ext/standard/datetime.h b/ext/standard/datetime.h deleted file mode 100644 index da87cb8ece493..0000000000000 --- a/ext/standard/datetime.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | Copyright (c) The PHP Group | - +----------------------------------------------------------------------+ - | This source file is subject to version 3.01 of the PHP license, | - | that is bundled with this package in the file LICENSE, and is | - | available through the world-wide-web at the following url: | - | https://www.php.net/license/3_01.txt | - | If you did not receive a copy of the PHP license and are unable to | - | obtain it through the world-wide-web, please send a note to | - | license@php.net so we can mail you a copy immediately. | - +----------------------------------------------------------------------+ - | Authors: Andi Gutmans | - | Zeev Suraski | - +----------------------------------------------------------------------+ -*/ - -#ifndef DATETIME_H -#define DATETIME_H - -PHPAPI char *php_std_date(time_t t); - -#endif /* DATETIME_H */ diff --git a/ext/standard/php_standard.h b/ext/standard/php_standard.h index 5bf41431eeabb..78eba25f11a58 100644 --- a/ext/standard/php_standard.h +++ b/ext/standard/php_standard.h @@ -30,7 +30,6 @@ #include "php_filestat.h" #include "php_browscap.h" #include "pack.h" -#include "datetime.h" #include "url.h" #include "pageinfo.h" #include "fsock.h" From ba4567a987aac3f349f548785854b5a62ffeb6ec Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 12 May 2025 18:45:01 +0200 Subject: [PATCH 25/62] Fix OSS-Fuzz #416302790 (#18537) The parser accepted invalid code: consts are only valid at the top level, but because GH-16952 changed the grammar it was incorrectly allowed at all places that allowed attributed statements. Fix this by introducing a variant of attributed_statement for the top level. --- Zend/tests/constants/oss_fuzz_416302790.phpt | 10 ++++++++++ Zend/zend_language_parser.y | 10 +++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/constants/oss_fuzz_416302790.phpt diff --git a/Zend/tests/constants/oss_fuzz_416302790.phpt b/Zend/tests/constants/oss_fuzz_416302790.phpt new file mode 100644 index 0000000000000..7ace0a72867da --- /dev/null +++ b/Zend/tests/constants/oss_fuzz_416302790.phpt @@ -0,0 +1,10 @@ +--TEST-- +OSS-Fuzz #416302790 +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "const" in %s on line %d diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 0c5bb36501e72..08b2ac6b3f39b 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -279,7 +279,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type isset_variable type return_type type_expr type_without_static %type identifier type_expr_without_static union_type_without_static_element union_type_without_static intersection_type_without_static %type inline_function union_type_element union_type intersection_type -%type attributed_statement attributed_class_statement attributed_parameter +%type attributed_statement attributed_top_statement attributed_class_statement attributed_parameter %type attribute_decl attribute attributes attribute_group namespace_declaration_name %type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list %type enum_declaration_statement enum_backing_type enum_case enum_case_expr @@ -391,13 +391,17 @@ attributed_statement: | trait_declaration_statement { $$ = $1; } | interface_declaration_statement { $$ = $1; } | enum_declaration_statement { $$ = $1; } +; + +attributed_top_statement: + attributed_statement { $$ = $1; } | T_CONST const_list ';' { $$ = $2; } ; top_statement: statement { $$ = $1; } - | attributed_statement { $$ = $1; } - | attributes attributed_statement { $$ = zend_ast_with_attributes($2, $1); } + | attributed_top_statement { $$ = $1; } + | attributes attributed_top_statement { $$ = zend_ast_with_attributes($2, $1); } | T_HALT_COMPILER '(' ')' ';' { $$ = zend_ast_create(ZEND_AST_HALT_COMPILER, zend_ast_create_zval_from_long(zend_get_scanned_file_offset())); From 4122daa49421b0d079857d669b041e0f23f328ff Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Mon, 12 May 2025 19:03:59 +0100 Subject: [PATCH 26/62] ext/date: various array optimisations. (#18382) --- ext/date/php_date.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ext/date/php_date.c b/ext/date/php_date.c index a991c5072f941..6f12a26e465e8 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -1408,7 +1408,7 @@ PHP_FUNCTION(localtime) ts->zone_type = TIMELIB_ZONETYPE_ID; timelib_unixtime2local(ts, (timelib_sll) timestamp); - array_init(return_value); + array_init_size(return_value, 9); if (associative) { add_assoc_long(return_value, "tm_sec", ts->s); @@ -1421,6 +1421,7 @@ PHP_FUNCTION(localtime) add_assoc_long(return_value, "tm_yday", timelib_day_of_year(ts->y, ts->m, ts->d)); add_assoc_long(return_value, "tm_isdst", ts->dst); } else { + zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); add_next_index_long(return_value, ts->s); add_next_index_long(return_value, ts->i); add_next_index_long(return_value, ts->h); @@ -1462,7 +1463,7 @@ PHP_FUNCTION(getdate) ts->zone_type = TIMELIB_ZONETYPE_ID; timelib_unixtime2local(ts, (timelib_sll) timestamp); - array_init(return_value); + array_init_size(return_value, 11); add_assoc_long(return_value, "seconds", ts->s); add_assoc_long(return_value, "minutes", ts->i); @@ -3059,14 +3060,14 @@ static void zval_from_error_container(zval *z, const timelib_error_container *er zval element; add_assoc_long(z, "warning_count", error->warning_count); - array_init(&element); + array_init_size(&element, error->warning_count); for (i = 0; i < error->warning_count; i++) { add_index_string(&element, error->warning_messages[i].position, error->warning_messages[i].message); } add_assoc_zval(z, "warnings", &element); add_assoc_long(z, "error_count", error->error_count); - array_init(&element); + array_init_size(&element, error->error_count); for (i = 0; i < error->error_count; i++) { add_index_string(&element, error->error_messages[i].position, error->error_messages[i].message); } @@ -4275,7 +4276,7 @@ PHP_FUNCTION(timezone_transitions_get) } #define add_nominal() \ - array_init(&element); \ + array_init_size(&element, 5); \ add_assoc_long(&element, "ts", timestamp_begin); \ add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, timestamp_begin, 0)); \ add_assoc_long(&element, "offset", tzobj->tzi.tz->type[0].offset); \ @@ -4284,7 +4285,7 @@ PHP_FUNCTION(timezone_transitions_get) add_next_index_zval(return_value, &element); #define add(i,ts) \ - array_init(&element); \ + array_init_size(&element, 5); \ add_assoc_long(&element, "ts", ts); \ add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \ add_assoc_long(&element, "offset", tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].offset); \ @@ -4293,7 +4294,7 @@ PHP_FUNCTION(timezone_transitions_get) add_next_index_zval(return_value, &element); #define add_by_index(i,ts) \ - array_init(&element); \ + array_init_size(&element, 5); \ add_assoc_long(&element, "ts", ts); \ add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \ add_assoc_long(&element, "offset", tzobj->tzi.tz->type[i].offset); \ @@ -4302,7 +4303,7 @@ PHP_FUNCTION(timezone_transitions_get) add_next_index_zval(return_value, &element); #define add_from_tto(to,ts) \ - array_init(&element); \ + array_init_size(&element, 5); \ add_assoc_long(&element, "ts", ts); \ add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \ add_assoc_long(&element, "offset", (to)->offset); \ @@ -5336,6 +5337,7 @@ PHP_FUNCTION(timezone_identifiers_list) table = timelib_timezone_identifiers_list((timelib_tzdb*) tzdb, &item_count); array_init(return_value); + zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); for (i = 0; i < item_count; ++i) { if (what == PHP_DATE_TIMEZONE_PER_COUNTRY) { From 18276a8b42727fc9601c156fa57936d85aaad9d7 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 23 Apr 2025 16:58:37 +0200 Subject: [PATCH 27/62] Snapshotted poly_func / poly_this may be spilled Polymorphic calls pass this and the function to side traces via snapshotting. However, we assume that this/func are in registers, when in fact they may be spilled. Here I update snapshotting of poly_func/poly_this to support spilling: - In zend_jit_snapshot_handler, keep track of the C stack offset of the spilled register, in a way similar to how stack variables. - In zend_jit_start, do not pre-load the registers if they were spilled. - In zend_jit_trace_exit / zend_jit_trace_deoptimization, load from the stack if the register was spilled. - Store a reference to poly_func/poly_this in zend_jit_ctx so we can use that directly in the side trace. Closes GH-18408 --- NEWS | 1 + ext/opcache/jit/zend_jit_internal.h | 24 ++++--- ext/opcache/jit/zend_jit_ir.c | 99 +++++++++++++++++++++-------- ext/opcache/jit/zend_jit_trace.c | 81 ++++++++++++++--------- 4 files changed, 138 insertions(+), 67 deletions(-) diff --git a/NEWS b/NEWS index 737e352b2d6e6..608880d61fadd 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,7 @@ PHP NEWS memory_consumption or jit_buffer_size). (nielsdos) . Fixed bug GH-18297 (Exception not handled when jit guard is triggered). (Arnaud) + . Fixed bug GH-18408 (Snapshotted poly_func / poly_this may be spilled). - SPL: . Fixed bug GH-18421 (Integer overflow with large numbers in LimitIterator). diff --git a/ext/opcache/jit/zend_jit_internal.h b/ext/opcache/jit/zend_jit_internal.h index 1bf62e1f91452..f320c51c78b2a 100644 --- a/ext/opcache/jit/zend_jit_internal.h +++ b/ext/opcache/jit/zend_jit_internal.h @@ -421,16 +421,22 @@ struct _zend_jit_trace_rec { #define ZEND_JIT_TRACE_START_REC_SIZE 2 +typedef struct _zend_jit_ref_snapshot { + union { + int32_t ref; /* While generating code: The ir_ref to snapshot */ + int32_t offset; /* After compilation / during deopt: C stack offset if 'reg' is spilled */ + }; + int8_t reg; /* Set after compilation by zend_jit_snapshot_handler() */ +} zend_jit_ref_snapshot; + typedef struct _zend_jit_trace_exit_info { - const zend_op *opline; /* opline where VM should continue execution */ - const zend_op_array *op_array; - uint32_t flags; /* set of ZEND_JIT_EXIT_... */ - uint32_t stack_size; - uint32_t stack_offset; - int32_t poly_func_ref; - int32_t poly_this_ref; - int8_t poly_func_reg; - int8_t poly_this_reg; + const zend_op *opline; /* opline where VM should continue execution */ + const zend_op_array *op_array; + uint32_t flags; /* set of ZEND_JIT_EXIT_... */ + uint32_t stack_size; + uint32_t stack_offset; + zend_jit_ref_snapshot poly_func; + zend_jit_ref_snapshot poly_this; } zend_jit_trace_exit_info; typedef struct _zend_jit_trace_stack { diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 064e88c39c14b..1f833ad8f19c5 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -277,6 +277,8 @@ typedef struct _zend_jit_ctx { ir_ref tls; #endif ir_ref fp; + ir_ref poly_func_ref; /* restored from parent trace snapshot */ + ir_ref poly_this_ref; /* restored from parent trace snapshot */ ir_ref trace_loop_ref; ir_ref return_inputs; const zend_op_array *op_array; @@ -624,12 +626,12 @@ static void jit_SNAPSHOT(zend_jit_ctx *jit, ir_ref addr) uint32_t exit_point = 0, n = 0; if (addr < 0) { - if (t->exit_count > 0 - && jit->ctx.ir_base[addr].val.u64 == (uintptr_t)zend_jit_trace_get_exit_addr(t->exit_count - 1)) { - exit_point = t->exit_count - 1; - if (t->exit_info[exit_point].flags & ZEND_JIT_EXIT_METHOD_CALL) { - n = 2; - } + /* addr is not always the address of the *last* exit point, + * so we can not optimize this to 'exit_point = t->exit_count-1' */ + exit_point = zend_jit_exit_point_by_addr(ptr); + ZEND_ASSERT(exit_point != -1); + if (t->exit_info[exit_point].flags & ZEND_JIT_EXIT_METHOD_CALL) { + n = 2; } } @@ -660,8 +662,8 @@ static void jit_SNAPSHOT(zend_jit_ctx *jit, ir_ref addr) ir_SNAPSHOT_SET_OP(snapshot, i + 1, ref); } if (n) { - ir_SNAPSHOT_SET_OP(snapshot, snapshot_size + 1, t->exit_info[exit_point].poly_func_ref); - ir_SNAPSHOT_SET_OP(snapshot, snapshot_size + 2, t->exit_info[exit_point].poly_this_ref); + ir_SNAPSHOT_SET_OP(snapshot, snapshot_size + 1, t->exit_info[exit_point].poly_func.ref); + ir_SNAPSHOT_SET_OP(snapshot, snapshot_size + 2, t->exit_info[exit_point].poly_this.ref); } } } @@ -710,6 +712,31 @@ uint32_t zend_jit_duplicate_exit_point(ir_ctx *ctx, zend_jit_trace_info *t, uint return new_exit_point; } +static void zend_jit_resolve_ref_snapshot(zend_jit_ref_snapshot *dest, ir_ctx *ctx, ir_ref snapshot_ref, ir_insn *snapshot, int op) +{ + int8_t *reg_ops = ctx->regs[snapshot_ref]; + ZEND_ASSERT(reg_ops[op] != ZREG_NONE); + + int8_t reg = reg_ops[op]; + int32_t offset; + + if (IR_REG_SPILLED(reg)) { + reg = ((ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FP : IR_REG_SP) | IR_REG_SPILL_LOAD; + offset = ir_get_spill_slot_offset(ctx, ir_insn_op(snapshot, op)); + } else { + offset = 0; + } + + dest->reg = reg; + dest->offset = offset; +} + +static bool zend_jit_ref_snapshot_equals(const zend_jit_ref_snapshot *a, const zend_jit_ref_snapshot *b) +{ + return a->reg == b->reg + && (!IR_REG_SPILLED(a->reg) || (a->offset == b->offset)); +} + void *zend_jit_snapshot_handler(ir_ctx *ctx, ir_ref snapshot_ref, ir_insn *snapshot, void *addr) { zend_jit_trace_info *t = ((zend_jit_ctx*)ctx)->trace; @@ -722,18 +749,19 @@ void *zend_jit_snapshot_handler(ir_ctx *ctx, ir_ref snapshot_ref, ir_insn *snaps exit_flags = t->exit_info[exit_point].flags; if (exit_flags & ZEND_JIT_EXIT_METHOD_CALL) { - int8_t *reg_ops = ctx->regs[snapshot_ref]; + zend_jit_ref_snapshot func, this; + zend_jit_resolve_ref_snapshot(&func, ctx, snapshot_ref, snapshot, n - 1); + zend_jit_resolve_ref_snapshot(&this, ctx, snapshot_ref, snapshot, n); - ZEND_ASSERT(reg_ops[n - 1] != -1 && reg_ops[n] != -1); if ((exit_flags & ZEND_JIT_EXIT_FIXED) - && (t->exit_info[exit_point].poly_func_reg != reg_ops[n - 1] - || t->exit_info[exit_point].poly_this_reg != reg_ops[n])) { + && (!zend_jit_ref_snapshot_equals(&t->exit_info[exit_point].poly_func, &func) + || !zend_jit_ref_snapshot_equals(&t->exit_info[exit_point].poly_this, &this))) { exit_point = zend_jit_duplicate_exit_point(ctx, t, exit_point, snapshot_ref); addr = (void*)zend_jit_trace_get_exit_addr(exit_point); exit_flags &= ~ZEND_JIT_EXIT_FIXED; } - t->exit_info[exit_point].poly_func_reg = reg_ops[n - 1]; - t->exit_info[exit_point].poly_this_reg = reg_ops[n]; + t->exit_info[exit_point].poly_func = func; + t->exit_info[exit_point].poly_this = this; n -= 2; } @@ -2751,6 +2779,8 @@ static void zend_jit_init_ctx(zend_jit_ctx *jit, uint32_t flags) jit->tls = IR_UNUSED; #endif jit->fp = IR_UNUSED; + jit->poly_func_ref = IR_UNUSED; + jit->poly_this_ref = IR_UNUSED; jit->trace_loop_ref = IR_UNUSED; jit->return_inputs = IR_UNUSED; jit->bb_start_ref = NULL; @@ -4423,6 +4453,18 @@ static ir_ref zend_jit_deopt_rload(zend_jit_ctx *jit, ir_type type, int32_t reg) return ir_RLOAD(type, reg); } +/* Same as zend_jit_deopt_rload(), but 'reg' may be spilled on C stack */ +static ir_ref zend_jit_deopt_rload_spilled(zend_jit_ctx *jit, ir_type type, int8_t reg, int32_t offset) +{ + ZEND_ASSERT(reg >= 0); + + if (IR_REG_SPILLED(reg)) { + return ir_LOAD(type, ir_ADD_OFFSET(zend_jit_deopt_rload(jit, type, IR_REG_NUM(reg)), offset)); + } else { + return zend_jit_deopt_rload(jit, type, reg); + } +} + static int zend_jit_store_const_long(zend_jit_ctx *jit, int var, zend_long val) { zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var)); @@ -8477,10 +8519,9 @@ static int zend_jit_stack_check(zend_jit_ctx *jit, const zend_op *opline, uint32 return 1; } -static int zend_jit_free_trampoline(zend_jit_ctx *jit, int8_t func_reg) +static int zend_jit_free_trampoline(zend_jit_ctx *jit, ir_ref func) { // JIT: if (UNEXPECTED(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) - ir_ref func = ir_RLOAD_A(func_reg); ir_ref if_trampoline = ir_IF(ir_AND_U32( ir_LOAD_U32(ir_ADD_OFFSET(func, offsetof(zend_function, common.fn_flags))), ir_CONST_U32(ZEND_ACC_CALL_VIA_TRAMPOLINE))); @@ -8962,15 +9003,15 @@ static int zend_jit_init_method_call(zend_jit_ctx *jit, zend_class_entry *trace_ce, zend_jit_trace_rec *trace, int checked_stack, - int8_t func_reg, - int8_t this_reg, + ir_ref func_ref, + ir_ref this_ref, bool polymorphic_side_trace) { zend_func_info *info = ZEND_FUNC_INFO(op_array); zend_call_info *call_info = NULL; zend_function *func = NULL; zval *function_name; - ir_ref if_static = IR_UNUSED, cold_path, this_ref = IR_NULL, func_ref = IR_NULL; + ir_ref if_static = IR_UNUSED, cold_path; ZEND_ASSERT(opline->op2_type == IS_CONST); ZEND_ASSERT(op1_info & MAY_BE_OBJECT); @@ -8988,10 +9029,8 @@ static int zend_jit_init_method_call(zend_jit_ctx *jit, } if (polymorphic_side_trace) { - /* function is passed in r0 from parent_trace */ - ZEND_ASSERT(func_reg >= 0 && this_reg >= 0); - func_ref = zend_jit_deopt_rload(jit, IR_ADDR, func_reg); - this_ref = zend_jit_deopt_rload(jit, IR_ADDR, this_reg); + /* function is passed from parent snapshot */ + ZEND_ASSERT(func_ref != IR_UNUSED && this_ref != IR_UNUSED); } else { ir_ref ref, ref2, if_found, fast_path, run_time_cache, this_ref2; @@ -9137,8 +9176,8 @@ static int zend_jit_init_method_call(zend_jit_ctx *jit, return 0; } - jit->trace->exit_info[exit_point].poly_func_ref = func_ref; - jit->trace->exit_info[exit_point].poly_this_ref = this_ref; + jit->trace->exit_info[exit_point].poly_func.ref = func_ref; + jit->trace->exit_info[exit_point].poly_this.ref = this_ref; func = (zend_function*)trace->func; @@ -16991,9 +17030,13 @@ static int zend_jit_trace_start(zend_jit_ctx *jit, } if (parent && parent->exit_info[exit_num].flags & ZEND_JIT_EXIT_METHOD_CALL) { - ZEND_ASSERT(parent->exit_info[exit_num].poly_func_reg >= 0 && parent->exit_info[exit_num].poly_this_reg >= 0); - ir_RLOAD_A(parent->exit_info[exit_num].poly_func_reg); - ir_RLOAD_A(parent->exit_info[exit_num].poly_this_reg); + ZEND_ASSERT(parent->exit_info[exit_num].poly_func.reg >= 0 && parent->exit_info[exit_num].poly_this.reg >= 0); + if (!IR_REG_SPILLED(parent->exit_info[exit_num].poly_func.reg)) { + ir_RLOAD_A(parent->exit_info[exit_num].poly_func.reg); + } + if (!IR_REG_SPILLED(parent->exit_info[exit_num].poly_this.reg)) { + ir_RLOAD_A(parent->exit_info[exit_num].poly_this.reg); + } } ir_STORE(jit_EG(jit_trace_num), ir_CONST_U32(trace_num)); diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index c0ee09e6e6315..2502b22a608f4 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -198,10 +198,8 @@ static uint32_t zend_jit_trace_get_exit_point(const zend_op *to_opline, uint32_t t->exit_info[exit_point].flags = flags; t->exit_info[exit_point].stack_size = stack_size; t->exit_info[exit_point].stack_offset = stack_offset; - t->exit_info[exit_point].poly_func_ref = 0; - t->exit_info[exit_point].poly_this_ref = 0; - t->exit_info[exit_point].poly_func_reg = ZREG_NONE; - t->exit_info[exit_point].poly_this_reg = ZREG_NONE; + t->exit_info[exit_point].poly_func = (zend_jit_ref_snapshot){.reg = ZREG_NONE}; + t->exit_info[exit_point].poly_this = (zend_jit_ref_snapshot){.reg = ZREG_NONE}; } return exit_point; @@ -3484,17 +3482,18 @@ static int zend_jit_trace_exit_needs_deoptimization(uint32_t trace_num, uint32_t } static int zend_jit_trace_deoptimization( - zend_jit_ctx *jit, - uint32_t flags, - const zend_op *opline, - zend_jit_trace_stack *parent_stack, - int parent_vars_count, - zend_ssa *ssa, - zend_jit_trace_stack *stack, - zend_jit_exit_const *constants, - int8_t func_reg, - bool polymorphic_side_trace) + zend_jit_ctx *jit, + const zend_jit_trace_exit_info *exit_info, + zend_jit_trace_stack *parent_stack, + int parent_vars_count, + zend_ssa *ssa, + zend_jit_trace_stack *stack, + zend_jit_exit_const *constants, + bool polymorphic_side_trace) { + uint32_t flags = exit_info->flags; + const zend_op *opline = exit_info->opline; + int i; int check2 = -1; @@ -3638,9 +3637,16 @@ static int zend_jit_trace_deoptimization( zend_jit_check_exception(jit); } - if ((flags & ZEND_JIT_EXIT_METHOD_CALL) && !polymorphic_side_trace) { - if (!zend_jit_free_trampoline(jit, func_reg)) { - return 0; + if (flags & ZEND_JIT_EXIT_METHOD_CALL) { + jit->poly_func_ref = zend_jit_deopt_rload_spilled(jit, IR_ADDR, + exit_info->poly_func.reg, exit_info->poly_func.offset); + jit->poly_this_ref = zend_jit_deopt_rload_spilled(jit, IR_ADDR, + exit_info->poly_this.reg, exit_info->poly_this.offset); + + if (!polymorphic_side_trace) { + if (!zend_jit_free_trampoline(jit, jit->poly_func_ref)) { + return 0; + } } } @@ -4235,11 +4241,9 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par if (parent_trace) { /* Deoptimization */ if (!zend_jit_trace_deoptimization(&ctx, - zend_jit_traces[parent_trace].exit_info[exit_num].flags, - zend_jit_traces[parent_trace].exit_info[exit_num].opline, + &zend_jit_traces[parent_trace].exit_info[exit_num], parent_stack, parent_vars_count, ssa, stack, zend_jit_traces[parent_trace].constants, - zend_jit_traces[parent_trace].exit_info[exit_num].poly_func_reg, polymorphic_side_trace)) { goto jit_failure; } @@ -6323,8 +6327,8 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par op_array, ssa, ssa_op, frame->call_level, op1_info, op1_addr, ce, ce_is_instanceof, on_this, delayed_fetch_this, op1_ce, p + 1, peek_checked_stack - checked_stack, - polymorphic_side_trace ? zend_jit_traces[parent_trace].exit_info[exit_num].poly_func_reg : -1, - polymorphic_side_trace ? zend_jit_traces[parent_trace].exit_info[exit_num].poly_this_reg : -1, + polymorphic_side_trace ? jit->poly_func_ref : -1, + polymorphic_side_trace ? jit->poly_this_ref : -1, polymorphic_side_trace)) { goto jit_failure; } @@ -7348,11 +7352,9 @@ static const void *zend_jit_trace_exit_to_vm(uint32_t trace_num, uint32_t exit_n NULL; if (!zend_jit_trace_deoptimization(&ctx, - zend_jit_traces[trace_num].exit_info[exit_num].flags, - zend_jit_traces[trace_num].exit_info[exit_num].opline, + &zend_jit_traces[trace_num].exit_info[exit_num], stack, stack_size, NULL, NULL, zend_jit_traces[trace_num].constants, - zend_jit_traces[trace_num].exit_info[exit_num].poly_func_reg, 0)) { goto jit_failure; } @@ -7902,6 +7904,17 @@ static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa } } +static void zend_jit_dump_ref_snapshot(zend_jit_ref_snapshot *rs) +{ + if (rs->reg == ZREG_NONE) { + fprintf(stderr, "?"); + } else if (!IR_REG_SPILLED(rs->reg)) { + fprintf(stderr, "%s", zend_reg_name(rs->reg)); + } else { + fprintf(stderr, "0x%x(%s)", rs->offset, zend_reg_name(IR_REG_NUM(rs->reg))); + } +} + static void zend_jit_dump_exit_info(zend_jit_trace_info *t) { int i, j; @@ -7932,9 +7945,11 @@ static void zend_jit_dump_exit_info(zend_jit_trace_info *t) if (t->exit_info[i].flags & (ZEND_JIT_EXIT_POLYMORPHISM|ZEND_JIT_EXIT_METHOD_CALL|ZEND_JIT_EXIT_CLOSURE_CALL)) { fprintf(stderr, "/POLY"); if (t->exit_info[i].flags & ZEND_JIT_EXIT_METHOD_CALL) { - fprintf(stderr, "(%s, %s)", - t->exit_info[i].poly_func_reg != ZREG_NONE ? zend_reg_name(t->exit_info[i].poly_func_reg) : "?", - t->exit_info[i].poly_this_reg != ZREG_NONE ? zend_reg_name(t->exit_info[i].poly_this_reg) : "?"); + fprintf(stderr, "("); + zend_jit_dump_ref_snapshot(&t->exit_info[i].poly_func); + fprintf(stderr, ", "); + zend_jit_dump_ref_snapshot(&t->exit_info[i].poly_this); + fprintf(stderr, ")"); } } if (t->exit_info[i].flags & ZEND_JIT_EXIT_FREE_OP1) { @@ -8668,9 +8683,15 @@ int ZEND_FASTCALL zend_jit_trace_exit(uint32_t exit_num, zend_jit_registers_buf } } if (t->exit_info[exit_num].flags & ZEND_JIT_EXIT_METHOD_CALL) { - ZEND_ASSERT(t->exit_info[exit_num].poly_func_reg >= 0); - zend_function *func = (zend_function*)regs->gpr[t->exit_info[exit_num].poly_func_reg]; + zend_jit_ref_snapshot *func_snapshot = &t->exit_info[exit_num].poly_func; + ZEND_ASSERT(func_snapshot->reg >= 0); + zend_function *func; + if (IR_REG_SPILLED(func_snapshot->reg)) { + func = *(zend_function**)(regs->gpr[IR_REG_NUM(func_snapshot->reg)] + func_snapshot->offset); + } else { + func = (zend_function*)regs->gpr[func_snapshot->reg]; + } if (UNEXPECTED(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { zend_string_release_ex(func->common.function_name, 0); zend_free_trampoline(func); From 89dc8d79a76128f961e95b91bc5bc7a8b33a99cd Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Wed, 14 May 2025 14:36:20 -0500 Subject: [PATCH 28/62] cli: Fix swapped output in `php --ini` (#18557) In #18527, I accidentally swapped the values. This is before my modification: ``` zend_printf("Configuration File (php.ini) Path: %s\n", PHP_CONFIG_FILE_PATH); zend_printf("Loaded Configuration File: %s\n", php_ini_opened_path ? php_ini_opened_path : "(none)"); zend_printf("Scan for additional .ini files in: %s\n", php_ini_scanned_path ? php_ini_scanned_path : "(none)"); ``` - "Loaded Configuration File" should be `php_ini_opened_path` - "Scan for additional .ini files in" shoudl be `php_ini_scanned_path` --- sapi/cli/php_cli.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 8a8f13895340f..e212a0f71a23d 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -1107,13 +1107,13 @@ static int do_cli(int argc, char **argv) /* {{{ */ case PHP_CLI_MODE_SHOW_INI_CONFIG: { zend_printf("Configuration File (php.ini) Path: \"%s\"\n", PHP_CONFIG_FILE_PATH); - if (php_ini_scanned_path) { - zend_printf("Loaded Configuration File: \"%s\"\n", php_ini_scanned_path); + if (php_ini_opened_path) { + zend_printf("Loaded Configuration File: \"%s\"\n", php_ini_opened_path); } else { zend_printf("Loaded Configuration File: (none)\n"); } - if (php_ini_opened_path) { - zend_printf("Scan for additional .ini files in: \"%s\"\n", php_ini_opened_path); + if (php_ini_scanned_path) { + zend_printf("Scan for additional .ini files in: \"%s\"\n", php_ini_scanned_path); } else { zend_printf("Scan for additional .ini files in: (none)\n"); } From 2760a3ef9719dac2e53baf3dc2d8a3dd1227d88b Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Tue, 13 May 2025 16:00:32 +0200 Subject: [PATCH 29/62] Fix GH-18529: ldap no longer respects TLS_CACERT from ldaprc in ldap_start_tls() Regresion introduced in fix for GH-17776 - ensure TLS string options are properly inherited workaround to openldap issue https://bugs.openldap.org/show_bug.cgi?id=10337 - fix ldaps/start_tls tests using LDAPNOINIT in ldaps/tls tests --- ext/ldap/ldap.c | 49 ++++++++++++++++++++++-- ext/ldap/tests/ldap_start_tls_basic.phpt | 2 + ext/ldap/tests/ldaps_basic.phpt | 4 +- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 6c005337346b5..a1b7e7322a5de 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -3721,15 +3721,56 @@ PHP_FUNCTION(ldap_rename_ext) /* }}} */ #ifdef HAVE_LDAP_START_TLS_S +/* + Force new tls context creation with string options inherited from global + Workaround to https://bugs.openldap.org/show_bug.cgi?id=10337 + */ +static int _php_ldap_tls_newctx(LDAP *ld) +{ + int val = 0, i, opts[] = { +#if (LDAP_API_VERSION > 2000) + LDAP_OPT_X_TLS_CACERTDIR, + LDAP_OPT_X_TLS_CACERTFILE, + LDAP_OPT_X_TLS_CERTFILE, + LDAP_OPT_X_TLS_CIPHER_SUITE, + LDAP_OPT_X_TLS_KEYFILE, + LDAP_OPT_X_TLS_RANDOM_FILE, +#endif +#ifdef LDAP_OPT_X_TLS_CRLFILE + LDAP_OPT_X_TLS_CRLFILE, +#endif +#ifdef LDAP_OPT_X_TLS_DHFILE + LDAP_OPT_X_TLS_DHFILE, +#endif +#ifdef LDAP_OPT_X_TLS_ECNAME + LDAP_OPT_X_TLS_ECNAME, +#endif + 0}; + + for (i=0 ; opts[i] ; i++) { + char *path = NULL; + + ldap_get_option(ld, opts[i], &path); + if (path) { /* already set locally */ + ldap_memfree(path); + } else { + ldap_get_option(NULL, opts[i], &path); + if (path) { /* set globally, inherit */ + ldap_set_option(ld, opts[i], path); + ldap_memfree(path); + } + } + } + + return ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &val); +} + /* {{{ Start TLS */ PHP_FUNCTION(ldap_start_tls) { zval *link; ldap_linkdata *ld; int rc, protocol = LDAP_VERSION3; -#ifdef LDAP_OPT_X_TLS_NEWCTX - int val = 0; -#endif if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &link, ldap_link_ce) != SUCCESS) { RETURN_THROWS(); @@ -3740,7 +3781,7 @@ PHP_FUNCTION(ldap_start_tls) if (((rc = ldap_set_option(ld->link, LDAP_OPT_PROTOCOL_VERSION, &protocol)) != LDAP_SUCCESS) || #ifdef LDAP_OPT_X_TLS_NEWCTX - (LDAPG(tls_newctx) && (rc = ldap_set_option(ld->link, LDAP_OPT_X_TLS_NEWCTX, &val)) != LDAP_OPT_SUCCESS) || + (LDAPG(tls_newctx) && (rc = _php_ldap_tls_newctx(ld->link)) != LDAP_OPT_SUCCESS) || #endif ((rc = ldap_start_tls_s(ld->link, NULL, NULL)) != LDAP_SUCCESS) ) { diff --git a/ext/ldap/tests/ldap_start_tls_basic.phpt b/ext/ldap/tests/ldap_start_tls_basic.phpt index b8816de9ac4f5..7278292027f4a 100644 --- a/ext/ldap/tests/ldap_start_tls_basic.phpt +++ b/ext/ldap/tests/ldap_start_tls_basic.phpt @@ -5,6 +5,8 @@ Patrick Allaert # Belgian PHP Testfest 2009 --EXTENSIONS-- ldap +--ENV-- +LDAPNOINIT=1 --SKIPIF-- --FILE-- diff --git a/ext/ldap/tests/ldaps_basic.phpt b/ext/ldap/tests/ldaps_basic.phpt index 7a1a1383436d7..9fa49a6ce7986 100644 --- a/ext/ldap/tests/ldaps_basic.phpt +++ b/ext/ldap/tests/ldaps_basic.phpt @@ -2,8 +2,8 @@ ldap_connect() - Basic ldaps test --EXTENSIONS-- ldap ---XFAIL-- -Passes locally but fails on CI - need investigation (configuration ?) +--ENV-- +LDAPNOINIT=1 --SKIPIF-- --FILE-- From 8da953065219b7dd2695e5759eaa2b7c97a3d38d Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 15 May 2025 09:21:58 +0200 Subject: [PATCH 30/62] NEWS --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index f5dd8fe320839..6532b30c1ecf6 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,10 @@ PHP NEWS - Intl: . Fix various reference issues. (nielsdos) +- LDAP: + . Fixed bug GH-18529 (ldap no longer respects TLS_CACERT from ldaprc in + ldap_start_tls()). (Remi) + - Opcache: . Fixed bug GH-18417 (Windows SHM reattachment fails when increasing memory_consumption or jit_buffer_size). (nielsdos) From 73321e22d27298b702bf16cee1258ac1677c8239 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 15 May 2025 09:22:41 +0200 Subject: [PATCH 31/62] NEWS --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 608880d61fadd..5e26e2bc7ff36 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,10 @@ PHP NEWS - Intl: . Fix various reference issues. (nielsdos) +- LDAP: + . Fixed bug GH-18529 (ldap no longer respects TLS_CACERT from ldaprc in + ldap_start_tls()). (Remi) + - Opcache: . Fixed bug GH-18417 (Windows SHM reattachment fails when increasing memory_consumption or jit_buffer_size). (nielsdos) From 8e5b3129de5c62c60e366e9d87d34eaee7bbb9b1 Mon Sep 17 00:00:00 2001 From: George Wang Date: Thu, 15 May 2025 11:52:49 -0400 Subject: [PATCH 32/62] Address compiler warnings. --- sapi/litespeed/lsapilib.c | 4 ++-- sapi/litespeed/lscriu.c | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sapi/litespeed/lsapilib.c b/sapi/litespeed/lsapilib.c index ca47039a636a9..d1e2c4dda494e 100644 --- a/sapi/litespeed/lsapilib.c +++ b/sapi/litespeed/lsapilib.c @@ -2652,8 +2652,8 @@ int LSAPI_ParseSockAddr( const char * pBind, struct sockaddr * pAddr ) { case '/': pAddr->sa_family = AF_UNIX; - strncpy( ((struct sockaddr_un *)pAddr)->sun_path, p, - sizeof(((struct sockaddr_un *)pAddr)->sun_path) ); + memccpy(((struct sockaddr_un *)pAddr)->sun_path, p, 0, + sizeof(((struct sockaddr_un *)pAddr)->sun_path)); return 0; case '[': diff --git a/sapi/litespeed/lscriu.c b/sapi/litespeed/lscriu.c index 2854fefe596e5..42b09eb38c6eb 100644 --- a/sapi/litespeed/lscriu.c +++ b/sapi/litespeed/lscriu.c @@ -417,7 +417,9 @@ static int LSCRIU_Native_Dump(pid_t iPid, memset(&criu_native_dump, 0, sizeof(criu_native_dump)); criu_native_dump.m_iPidToDump = iPid; strncpy(criu_native_dump.m_chImageDirectory, pchImagePath, - sizeof(criu_native_dump.m_chImageDirectory)); + sizeof(criu_native_dump.m_chImageDirectory) - 1); + criu_native_dump.m_chImageDirectory[ + sizeof(criu_native_dump.m_chImageDirectory) - 1] = '\0'; pchLastSlash = strrchr(criu_native_dump.m_chSocketDir,'/'); if (pchLastSlash) { pchLastSlash++; From 47354a740419c8d6456123aed2ae94230292e503 Mon Sep 17 00:00:00 2001 From: Saki Takamachi <34942839+SakiTakamachi@users.noreply.github.com> Date: Fri, 16 May 2025 15:42:20 +0900 Subject: [PATCH 33/62] Added `zend_simd.h` (#18413) --- Zend/zend_simd.h | 410 ++++++++++++++++++++++++++++++++++ ext/opcache/ZendAccelerator.c | 4 +- ext/standard/string.c | 9 +- ext/standard/url.c | 7 +- 4 files changed, 420 insertions(+), 10 deletions(-) create mode 100644 Zend/zend_simd.h diff --git a/Zend/zend_simd.h b/Zend/zend_simd.h new file mode 100644 index 0000000000000..9bd16ce9e9afb --- /dev/null +++ b/Zend/zend_simd.h @@ -0,0 +1,410 @@ +/******************************************************************************** + * MIT License + * Copyright (c) 2025 Saki Takamachi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + + + #ifndef XSSE_H + #define XSSE_H + + #define XSSE_VERSION 10000 + + #ifdef _MSC_VER + # define XSSE_FORCE_INLINE __forceinline + #elif defined(__GNUC__) || defined(__clang__) + # define XSSE_FORCE_INLINE inline __attribute__((always_inline)) + # define XSSE_HAS_MACRO_EXTENSION + #else + # define XSSE_FORCE_INLINE inline + #endif + + + #if defined(__SSE2__) || defined(_M_X64) || defined(_M_AMD64) + #include + #define XSSE2 + + + #elif defined(__aarch64__) || defined(_M_ARM64) + #include + #define XSSE2 + + typedef int8x16_t __m128i; + + + /***************************************************************************** + * Load / Store * + *****************************************************************************/ + + #define _mm_set_epi8(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) \ + ((int8x16_t) { \ + (int8_t) (x15), (int8_t) (x14), (int8_t) (x13), (int8_t) (x12), \ + (int8_t) (x11), (int8_t) (x10), (int8_t) (x9), (int8_t) (x8), \ + (int8_t) (x7), (int8_t) (x6), (int8_t) (x5), (int8_t) (x4), \ + (int8_t) (x3), (int8_t) (x2), (int8_t) (x1), (int8_t) (x0) }) + #define _mm_set_epi16(x0, x1, x2, x3, x4, x5, x6, x7) \ + (vreinterpretq_s8_s16((int16x8_t) { \ + (int16_t) (x7), (int16_t) (x6), (int16_t) (x5), (int16_t) (x4), \ + (int16_t) (x3), (int16_t) (x2), (int16_t) (x1), (int16_t) (x0) })) + #define _mm_set_epi32(x0, x1, x2, x3) \ + (vreinterpretq_s8_s32((int32x4_t) { (int32_t) (x3), (int32_t) (x2), (int32_t) (x1), (int32_t) (x0) })) + #define _mm_set_epi64x(x0, x1) (vreinterpretq_s8_s64((int64x2_t) { (int64_t) (x1), (int64_t) (x0) })) + #define _mm_set1_epi8(x) (vdupq_n_s8((int8_t) (x))) + #define _mm_set1_epi16(x) (vreinterpretq_s8_s16(vdupq_n_s16((int16_t) (x)))) + #define _mm_set1_epi32(x) (vreinterpretq_s8_s32(vdupq_n_s32((int32_t) (x)))) + #define _mm_set1_epi64x(x) (vreinterpretq_s8_s64(vdupq_n_s64((int64_t) (x)))) + + #define _mm_setr_epi8(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) \ + ((int8x16_t) { \ + (int8_t) (x0), (int8_t) (x1), (int8_t) (x2), (int8_t) (x3), \ + (int8_t) (x4), (int8_t) (x5), (int8_t) (x6), (int8_t) (x7), \ + (int8_t) (x8), (int8_t) (x9), (int8_t) (x10), (int8_t) (x11), \ + (int8_t) (x12), (int8_t) (x13), (int8_t) (x14), (int8_t) (x15) }) + #define _mm_setr_epi16(x0, x1, x2, x3, x4, x5, x6, x7) \ + (vreinterpretq_s8_s16((int16x8_t) { \ + (int16_t) (x0), (int16_t) (x1), (int16_t) (x2), (int16_t) (x3), \ + (int16_t) (x4), (int16_t) (x5), (int16_t) (x6), (int16_t) (x7) })) + #define _mm_setr_epi32(x0, x1, x2, x3) \ + (vreinterpretq_s8_s32((int32x4_t) { (int32_t) (x0), (int32_t) (x1), (int32_t) (x2), (int32_t) (x3) })) + + #define _mm_setzero_si128() (vdupq_n_s8(0)) + + #define _mm_load_si128(x) (vld1q_s8((const int8_t *) (x))) + #define _mm_loadu_si128(x) _mm_load_si128(x) + + #define _mm_store_si128(to, x) (vst1q_s8((int8_t *) (to), x)) + #define _mm_storeu_si128(to, x) _mm_store_si128(to, x) + #define _mm_stream_si128(to, x) _mm_store_si128(to, x) + #define _mm_stream_si32(to, x) (*(volatile int32_t *)(to) = (int32_t)(x)) + + + /***************************************************************************** + * Bit shift / Bit wise * + *****************************************************************************/ + + #define _mm_or_si128(a, b) (vorrq_s8((a), (b))) + #define _mm_xor_si128(a, b) (veorq_s8((a), (b))) + #define _mm_and_si128(a, b) (vandq_s8((a), (b))) + #define _mm_andnot_si128(a, b) (vbicq_s8((b), (a))) + + #define _mm_slli_epi16(x, count) (vreinterpretq_s8_u16(vshlq_n_u16(vreinterpretq_u16_s8(x), (count)))) + #define _mm_slli_epi32(x, count) (vreinterpretq_s8_u32(vshlq_n_u32(vreinterpretq_u32_s8(x), (count)))) + #define _mm_slli_epi64(x, count) (vreinterpretq_s8_u64(vshlq_n_u64(vreinterpretq_u64_s8(x), (count)))) + static XSSE_FORCE_INLINE __m128i _mm_sll_epi16(__m128i x, __m128i count) + { + uint16_t shift = (uint16_t) (vgetq_lane_s64(vreinterpretq_s64_s8(count), 0) & 0xFFFF); + return vreinterpretq_s8_u16( + vshlq_u16(vreinterpretq_u16_s8(x), vdupq_n_s16((int16_t) shift)) + ); + } + static XSSE_FORCE_INLINE __m128i _mm_sll_epi32(__m128i x, __m128i count) + { + uint32_t shift = (uint32_t) (vgetq_lane_s64(vreinterpretq_s64_s8(count), 0) & 0xFFFFFFFF); + return vreinterpretq_s8_u32( + vshlq_u32(vreinterpretq_u32_s8(x), vdupq_n_s32((int32_t) shift)) + ); + } + static XSSE_FORCE_INLINE __m128i _mm_sll_epi64(__m128i x, __m128i count) + { + uint64_t shift = (uint64_t) vgetq_lane_s64(vreinterpretq_s64_s8(count), 0); + return vreinterpretq_s8_u64( + vshlq_u64(vreinterpretq_u64_s8(x), vdupq_n_s64((int64_t) shift)) + ); + } + + #define _mm_slli_si128(x, imm) \ + ((imm) >= 16 ? vdupq_n_s8(0) : vreinterpretq_s8_u8(vextq_u8(vdupq_n_u8(0), vreinterpretq_u8_s8(x), 16 - (imm)))) + + #define _mm_srai_epi16(x, count) (vreinterpretq_s8_s16(vshrq_n_s16(vreinterpretq_s16_s8(x), (count)))) + #define _mm_srai_epi32(x, count) (vreinterpretq_s8_s32(vshrq_n_s32(vreinterpretq_s32_s8(x), (count)))) + static inline __m128i _mm_sra_epi16(__m128i x, __m128i count) + { + uint16_t shift = (uint16_t) (vgetq_lane_s64(vreinterpretq_s64_s8(count), 0) & 0xFFFF); + return vreinterpretq_s8_s16( + vshlq_s16(vreinterpretq_s16_s8(x), vdupq_n_s16(-(int16_t) shift)) + ); + } + static inline __m128i _mm_sra_epi32(__m128i x, __m128i count) + { + uint32_t shift = (uint32_t) (vgetq_lane_s64(vreinterpretq_s64_s8(count), 0) & 0xFFFFFFFF); + return vreinterpretq_s8_s32( + vshlq_s32(vreinterpretq_s32_s8(x), vdupq_n_s32(-(int32_t) shift)) + ); + } + + #define _mm_srli_epi16(x, count) (vreinterpretq_s8_u16(vshrq_n_u16(vreinterpretq_u16_s8(x), (count)))) + #define _mm_srli_epi32(x, count) (vreinterpretq_s8_u32(vshrq_n_u32(vreinterpretq_u32_s8(x), (count)))) + #define _mm_srli_epi64(x, count) (vreinterpretq_s8_u64(vshrq_n_u64(vreinterpretq_u64_s8(x), (count)))) + static XSSE_FORCE_INLINE __m128i _mm_srl_epi16(__m128i x, __m128i count) + { + uint16_t shift = (uint16_t) (vgetq_lane_s64(vreinterpretq_s64_s8(count), 0) & 0xFFFF); + return vreinterpretq_s8_u16( + vshlq_u16(vreinterpretq_u16_s8(x), vdupq_n_s16(-(int16_t) shift)) + ); + } + static XSSE_FORCE_INLINE __m128i _mm_srl_epi32(__m128i x, __m128i count) + { + uint32_t shift = (uint32_t) (vgetq_lane_s64(vreinterpretq_s64_s8(count), 0) & 0xFFFFFFFF); + return vreinterpretq_s8_u32( + vshlq_u32(vreinterpretq_u32_s8(x), vdupq_n_s32(-(int32_t) shift)) + ); + } + static XSSE_FORCE_INLINE __m128i _mm_srl_epi64(__m128i x, __m128i count) + { + uint64_t shift = (uint64_t) vgetq_lane_s64(vreinterpretq_s64_s8(count), 0); + return vreinterpretq_s8_u64( + vshlq_u64(vreinterpretq_u64_s8(x), vdupq_n_s64(-(int64_t) shift)) + ); + } + + #define _mm_srli_si128(x, imm) \ + ((imm) >= 16 ? vdupq_n_s8(0) : vreinterpretq_s8_u8(vextq_u8(vreinterpretq_u8_s8(x), vdupq_n_u8(0), (imm)))) + + + /***************************************************************************** + * Integer Arithmetic Operations * + *****************************************************************************/ + + /** + * In practice, there is no problem, but a runtime error for signed integer overflow is triggered by UBSAN, + * so perform the calculation as unsigned. Since it is optimized at compile time, there are no unnecessary casts at runtime. + */ + #define _mm_add_epi8(a, b) (vreinterpretq_s8_u8(vaddq_u8(vreinterpretq_u8_s8(a), vreinterpretq_u8_s8(b)))) + #define _mm_add_epi16(a, b) (vreinterpretq_s8_u16(vaddq_u16(vreinterpretq_u16_s8(a), vreinterpretq_u16_s8(b)))) + #define _mm_add_epi32(a, b) (vreinterpretq_s8_u32(vaddq_u32(vreinterpretq_u32_s8(a), vreinterpretq_u32_s8(b)))) + #define _mm_add_epi64(a, b) (vreinterpretq_s8_u64(vaddq_u64(vreinterpretq_u64_s8(a), vreinterpretq_u64_s8(b)))) + + #define _mm_adds_epi8(a, b) (vqaddq_s8((a), (b))) + #define _mm_adds_epi16(a, b) (vreinterpretq_s8_s16(vqaddq_s16(vreinterpretq_s16_s8(a), vreinterpretq_s16_s8(b)))) + #define _mm_adds_epu8(a, b) (vreinterpretq_s8_u8(vqaddq_u8(vreinterpretq_u8_s8(a), vreinterpretq_u8_s8(b)))) + #define _mm_adds_epu16(a, b) (vreinterpretq_s8_u16(vqaddq_u16(vreinterpretq_u16_s8(a), vreinterpretq_u16_s8(b)))) + + #define _mm_avg_epu8(a, b) (vreinterpretq_s8_u8(vrhaddq_u8(vreinterpretq_u8_s8(a), vreinterpretq_u8_s8(b)))) + #define _mm_avg_epu16(a, b) (vreinterpretq_s8_u16(vrhaddq_u16(vreinterpretq_u16_s8(a), vreinterpretq_u16_s8(b)))) + + static XSSE_FORCE_INLINE __m128i _mm_madd_epi16(__m128i a, __m128i b) + { + int32x4_t mul_lo = vmull_s16(vget_low_s16(vreinterpretq_s16_s8(a)), vget_low_s16(vreinterpretq_s16_s8(b))); + int32x4_t mul_hi = vmull_s16(vget_high_s16(vreinterpretq_s16_s8(a)), vget_high_s16(vreinterpretq_s16_s8(b))); + + return vreinterpretq_s8_s32(vcombine_s32( + vpadd_s32(vget_low_s32(mul_lo), vget_high_s32(mul_lo)), + vpadd_s32(vget_low_s32(mul_hi), vget_high_s32(mul_hi)) + )); + } + + #define _mm_max_epu8(a, b) (vreinterpretq_s8_u8(vmaxq_u8(vreinterpretq_u8_s8(a), vreinterpretq_u8_s8(b)))) + #define _mm_max_epi16(a, b) (vreinterpretq_s8_s16(vmaxq_s16(vreinterpretq_s16_s8(a), vreinterpretq_s16_s8(b)))) + #define _mm_min_epu8(a, b) (vreinterpretq_s8_u8(vminq_u8(vreinterpretq_u8_s8(a), vreinterpretq_u8_s8(b)))) + #define _mm_min_epi16(a, b) (vreinterpretq_s8_s16(vminq_s16(vreinterpretq_s16_s8(a), vreinterpretq_s16_s8(b)))) + + static XSSE_FORCE_INLINE __m128i _mm_mulhi_epi16(__m128i a, __m128i b) + { + int32x4_t lo = vmull_s16(vget_low_s16(vreinterpretq_s16_s8(a)), vget_low_s16(vreinterpretq_s16_s8(b))); + int32x4_t hi = vmull_s16(vget_high_s16(vreinterpretq_s16_s8(a)), vget_high_s16(vreinterpretq_s16_s8(b))); + return vreinterpretq_s8_s16(vcombine_s16(vshrn_n_s32(lo, 16), vshrn_n_s32(hi, 16))); + } + static XSSE_FORCE_INLINE __m128i _mm_mulhi_epu16(__m128i a, __m128i b) + { + uint32x4_t lo = vmull_u16(vget_low_u16(vreinterpretq_u16_s8(a)), vget_low_u16(vreinterpretq_u16_s8(b))); + uint32x4_t hi = vmull_u16(vget_high_u16(vreinterpretq_u16_s8(a)), vget_high_u16(vreinterpretq_u16_s8(b))); + return vreinterpretq_s8_u16(vcombine_u16(vshrn_n_u32(lo, 16), vshrn_n_u32(hi, 16))); + } + static XSSE_FORCE_INLINE __m128i _mm_mullo_epi16(__m128i a, __m128i b) + { + int32x4_t lo = vmull_s16(vget_low_s16(vreinterpretq_s16_s8(a)), vget_low_s16(vreinterpretq_s16_s8(b))); + int32x4_t hi = vmull_s16(vget_high_s16(vreinterpretq_s16_s8(a)), vget_high_s16(vreinterpretq_s16_s8(b))); + return vreinterpretq_s8_s16(vcombine_s16(vmovn_s32(lo), vmovn_s32(hi))); + } + static XSSE_FORCE_INLINE __m128i _mm_mul_epu32(__m128i a, __m128i b) + { + uint32x4_t evens = vuzpq_u32(vreinterpretq_u32_s8(a), vreinterpretq_u32_s8(b)).val[0]; + return vreinterpretq_s8_u64(vmull_u32(vget_low_u32(evens), vget_high_u32(evens))); + } + static XSSE_FORCE_INLINE __m128i _mm_sad_epu8(__m128i a, __m128i b) + { + uint16x8_t abs_diffs_16 = vpaddlq_u8(vabdq_u8(vreinterpretq_u8_s8(a), vreinterpretq_u8_s8(b))); + uint32x4_t abs_diffs_32 = vpaddlq_u16(abs_diffs_16); + uint64x2_t abs_diffs_64 = vpaddlq_u32(abs_diffs_32); + + return vreinterpretq_s8_u16((uint16x8_t) { + (int16_t) vgetq_lane_u64(abs_diffs_64, 0), 0, 0, 0, + (int16_t) vgetq_lane_u64(abs_diffs_64, 1), 0, 0, 0 + }); + } + + #define _mm_sub_epi8(a, b) (vreinterpretq_s8_u8(vsubq_u8(vreinterpretq_u8_s8(a), vreinterpretq_u8_s8(b)))) + #define _mm_sub_epi16(a, b) (vreinterpretq_s8_u16(vsubq_u16(vreinterpretq_u16_s8(a), vreinterpretq_u16_s8(b)))) + #define _mm_sub_epi32(a, b) (vreinterpretq_s8_u32(vsubq_u32(vreinterpretq_u32_s8(a), vreinterpretq_u32_s8(b)))) + #define _mm_sub_epi64(a, b) (vreinterpretq_s8_u64(vsubq_u64(vreinterpretq_u64_s8(a), vreinterpretq_u64_s8(b)))) + + #define _mm_subs_epi8(a, b) (vqsubq_s8((a), (b))) + #define _mm_subs_epi16(a, b) (vreinterpretq_s8_s16(vqsubq_s16(vreinterpretq_s16_s8(a), vreinterpretq_s16_s8(b)))) + #define _mm_subs_epu8(a, b) (vreinterpretq_s8_u8(vqsubq_u8(vreinterpretq_u8_s8(a), vreinterpretq_u8_s8(b)))) + #define _mm_subs_epu16(a, b) (vreinterpretq_s8_u16(vqsubq_u16(vreinterpretq_u16_s8(a), vreinterpretq_u16_s8(b)))) + + + /***************************************************************************** + * Comparison * + *****************************************************************************/ + + #define _mm_cmpeq_epi8(a, b) (vreinterpretq_s8_u8(vceqq_s8((a), (b)))) + #define _mm_cmpeq_epi16(a, b) (vreinterpretq_s8_u16(vceqq_s16(vreinterpretq_s16_s8(a), vreinterpretq_s16_s8(b)))) + #define _mm_cmpeq_epi32(a, b) (vreinterpretq_s8_u32(vceqq_s32(vreinterpretq_s32_s8(a), vreinterpretq_s32_s8(b)))) + + #define _mm_cmplt_epi8(a, b) (vreinterpretq_s8_u8(vcltq_s8((a), (b)))) + #define _mm_cmplt_epi16(a, b) (vreinterpretq_s8_u16(vcltq_s16(vreinterpretq_s16_s8(a), vreinterpretq_s16_s8(b)))) + #define _mm_cmplt_epi32(a, b) (vreinterpretq_s8_u32(vcltq_s32(vreinterpretq_s32_s8(a), vreinterpretq_s32_s8(b)))) + + #define _mm_cmpgt_epi8(a, b) (vreinterpretq_s8_u8(vcgtq_s8((a), (b)))) + #define _mm_cmpgt_epi16(a, b) (vreinterpretq_s8_u16(vcgtq_s16(vreinterpretq_s16_s8(a), vreinterpretq_s16_s8(b)))) + #define _mm_cmpgt_epi32(a, b) (vreinterpretq_s8_u32(vcgtq_s32(vreinterpretq_s32_s8(a), vreinterpretq_s32_s8(b)))) + + + /***************************************************************************** + * Convert * + *****************************************************************************/ + + #define _mm_cvtsi32_si128(x) (vreinterpretq_s8_s32((int32x4_t) { (int32_t) (x), 0, 0, 0 })) + #define _mm_cvtsi64_si128(x) (vreinterpretq_s8_s64((int64x2_t) { (int64_t) (x), 0 })) + #define _mm_cvtsi128_si32(x) (vgetq_lane_s32(vreinterpretq_s32_s8(x), 0)) + #define _mm_cvtsi128_si64(x) (vgetq_lane_s64(vreinterpretq_s64_s8(x), 0)) + + + /***************************************************************************** + * Others * + *****************************************************************************/ + + #define _mm_packs_epi16(a, b) (vcombine_s8(vqmovn_s16(vreinterpretq_s16_s8(a)), vqmovn_s16(vreinterpretq_s16_s8(b)))) + #define _mm_packs_epi32(a, b) \ + (vreinterpretq_s8_s16(vcombine_s16(vqmovn_s32(vreinterpretq_s32_s8(a)), vqmovn_s32(vreinterpretq_s32_s8(b))))) + #define _mm_packus_epi16(a, b) \ + (vreinterpretq_s8_u8(vcombine_u8(vqmovun_s16(vreinterpretq_s16_s8(a)), vqmovun_s16(vreinterpretq_s16_s8(b))))) + + #define _mm_extract_epi16(x, imm) (vgetq_lane_s16(vreinterpretq_s16_s8(x), (imm))) + #define _mm_insert_epi16(x, val, imm) (vreinterpretq_s8_s16(vsetq_lane_s16((int16_t) (val), vreinterpretq_s16_s8(x), (imm)))) + + static XSSE_FORCE_INLINE int _mm_movemask_epi8(__m128i x) + { + /** + * based on code from + * https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon + */ + uint16x8_t high_bits = vreinterpretq_u16_u8(vshrq_n_u8(vreinterpretq_u8_s8(x), 7)); + uint32x4_t paired16 = vreinterpretq_u32_u16(vsraq_n_u16(high_bits, high_bits, 7)); + uint64x2_t paired32 = vreinterpretq_u64_u32(vsraq_n_u32(paired16, paired16, 14)); + uint8x16_t paired64 = vreinterpretq_u8_u64(vsraq_n_u64(paired32, paired32, 28)); + return vgetq_lane_u8(paired64, 0) | ((int) vgetq_lane_u8(paired64, 8) << 8); + } + + #define _MM_SHUFFLE(a, b, c, d) (((a) << 6) | ((b) << 4) | ((c) << 2) | (d)) + #ifdef XSSE_HAS_MACRO_EXTENSION + #define _mm_shuffle_epi32(x, imm) __extension__({ \ + int32x4_t __xsse_tmp = vreinterpretq_s32_s8(x); \ + vreinterpretq_s8_s32((int32x4_t) { \ + (int32_t) vgetq_lane_s32(__xsse_tmp, ((imm) >> 0) & 0x3), \ + (int32_t) vgetq_lane_s32(__xsse_tmp, ((imm) >> 2) & 0x3), \ + (int32_t) vgetq_lane_s32(__xsse_tmp, ((imm) >> 4) & 0x3), \ + (int32_t) vgetq_lane_s32(__xsse_tmp, ((imm) >> 6) & 0x3) \ + }); \ + }) + #define _mm_shufflehi_epi16(x, imm) __extension__({ \ + int16x8_t __xsse_tmp = vreinterpretq_s16_s8(x); \ + vreinterpretq_s8_s16(vcombine_s16( \ + vget_low_s16(__xsse_tmp), \ + (int16x4_t) { \ + (int16_t) vgetq_lane_s16(__xsse_tmp, (((imm) >> 0) & 0x3) + 4), \ + (int16_t) vgetq_lane_s16(__xsse_tmp, (((imm) >> 2) & 0x3) + 4), \ + (int16_t) vgetq_lane_s16(__xsse_tmp, (((imm) >> 4) & 0x3) + 4), \ + (int16_t) vgetq_lane_s16(__xsse_tmp, (((imm) >> 6) & 0x3) + 4) \ + } \ + )); \ + }) + #define _mm_shufflelo_epi16(x, imm) __extension__({ \ + int16x8_t __xsse_tmp = vreinterpretq_s16_s8(x); \ + vreinterpretq_s8_s16(vcombine_s16( \ + (int16x4_t) { \ + (int16_t) vgetq_lane_s16(__xsse_tmp, (((imm) >> 0) & 0x3)), \ + (int16_t) vgetq_lane_s16(__xsse_tmp, (((imm) >> 2) & 0x3)), \ + (int16_t) vgetq_lane_s16(__xsse_tmp, (((imm) >> 4) & 0x3)), \ + (int16_t) vgetq_lane_s16(__xsse_tmp, (((imm) >> 6) & 0x3)) \ + }, \ + vget_high_s16(__xsse_tmp) \ + )); \ + }) + #else + static XSSE_FORCE_INLINE __m128i _mm_shuffle_epi32(__m128i x, int imm) + { + int32x4_t vec = vreinterpretq_s32_s8(x); + int32_t arr[4]; + vst1q_s32(arr, vec); + + return vreinterpretq_s8_s32((int32x4_t) { + arr[(imm >> 0) & 0x3], + arr[(imm >> 2) & 0x3], + arr[(imm >> 4) & 0x3], + arr[(imm >> 6) & 0x3] + }); + } + static XSSE_FORCE_INLINE __m128i _mm_shufflehi_epi16(__m128i x, int imm) + { + int16x8_t vec = vreinterpretq_s16_s8(x); + int16_t arr[8]; + vst1q_s16(arr, vec); + + return vreinterpretq_s8_s16((int16x8_t) { + arr[0], arr[1], arr[2], arr[3], + arr[((imm >> 0) & 0x3) + 4], + arr[((imm >> 2) & 0x3) + 4], + arr[((imm >> 4) & 0x3) + 4], + arr[((imm >> 6) & 0x3) + 4] + }); + } + static XSSE_FORCE_INLINE __m128i _mm_shufflelo_epi16(__m128i x, int imm) + { + int16x8_t vec = vreinterpretq_s16_s8(x); + int16_t arr[8]; + vst1q_s16(arr, vec); + + return vreinterpretq_s8_s16((int16x8_t) { + arr[((imm >> 0) & 0x3)], + arr[((imm >> 2) & 0x3)], + arr[((imm >> 4) & 0x3)], + arr[((imm >> 6) & 0x3)], + arr[4], arr[5], arr[6], arr[7] + }); + } + #endif + + #define _mm_unpackhi_epi8(a, b) (vzip2q_s8((a), (b))) + #define _mm_unpackhi_epi16(a, b) (vreinterpretq_s8_s16(vzip2q_s16(vreinterpretq_s16_s8(a), vreinterpretq_s16_s8(b)))) + #define _mm_unpackhi_epi32(a, b) (vreinterpretq_s8_s32(vzip2q_s32(vreinterpretq_s32_s8(a), vreinterpretq_s32_s8(b)))) + #define _mm_unpackhi_epi64(a, b) (vreinterpretq_s8_s64(vzip2q_s64(vreinterpretq_s64_s8(a), vreinterpretq_s64_s8(b)))) + + #define _mm_unpacklo_epi8(a, b) (vzip1q_s8((a), (b))) + #define _mm_unpacklo_epi16(a, b) (vreinterpretq_s8_s16(vzip1q_s16(vreinterpretq_s16_s8(a), vreinterpretq_s16_s8(b)))) + #define _mm_unpacklo_epi32(a, b) (vreinterpretq_s8_s32(vzip1q_s32(vreinterpretq_s32_s8(a), vreinterpretq_s32_s8(b)))) + #define _mm_unpacklo_epi64(a, b) (vreinterpretq_s8_s64(vzip1q_s64(vreinterpretq_s64_s8(a), vreinterpretq_s64_s8(b)))) + + #define _mm_move_epi64(x) (vreinterpretq_s8_s64((int64x2_t) { vgetq_lane_s64(vreinterpretq_s64_s8(x), 0), 0 })) + + #endif + + #endif /* XSSE_H */ diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 704846c4a860f..eb75bc0b74736 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -98,6 +98,8 @@ typedef int gid_t; #include #endif +#include "zend_simd.h" + ZEND_EXTENSION(); #ifndef ZTS @@ -171,7 +173,7 @@ static void bzero_aligned(void *mem, size_t size) _mm256_store_si256((__m256i*)(p+32), ymm0); p += 64; } -#elif defined(__SSE2__) +#elif defined(XSSE2) char *p = (char*)mem; char *end = p + size; __m128i xmm0 = _mm_setzero_si128(); diff --git a/ext/standard/string.c b/ext/standard/string.c index 1e20791eb61ce..f21c9be8a7bd2 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -46,10 +46,11 @@ #include "ext/random/php_random.h" #ifdef __SSE2__ -#include #include "Zend/zend_bitset.h" #endif +#include "zend_simd.h" + /* this is read-only, so it's ok */ ZEND_SET_ALIGNED(16, static const char hexconvtab[]) = "0123456789abcdef"; @@ -2817,7 +2818,7 @@ static zend_string *php_strtr_ex(zend_string *str, const char *str_from, const c char *input = ZSTR_VAL(str); size_t len = ZSTR_LEN(str); -#ifdef __SSE2__ +#ifdef XSSE2 if (ZSTR_LEN(str) >= sizeof(__m128i)) { __m128i search = _mm_set1_epi8(ch_from); __m128i delta = _mm_set1_epi8(ch_to - ch_from); @@ -3037,7 +3038,7 @@ static zend_always_inline zend_long count_chars(const char *p, zend_long length, zend_long count = 0; const char *endp; -#ifdef __SSE2__ +#ifdef XSSE2 if (length >= sizeof(__m128i)) { __m128i search = _mm_set1_epi8(ch); @@ -5835,7 +5836,7 @@ static zend_string *php_str_rot13(zend_string *str) e = p + ZSTR_LEN(str); target = ZSTR_VAL(ret); -#ifdef __SSE2__ +#ifdef XSSE2 if (e - p > 15) { const __m128i a_minus_1 = _mm_set1_epi8('a' - 1); const __m128i m_plus_1 = _mm_set1_epi8('m' + 1); diff --git a/ext/standard/url.c b/ext/standard/url.c index da2ddea067314..3c79fd2250021 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -19,14 +19,11 @@ #include #include -#ifdef __SSE2__ -#include -#endif - #include "php.h" #include "url.h" #include "file.h" +#include "zend_simd.h" /* {{{ free_url */ PHPAPI void php_url_free(php_url *theurl) @@ -460,7 +457,7 @@ static zend_always_inline zend_string *php_url_encode_impl(const char *s, size_t start = zend_string_safe_alloc(3, len, 0, 0); to = (unsigned char*)ZSTR_VAL(start); -#ifdef __SSE2__ +#ifdef XSSE2 while (from + 16 < end) { __m128i mask; uint32_t bits; From dbc7c5f34a4716938f5278c7e9401112ac4ff802 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 16 May 2025 20:27:56 +0200 Subject: [PATCH 34/62] Backport lexbor/lexbor@814e0bce970eb95d553ae1a4ba88b26ba8102d12 (#18574) Co-authored-by: Alexander Borisov --- ext/dom/lexbor/lexbor/dom/interfaces/element.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/dom/lexbor/lexbor/dom/interfaces/element.c b/ext/dom/lexbor/lexbor/dom/interfaces/element.c index 157beb9cf7be6..9ed65f522a7e4 100644 --- a/ext/dom/lexbor/lexbor/dom/interfaces/element.c +++ b/ext/dom/lexbor/lexbor/dom/interfaces/element.c @@ -444,7 +444,9 @@ lxb_dom_element_attr_by_local_name_data(lxb_dom_element_t *element, lxb_dom_attr_t *attr = element->first_attr; while (attr != NULL) { - if (attr->node.local_name == data->attr_id) { + if (attr->node.local_name == data->attr_id + || attr->qualified_name == data->attr_id) + { return attr; } From 4dcbd24badf5505224bf110107dd9fdc9b2baf9e Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 16 May 2025 20:54:56 +0100 Subject: [PATCH 35/62] GH-18572: infinite stack recursion in fallback object comparison. With nested objects and recursive comparisons, it is for now unavoidable to have a stack overflow we do some early damage control attempt early on with zend.max_allowed_stack_size check but ultimately more a band-aid than a definitive solution. close GH-18577 --- NEWS | 2 ++ Zend/tests/gh18572.phpt | 39 +++++++++++++++++++++++++++++++++++++ Zend/zend_object_handlers.c | 14 +++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 Zend/tests/gh18572.phpt diff --git a/NEWS b/NEWS index 6532b30c1ecf6..25651e5ca7709 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed GH-18480 (array_splice with large values for offset/length arguments). (nielsdos/David Carlier) + . Partially fixed GH-18572 (nested object comparisons leading to stack overflow). + (David Carlier) - Curl: . Fixed GH-18460 (curl_easy_setopt with CURLOPT_USERPWD/CURLOPT_USERNAME/ diff --git a/Zend/tests/gh18572.phpt b/Zend/tests/gh18572.phpt new file mode 100644 index 0000000000000..46361abe96641 --- /dev/null +++ b/Zend/tests/gh18572.phpt @@ -0,0 +1,39 @@ +--TEST-- +GH-18572: Nested object comparison leading to stack overflow +--SKIPIF-- + +--FILE-- +previous = $first; +$first->next = $first; + +$cur = $first; + +for ($i = 0; $i < 50000; $i++) { + $new = new Node(); + $new->previous = $cur; + $cur->next = $new; + $new->next = $first; + $first->previous = $new; + $cur = $new; +} + +try { + // Force comparison manually to trigger zend_hash_compare + $first == $cur; +} catch(Error $e) { + echo $e->getMessage(). PHP_EOL; +} +?> +--EXPECTREGEX-- +(Maximum call stack size reached during object comparison|Fatal error: Nesting level too deep - recursive dependency?.+) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index d688e4b63ed69..180364b248d71 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -42,6 +42,15 @@ #define IN_UNSET ZEND_GUARD_PROPERTY_UNSET #define IN_ISSET ZEND_GUARD_PROPERTY_ISSET +static zend_always_inline bool zend_objects_check_stack_limit(void) +{ +#ifdef ZEND_CHECK_STACK_LIMIT + return zend_call_stack_overflowed(EG(stack_limit)); +#else + return false; +#endif +} + /* __X accessors explanation: @@ -1714,6 +1723,11 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ { zend_object *zobj1, *zobj2; + if (zend_objects_check_stack_limit()) { + zend_throw_error(NULL, "Maximum call stack size reached during object comparison"); + return ZEND_UNCOMPARABLE; + } + if (Z_TYPE_P(o1) != Z_TYPE_P(o2)) { /* Object and non-object */ zval *object; From 88d6e7c238289658f7a614cad6dcfce96d8ffbfc Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 17 May 2025 12:44:57 +0100 Subject: [PATCH 36/62] fix regex typo for GH-18577 new test --- Zend/tests/gh18572.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/tests/gh18572.phpt b/Zend/tests/gh18572.phpt index 46361abe96641..5b877d74572d2 100644 --- a/Zend/tests/gh18572.phpt +++ b/Zend/tests/gh18572.phpt @@ -36,4 +36,4 @@ try { } ?> --EXPECTREGEX-- -(Maximum call stack size reached during object comparison|Fatal error: Nesting level too deep - recursive dependency?.+) +(Maximum call stack size reached during object comparison|Fatal error: Nesting level too deep - recursive dependency\?.+) From 68abc19229272219c3755541abb90a8b02149305 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 17 May 2025 11:41:15 +0100 Subject: [PATCH 37/62] Follow-up on GH-18577, adjust new test due to change on nested objects. --- Zend/tests/gh18572.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/tests/gh18572.phpt b/Zend/tests/gh18572.phpt index 5b877d74572d2..ff178ebef24f8 100644 --- a/Zend/tests/gh18572.phpt +++ b/Zend/tests/gh18572.phpt @@ -36,4 +36,4 @@ try { } ?> --EXPECTREGEX-- -(Maximum call stack size reached during object comparison|Fatal error: Nesting level too deep - recursive dependency\?.+) +(Maximum call stack size reached during object comparison|Nesting level too deep - recursive dependency\?) From 49a60ec08466db096a8b4ba3234d6587761fae71 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 17 May 2025 22:20:56 -0400 Subject: [PATCH 38/62] Correct typo in UPGRADING [skip ci] (#18584) `--enable-sanitzer` -> `--enable-sanitizer` --- UPGRADING | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING b/UPGRADING index 6000c237f85d0..c032c621edda7 100644 --- a/UPGRADING +++ b/UPGRADING @@ -463,7 +463,7 @@ PHP 8.5 UPGRADE NOTES worked for in-tree builds); some extension builds (especially when using Makefile.frag.w32) may need adjustments. -* --enable-sanitzer is now supported for MSVC builds. This enables ASan and +* --enable-sanitizer is now supported for MSVC builds. This enables ASan and debug assertions, and is supported as of MSVC 16.10 and Windows 10. * The --with-uncritical-warn-choke configuration option for clang builds is From 06738fc051a5f1c5eaee6b6de3e6fa3474480dba Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 18 May 2025 15:01:24 +0200 Subject: [PATCH 39/62] Remove ZEND_ACC_USE_GUARDS from hooks (#18587) This is no longer necessary since the hooks amendments removed guards for recursion. --- Zend/zend_inheritance.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index e718eb5684e65..3c638f288a806 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2983,7 +2983,6 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent hooks[j] = new_fn; } } - ce->ce_flags |= ZEND_ACC_USE_GUARDS; } } ZEND_HASH_FOREACH_END(); } From 3f754524549fabaddb17e8848c7d773dc311cdbf Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Thu, 8 May 2025 01:08:39 +0300 Subject: [PATCH 40/62] Intl: Add IntlListFormatter class Allows to format a list of item with - TYPE_AND/TYPE_OR/TYPE_UNITS operands. - WIDTH_WIDE, WIDTH_SHORT, WIDTH_NARROW. close GH-18519 --- NEWS | 2 + UPGRADING | 9 + ext/intl/config.m4 | 2 + ext/intl/config.w32 | 3 + ext/intl/listformatter/listformatter.stub.php | 40 +++ .../listformatter/listformatter_arginfo.h | 85 +++++++ ext/intl/listformatter/listformatter_class.c | 236 ++++++++++++++++++ ext/intl/listformatter/listformatter_class.h | 52 ++++ ext/intl/php_intl.c | 5 + .../listformatter/listformatter_basic.phpt | 60 +++++ .../listformatter/listformatter_clone.phpt | 21 ++ .../listformatter/listformatter_error.phpt | 38 +++ .../listformatter_with_parameters.phpt | 127 ++++++++++ .../listformatter_with_parameters_error.phpt | 26 ++ 14 files changed, 706 insertions(+) create mode 100644 ext/intl/listformatter/listformatter.stub.php create mode 100644 ext/intl/listformatter/listformatter_arginfo.h create mode 100644 ext/intl/listformatter/listformatter_class.c create mode 100644 ext/intl/listformatter/listformatter_class.h create mode 100644 ext/intl/tests/listformatter/listformatter_basic.phpt create mode 100644 ext/intl/tests/listformatter/listformatter_clone.phpt create mode 100644 ext/intl/tests/listformatter/listformatter_error.phpt create mode 100644 ext/intl/tests/listformatter/listformatter_with_parameters.phpt create mode 100644 ext/intl/tests/listformatter/listformatter_with_parameters_error.phpt diff --git a/NEWS b/NEWS index 706533b9c898a..09afc803601cd 100644 --- a/NEWS +++ b/NEWS @@ -96,6 +96,8 @@ PHP NEWS . Added grapheme_levenshtein() function. (Yuya Hamada) . Added Locale::addLikelySubtags/Locale::minimizeSubtags to handle adding/removing likely subtags to a locale. (David Carlier) + . Added IntlListFormatter class to format a list of items with a locale + , operands types and units. (BogdanUngureanu) - MySQLi: . Fixed bugs GH-17900 and GH-8084 (calling mysqli::__construct twice). diff --git a/UPGRADING b/UPGRADING index c032c621edda7..aec94c3b26eda 100644 --- a/UPGRADING +++ b/UPGRADING @@ -180,6 +180,9 @@ PHP 8.5 UPGRADE NOTES number formats. . Added Locale::addLikelySubtags and Locale::minimizeSubtags to handle likely tags on a given locale. + . Added IntlListFormatter class to format, order, punctuates + a list of items with a given locale, AND/OR and UNIT operands. + It is supported from icu 67. - XSL: . The $namespace argument of XSLTProcessor::getParameter(), @@ -415,6 +418,12 @@ PHP 8.5 UPGRADE NOTES - Intl: . DECIMAL_COMPACT_SHORT. . DECIMAL_COMPACT_LONG. + . TYPE_AND. + . TYPE_OR. + . TYPE_UNITS. + . WIDTH_WIDE. + . WIDTH_SHORT. + . WIDTH_NARROW. - POSIX: . POSIX_SC_OPEN_MAX. diff --git a/ext/intl/config.m4 b/ext/intl/config.m4 index 6a64f0f718100..20adc3a4ce3a7 100644 --- a/ext/intl/config.m4 +++ b/ext/intl/config.m4 @@ -39,6 +39,7 @@ if test "$PHP_INTL" != "no"; then locale/locale_class.c locale/locale_methods.c locale/locale.c + listformatter/listformatter_class.c msgformat/msgformat_attr.c msgformat/msgformat_class.c msgformat/msgformat_data.c @@ -119,6 +120,7 @@ if test "$PHP_INTL" != "no"; then $ext_builddir/grapheme $ext_builddir/idn $ext_builddir/locale + $ext_builddir/listformatter $ext_builddir/msgformat $ext_builddir/normalizer $ext_builddir/resourcebundle diff --git a/ext/intl/config.w32 b/ext/intl/config.w32 index 17b577327bbb9..b8161865d2540 100644 --- a/ext/intl/config.w32 +++ b/ext/intl/config.w32 @@ -39,6 +39,9 @@ if (PHP_INTL != "no") { formatter_main.c \ formatter_parse.c \ ", "intl"); + ADD_SOURCES(configure_module_dirname + "/listformatter", "\ + listformatter_class.c \ + ", "intl"); ADD_SOURCES(configure_module_dirname + "/locale", "\ locale.c \ locale_class.c \ diff --git a/ext/intl/listformatter/listformatter.stub.php b/ext/intl/listformatter/listformatter.stub.php new file mode 100644 index 0000000000000..b16ad5c270091 --- /dev/null +++ b/ext/intl/listformatter/listformatter.stub.php @@ -0,0 +1,40 @@ += 67 + /** @cvalue ULISTFMT_TYPE_OR */ + public const int TYPE_OR = UNKNOWN; + + /** @cvalue ULISTFMT_TYPE_UNITS */ + public const int TYPE_UNITS = UNKNOWN; +#endif + + /** @cvalue ULISTFMT_WIDTH_WIDE */ + public const int WIDTH_WIDE = UNKNOWN; + +#if U_ICU_VERSION_MAJOR_NUM >= 67 + /** @cvalue ULISTFMT_WIDTH_SHORT */ + public const int WIDTH_SHORT = UNKNOWN; + + /** @cvalue ULISTFMT_WIDTH_NARROW */ + public const int WIDTH_NARROW = UNKNOWN; +#endif + + public function __construct(string $locale, int $type = IntlListFormatter::TYPE_AND, int $width = IntlListFormatter::WIDTH_WIDE) {} + + public function format(array $strings): string|false {} + + public function getErrorCode(): int {} + + public function getErrorMessage(): string {} +} diff --git a/ext/intl/listformatter/listformatter_arginfo.h b/ext/intl/listformatter/listformatter_arginfo.h new file mode 100644 index 0000000000000..3e18c1154ae76 --- /dev/null +++ b/ext/intl/listformatter/listformatter_arginfo.h @@ -0,0 +1,85 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: f64f4171cfe4f66f976b9350b0a0e22269301ce5 */ + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_IntlListFormatter___construct, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, locale, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, type, IS_LONG, 0, "IntlListFormatter::TYPE_AND") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, width, IS_LONG, 0, "IntlListFormatter::WIDTH_WIDE") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_IntlListFormatter_format, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, strings, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_IntlListFormatter_getErrorCode, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_IntlListFormatter_getErrorMessage, 0, 0, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_METHOD(IntlListFormatter, __construct); +ZEND_METHOD(IntlListFormatter, format); +ZEND_METHOD(IntlListFormatter, getErrorCode); +ZEND_METHOD(IntlListFormatter, getErrorMessage); + +static const zend_function_entry class_IntlListFormatter_methods[] = { + ZEND_ME(IntlListFormatter, __construct, arginfo_class_IntlListFormatter___construct, ZEND_ACC_PUBLIC) + ZEND_ME(IntlListFormatter, format, arginfo_class_IntlListFormatter_format, ZEND_ACC_PUBLIC) + ZEND_ME(IntlListFormatter, getErrorCode, arginfo_class_IntlListFormatter_getErrorCode, ZEND_ACC_PUBLIC) + ZEND_ME(IntlListFormatter, getErrorMessage, arginfo_class_IntlListFormatter_getErrorMessage, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + +static zend_class_entry *register_class_IntlListFormatter(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "IntlListFormatter", class_IntlListFormatter_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); + + zval const_TYPE_AND_value; + ZVAL_LONG(&const_TYPE_AND_value, ULISTFMT_TYPE_AND); + zend_string *const_TYPE_AND_name = zend_string_init_interned("TYPE_AND", sizeof("TYPE_AND") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_TYPE_AND_name, &const_TYPE_AND_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_TYPE_AND_name); +#if U_ICU_VERSION_MAJOR_NUM >= 67 + + zval const_TYPE_OR_value; + ZVAL_LONG(&const_TYPE_OR_value, ULISTFMT_TYPE_OR); + zend_string *const_TYPE_OR_name = zend_string_init_interned("TYPE_OR", sizeof("TYPE_OR") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_TYPE_OR_name, &const_TYPE_OR_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_TYPE_OR_name); +#endif +#if U_ICU_VERSION_MAJOR_NUM >= 67 + + zval const_TYPE_UNITS_value; + ZVAL_LONG(&const_TYPE_UNITS_value, ULISTFMT_TYPE_UNITS); + zend_string *const_TYPE_UNITS_name = zend_string_init_interned("TYPE_UNITS", sizeof("TYPE_UNITS") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_TYPE_UNITS_name, &const_TYPE_UNITS_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_TYPE_UNITS_name); +#endif + + zval const_WIDTH_WIDE_value; + ZVAL_LONG(&const_WIDTH_WIDE_value, ULISTFMT_WIDTH_WIDE); + zend_string *const_WIDTH_WIDE_name = zend_string_init_interned("WIDTH_WIDE", sizeof("WIDTH_WIDE") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_WIDTH_WIDE_name, &const_WIDTH_WIDE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_WIDTH_WIDE_name); +#if U_ICU_VERSION_MAJOR_NUM >= 67 + + zval const_WIDTH_SHORT_value; + ZVAL_LONG(&const_WIDTH_SHORT_value, ULISTFMT_WIDTH_SHORT); + zend_string *const_WIDTH_SHORT_name = zend_string_init_interned("WIDTH_SHORT", sizeof("WIDTH_SHORT") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_WIDTH_SHORT_name, &const_WIDTH_SHORT_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_WIDTH_SHORT_name); +#endif +#if U_ICU_VERSION_MAJOR_NUM >= 67 + + zval const_WIDTH_NARROW_value; + ZVAL_LONG(&const_WIDTH_NARROW_value, ULISTFMT_WIDTH_NARROW); + zend_string *const_WIDTH_NARROW_name = zend_string_init_interned("WIDTH_NARROW", sizeof("WIDTH_NARROW") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_WIDTH_NARROW_name, &const_WIDTH_NARROW_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_WIDTH_NARROW_name); +#endif + + return class_entry; +} diff --git a/ext/intl/listformatter/listformatter_class.c b/ext/intl/listformatter/listformatter_class.c new file mode 100644 index 0000000000000..522ecdd371357 --- /dev/null +++ b/ext/intl/listformatter/listformatter_class.c @@ -0,0 +1,236 @@ +/* + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Bogdan Ungureanu | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "php_intl.h" +#include +#include "listformatter_arginfo.h" +#include "listformatter_class.h" +#include "intl_convert.h" + +static zend_object_handlers listformatter_handlers; + +static void listformatter_free_obj(zend_object *object) +{ + ListFormatter_object *obj = php_intl_listformatter_fetch_object(object); + + if( obj->lf_data.ulistfmt ) + ulistfmt_close( obj->lf_data.ulistfmt ); + + obj->lf_data.ulistfmt = NULL; + intl_error_reset( &obj->lf_data.error ); + + zend_object_std_dtor(&obj->zo); +} + +static zend_object *listformatter_create_object(zend_class_entry *class_type) +{ + ListFormatter_object *obj; + obj = zend_object_alloc(sizeof(ListFormatter_object), class_type); + + obj->lf_data.ulistfmt = NULL; + intl_error_reset( &obj->lf_data.error ); + + zend_object_std_init(&obj->zo, class_type); + object_properties_init(&obj->zo, class_type); + obj->zo.handlers = &listformatter_handlers; + return &obj->zo; +} + +PHP_METHOD(IntlListFormatter, __construct) +{ + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); + char* locale; + size_t locale_len = 0; + zend_long type = ULISTFMT_TYPE_AND; + zend_long width = ULISTFMT_WIDTH_WIDE; + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STRING(locale, locale_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(type) + Z_PARAM_LONG(width) + ZEND_PARSE_PARAMETERS_END(); + + if(locale_len == 0) { + locale = (char *)intl_locale_get_default(); + } + + if (locale_len > INTL_MAX_LOCALE_LEN) { + zend_argument_value_error(1, "Locale string too long, should be no longer than %d characters", INTL_MAX_LOCALE_LEN); + RETURN_THROWS(); + } + + if (strlen(uloc_getISO3Language(locale)) == 0) { + zend_argument_value_error(1, "\"%s\" is invalid", locale); + RETURN_THROWS(); + } + + UErrorCode status = U_ZERO_ERROR; + #if U_ICU_VERSION_MAJOR_NUM >= 67 + if (type != ULISTFMT_TYPE_AND && type != ULISTFMT_TYPE_OR && type != ULISTFMT_TYPE_UNITS) { + zend_argument_value_error(2, "must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS"); + RETURN_THROWS(); + } + + if (width != ULISTFMT_WIDTH_WIDE && width != ULISTFMT_WIDTH_SHORT && width != ULISTFMT_WIDTH_NARROW) { + zend_argument_value_error(3, "must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW"); + RETURN_THROWS(); + } + + LISTFORMATTER_OBJECT(obj) = ulistfmt_openForType(locale, type, width, &status); + #else + if (type != ULISTFMT_TYPE_AND) { + zend_argument_value_error(2, "contains an unsupported type. ICU 66 and below only support IntlListFormatter::TYPE_AND"); + RETURN_THROWS(); + } + + if (width != ULISTFMT_WIDTH_WIDE) { + zend_argument_value_error(3, "contains an unsupported width. ICU 66 and below only support IntlListFormatter::WIDTH_WIDE"); + RETURN_THROWS(); + } + + LISTFORMATTER_OBJECT(obj) = ulistfmt_open(locale, &status); + #endif + + if (U_FAILURE(status)) { + intl_error_set(NULL, status, "Constructor failed", 0); + zend_throw_exception(IntlException_ce_ptr, "Constructor failed", 0); + RETURN_THROWS(); + } +} + +PHP_METHOD(IntlListFormatter, format) +{ + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); + HashTable *ht; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(ht) + ZEND_PARSE_PARAMETERS_END(); + + uint32_t count = zend_hash_num_elements(ht); + if (count == 0) { + RETURN_EMPTY_STRING(); + } + + const UChar **items = (const UChar **)safe_emalloc(count, sizeof(const UChar *), 0); + int32_t *itemLengths = (int32_t *)safe_emalloc(count, sizeof(int32_t), 0); + uint32_t i = 0; + zval *val; + + ZEND_HASH_FOREACH_VAL(ht, val) { + zend_string *str_val; + + str_val = zval_get_string(val); + + // Convert PHP string to UTF-16 + UChar *ustr = NULL; + int32_t ustr_len = 0; + UErrorCode status = U_ZERO_ERROR; + + intl_convert_utf8_to_utf16(&ustr, &ustr_len, ZSTR_VAL(str_val), ZSTR_LEN(str_val), &status); + zend_string_release(str_val); + + if (U_FAILURE(status)) { + // We can't use goto cleanup because items and itemLengths are incompletely allocated + for (uint32_t j = 0; j < i; j++) { + efree((void *)items[j]); + } + efree(items); + efree(itemLengths); + intl_error_set(NULL, status, "Failed to convert string to UTF-16", 0); + RETURN_FALSE; + } + + items[i] = ustr; + itemLengths[i] = ustr_len; + i++; + } ZEND_HASH_FOREACH_END(); + + UErrorCode status = U_ZERO_ERROR; + int32_t resultLength; + UChar *result = NULL; + + resultLength = ulistfmt_format(LISTFORMATTER_OBJECT(obj), items, itemLengths, count, NULL, 0, &status); + + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) { + intl_error_set(NULL, status, "Failed to format list", 0); + RETVAL_FALSE; + goto cleanup; + } + + // Allocate buffer and try again + status = U_ZERO_ERROR; + result = (UChar *)safe_emalloc(resultLength + 1, sizeof(UChar), 0); + ulistfmt_format(LISTFORMATTER_OBJECT(obj), items, itemLengths, count, result, resultLength, &status); + + if (U_FAILURE(status)) { + if (result) { + efree(result); + } + intl_error_set(NULL, status, "Failed to format list", 0); + RETVAL_FALSE; + goto cleanup; + } + + // Convert result back to UTF-8 + zend_string *ret = intl_convert_utf16_to_utf8(result, resultLength, &status); + efree(result); + + if (!ret) { + intl_error_set(NULL, status, "Failed to convert result to UTF-8", 0); + RETVAL_FALSE; + } else { + RETVAL_NEW_STR(ret); + } + +cleanup: + for (i = 0; i < count; i++) { + efree((void *)items[i]); + } + efree(items); + efree(itemLengths); +} + +PHP_METHOD(IntlListFormatter, getErrorCode) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); + + UErrorCode status = intl_error_get_code(LISTFORMATTER_ERROR_P(obj)); + + RETURN_LONG(status); +} + +PHP_METHOD(IntlListFormatter, getErrorMessage) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS); + + zend_string *message = intl_error_get_message(LISTFORMATTER_ERROR_P(obj)); + RETURN_STR(message); +} + +void listformatter_register_class(void) +{ + zend_class_entry *class_entry = register_class_IntlListFormatter(); + class_entry->create_object = listformatter_create_object; + + memcpy(&listformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + listformatter_handlers.offset = XtOffsetOf(ListFormatter_object, zo); + listformatter_handlers.free_obj = listformatter_free_obj; + listformatter_handlers.clone_obj = NULL; +} diff --git a/ext/intl/listformatter/listformatter_class.h b/ext/intl/listformatter/listformatter_class.h new file mode 100644 index 0000000000000..9dd708ca3dfbc --- /dev/null +++ b/ext/intl/listformatter/listformatter_class.h @@ -0,0 +1,52 @@ +/* + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Bogdan Ungureanu | + +----------------------------------------------------------------------+ +*/ + +#ifndef LISTFORMATTER_CLASS_H +#define LISTFORMATTER_CLASS_H + +#include + +#include "intl_common.h" +#include "intl_error.h" +#include "intl_data.h" + +#include + +typedef struct { + // error handling + intl_error error; + + // formatter handling + UListFormatter* ulistfmt; +} listformatter_data; + +typedef struct { + listformatter_data lf_data; + zend_object zo; +} ListFormatter_object; + +static inline ListFormatter_object *php_intl_listformatter_fetch_object(zend_object *obj) { + return (ListFormatter_object *)((char*)(obj) - XtOffsetOf(ListFormatter_object, zo)); +} +#define Z_INTL_LISTFORMATTER_P(zv) php_intl_listformatter_fetch_object(Z_OBJ_P(zv)) + +#define LISTFORMATTER_ERROR(lfo) (lfo)->lf_data.error +#define LISTFORMATTER_ERROR_P(lfo) &(LISTFORMATTER_ERROR(lfo)) + +#define LISTFORMATTER_OBJECT(lfo) (lfo)->lf_data.ulistfmt + +void listformatter_register_class( void ); +extern zend_class_entry *ListFormatter_ce_ptr; + +#endif // LISTFORMATTER_CLASS_H diff --git a/ext/intl/php_intl.c b/ext/intl/php_intl.c index cba18f5ae07b1..68fd2dedfba85 100644 --- a/ext/intl/php_intl.c +++ b/ext/intl/php_intl.c @@ -41,6 +41,8 @@ #include "locale/locale.h" #include "locale/locale_class.h" +#include "listformatter/listformatter_class.h" + #include "dateformat/dateformat.h" #include "dateformat/dateformat_class.h" #include "dateformat/dateformat_data.h" @@ -156,6 +158,9 @@ PHP_MINIT_FUNCTION( intl ) /* Register 'NumberFormatter' PHP class */ formatter_register_class( ); + /* Register 'ListFormatter' PHP class */ + listformatter_register_class( ); + /* Register 'Normalizer' PHP class */ normalizer_register_Normalizer_class( ); diff --git a/ext/intl/tests/listformatter/listformatter_basic.phpt b/ext/intl/tests/listformatter/listformatter_basic.phpt new file mode 100644 index 0000000000000..ef6996e12aaa2 --- /dev/null +++ b/ext/intl/tests/listformatter/listformatter_basic.phpt @@ -0,0 +1,60 @@ +--TEST-- +IntlListFormatter: Basic functionality +--EXTENSIONS-- +intl +--FILE-- +format([1,2,3]) . PHP_EOL; +$formatter = new IntlListFormatter('EN_US'); +echo $formatter->format([1,2,3]) . PHP_EOL; + +echo $formatter->format([1.2,2.3,3.4]) . PHP_EOL; + +$item = 'test'; +$item2 = 'test2'; +$item3 = &$item; +$items = [$item, $item2, $item3]; +$items2 = &$items; + +echo $formatter->format($items) . PHP_EOL; +echo $formatter->format($items2) . PHP_EOL; + +echo $formatter->format([null, true, false]) . PHP_EOL; + +$classItem = new class { + public function __toString() { + return 'foo'; + } +}; + +echo $formatter->format([1, $classItem]) . PHP_EOL; + + +echo 'FR' . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('FR'); +echo $formatter->format([1,2,3]) . PHP_EOL; + +// Make it clear that numbers are not converted automatically to the locale. Use NumberFormatter for each value. +echo $formatter->format([1.2,2.3,3.4]) . PHP_EOL; +?> +--EXPECT-- +EN_US +1, 2, and 3 +1, 2, and 3 +1.2, 2.3, and 3.4 +test, test2, and test +test, test2, and test +, 1, and +1 and foo +FR +1, 2 et 3 +1, 2 et 3 +1.2, 2.3 et 3.4 diff --git a/ext/intl/tests/listformatter/listformatter_clone.phpt b/ext/intl/tests/listformatter/listformatter_clone.phpt new file mode 100644 index 0000000000000..ce7ce99aef45f --- /dev/null +++ b/ext/intl/tests/listformatter/listformatter_clone.phpt @@ -0,0 +1,21 @@ +--TEST-- +Test IntlListFormatter cannot be cloned +--SKIPIF-- + +--FILE-- +getMessage(); +} +?> +--EXPECT-- +Trying to clone an uncloneable object of class IntlListFormatter diff --git a/ext/intl/tests/listformatter/listformatter_error.phpt b/ext/intl/tests/listformatter/listformatter_error.phpt new file mode 100644 index 0000000000000..d420b92a9f8d8 --- /dev/null +++ b/ext/intl/tests/listformatter/listformatter_error.phpt @@ -0,0 +1,38 @@ +--TEST-- +IntlListFormatter: error messages +--EXTENSIONS-- +intl +--FILE-- +getMessage() . PHP_EOL; +} + +try { + $formatter = new IntlListFormatter('ro_thisiswaytooooooooooooooooooooooooooooooooooooooooooooolongtobevaliditneedstobeatleast157characterstofailthevalidationinthelistformattercodeimplementation', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE); +} catch(ValueError $exception) { + echo $exception->getMessage() . PHP_EOL; +} + +$formatter = new IntlListFormatter('ro', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE); + +try { + echo $formatter->format([new stdClass()]); +} catch(Error $error) { + echo $error->getMessage() . PHP_EOL; +} + +try { + echo $formatter->format([1, 2, new stdClass(), 4]); +} catch(Error $error) { + echo $error->getMessage() . PHP_EOL; +} +?> +--EXPECT-- +IntlListFormatter::__construct(): Argument #1 ($locale) "f" is invalid +IntlListFormatter::__construct(): Argument #1 ($locale) Locale string too long, should be no longer than 156 characters +Object of class stdClass could not be converted to string +Object of class stdClass could not be converted to string diff --git a/ext/intl/tests/listformatter/listformatter_with_parameters.phpt b/ext/intl/tests/listformatter/listformatter_with_parameters.phpt new file mode 100644 index 0000000000000..0e3d0614f2d2e --- /dev/null +++ b/ext/intl/tests/listformatter/listformatter_with_parameters.phpt @@ -0,0 +1,127 @@ +--TEST-- +IntlListFormatter: Test AND, OR and Width parameters +--EXTENSIONS-- +intl +--SKIPIF-- + +--FILE-- +format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('EN_US', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]) . PHP_EOL; + +echo 'GB' . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('en_GB', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]) . PHP_EOL; + +echo 'FR' . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT); +echo $formatter->format([1,2,3]) . PHP_EOL; + +$formatter = new IntlListFormatter('FR', IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW); +echo $formatter->format([1,2,3]); +?> +--EXPECT-- +EN_US +1, 2, and 3 +1, 2, & 3 +1, 2, 3 +1, 2, or 3 +1, 2, or 3 +1, 2, or 3 +1, 2, 3 +1, 2, 3 +1 2 3 +GB +1, 2 and 3 +1, 2 and 3 +1, 2, 3 +1, 2 or 3 +1, 2 or 3 +1, 2 or 3 +1, 2, 3 +1, 2, 3 +1 2 3 +FR +1, 2 et 3 +1, 2 et 3 +1, 2, 3 +1, 2 ou 3 +1, 2 ou 3 +1, 2 ou 3 +1, 2 et 3 +1, 2 et 3 +1 2 3 diff --git a/ext/intl/tests/listformatter/listformatter_with_parameters_error.phpt b/ext/intl/tests/listformatter/listformatter_with_parameters_error.phpt new file mode 100644 index 0000000000000..bfa75e7399bb4 --- /dev/null +++ b/ext/intl/tests/listformatter/listformatter_with_parameters_error.phpt @@ -0,0 +1,26 @@ +--TEST-- +IntlListFormatter: Test invalid parameters for TYPE and WIDTH +--EXTENSIONS-- +intl +--SKIPIF-- + +--FILE-- +getMessage(); +} + +echo PHP_EOL; + +try { + $formatter = new IntlListFormatter('ro', IntlListFormatter::TYPE_AND, 2323232); +} catch(ValueError $exception) { + echo $exception->getMessage(); +} +?> +--EXPECT-- +IntlListFormatter::__construct(): Argument #2 ($type) must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS +IntlListFormatter::__construct(): Argument #3 ($width) must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW From 00f0175ba91cf1d2ab3b223446c1d8e14a436692 Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Sun, 18 May 2025 16:52:30 +0100 Subject: [PATCH 41/62] [skip ci] IntlListFormatter UPGRADING fixes (#18590) --- UPGRADING | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/UPGRADING b/UPGRADING index aec94c3b26eda..d6af5b0ca3a29 100644 --- a/UPGRADING +++ b/UPGRADING @@ -181,7 +181,10 @@ PHP 8.5 UPGRADE NOTES . Added Locale::addLikelySubtags and Locale::minimizeSubtags to handle likely tags on a given locale. . Added IntlListFormatter class to format, order, punctuates - a list of items with a given locale, AND/OR and UNIT operands. + a list of items with a given locale, IntlListFormatter::TYPE_AND, + IntlListFormatter::TYPE_OR, IntlListFormatter::TYPE_UNITS operands and + IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT and + IntlListFormatter::WIDTH_NARROW widths. It is supported from icu 67. - XSL: @@ -418,12 +421,6 @@ PHP 8.5 UPGRADE NOTES - Intl: . DECIMAL_COMPACT_SHORT. . DECIMAL_COMPACT_LONG. - . TYPE_AND. - . TYPE_OR. - . TYPE_UNITS. - . WIDTH_WIDE. - . WIDTH_SHORT. - . WIDTH_NARROW. - POSIX: . POSIX_SC_OPEN_MAX. From 1e6909d25ea4889e5181c0ec8d4c313e0f9eece7 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 15 May 2025 09:59:46 +0200 Subject: [PATCH 42/62] allow ldap_get_option to retrieve global option --- ext/ldap/ldap.c | 28 +++++++++++-------- ext/ldap/ldap.stub.php | 2 +- ext/ldap/ldap_arginfo.h | 4 +-- .../tests/ldap_get_option_package_basic.phpt | 5 ++++ 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index ef05fd77aaeb8..15920db49edb7 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -2984,16 +2984,22 @@ PHP_FUNCTION(ldap_compare) /* {{{ Get the current value of various session-wide parameters */ PHP_FUNCTION(ldap_get_option) { - zval *link, *retval; + zval *link = NULL, *retval; ldap_linkdata *ld; zend_long option; + LDAP *ldap; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "Olz", &link, ldap_link_ce, &option, &retval) != SUCCESS) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O!lz", &link, ldap_link_ce, &option, &retval) != SUCCESS) { RETURN_THROWS(); } - ld = Z_LDAP_LINK_P(link); - VERIFY_LDAP_LINK_CONNECTED(ld); + if (!link) { + ldap = NULL; + } else { + ld = Z_LDAP_LINK_P(link); + VERIFY_LDAP_LINK_CONNECTED(ld); + ldap = ld->link; + } switch (option) { /* options with int value */ @@ -3029,7 +3035,7 @@ PHP_FUNCTION(ldap_get_option) { int val; - if (ldap_get_option(ld->link, option, &val)) { + if (ldap_get_option(ldap, option, &val)) { RETURN_FALSE; } ZEND_TRY_ASSIGN_REF_LONG(retval, val); @@ -3039,7 +3045,7 @@ PHP_FUNCTION(ldap_get_option) { struct timeval *timeout = NULL; - if (ldap_get_option(ld->link, LDAP_OPT_NETWORK_TIMEOUT, (void *) &timeout)) { + if (ldap_get_option(ldap, LDAP_OPT_NETWORK_TIMEOUT, (void *) &timeout)) { if (timeout) { ldap_memfree(timeout); } @@ -3056,7 +3062,7 @@ PHP_FUNCTION(ldap_get_option) { int timeout; - if (ldap_get_option(ld->link, LDAP_X_OPT_CONNECT_TIMEOUT, &timeout)) { + if (ldap_get_option(ldap, LDAP_X_OPT_CONNECT_TIMEOUT, &timeout)) { RETURN_FALSE; } ZEND_TRY_ASSIGN_REF_LONG(retval, (timeout / 1000)); @@ -3067,7 +3073,7 @@ PHP_FUNCTION(ldap_get_option) { struct timeval *timeout = NULL; - if (ldap_get_option(ld->link, LDAP_OPT_TIMEOUT, (void *) &timeout)) { + if (ldap_get_option(ldap, LDAP_OPT_TIMEOUT, (void *) &timeout)) { if (timeout) { ldap_memfree(timeout); } @@ -3117,7 +3123,7 @@ PHP_FUNCTION(ldap_get_option) { char *val = NULL; - if (ldap_get_option(ld->link, option, &val) || val == NULL || *val == '\0') { + if (ldap_get_option(ldap, option, &val) || val == NULL || *val == '\0') { if (val) { ldap_memfree(val); } @@ -3131,13 +3137,13 @@ PHP_FUNCTION(ldap_get_option) { LDAPControl **ctrls = NULL; - if (ldap_get_option(ld->link, option, &ctrls) || ctrls == NULL) { + if (ldap_get_option(ldap, option, &ctrls) || ctrls == NULL) { if (ctrls) { ldap_memfree(ctrls); } RETURN_FALSE; } - _php_ldap_controls_to_array(ld->link, ctrls, retval, 1); + _php_ldap_controls_to_array(ldap, ctrls, retval, 1); } break; /* options not implemented case LDAP_OPT_API_INFO: diff --git a/ext/ldap/ldap.stub.php b/ext/ldap/ldap.stub.php index f3615e4d42cc0..f9f2aab85c40b 100644 --- a/ext/ldap/ldap.stub.php +++ b/ext/ldap/ldap.stub.php @@ -740,7 +740,7 @@ function ldap_rename(LDAP\Connection $ldap, string $dn, string $new_rdn, string function ldap_rename_ext(LDAP\Connection $ldap, string $dn, string $new_rdn, string $new_parent, bool $delete_old_rdn, ?array $controls = null): LDAP\Result|false {} /** @param array|string|int $value */ - function ldap_get_option(LDAP\Connection $ldap, int $option, &$value = null): bool {} + function ldap_get_option(?LDAP\Connection $ldap, int $option, &$value = null): bool {} /** @param array|string|int|bool $value */ function ldap_set_option(?LDAP\Connection $ldap, int $option, $value): bool {} diff --git a/ext/ldap/ldap_arginfo.h b/ext/ldap/ldap_arginfo.h index 01e08540142b4..984a4bab9a69f 100644 --- a/ext/ldap/ldap_arginfo.h +++ b/ext/ldap/ldap_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7415695a7ae90e6abd45617baf8a9ecf9232b801 */ + * Stub hash: edd31d6c19c01bee6ddb04c747640c97f0bacba6 */ #if defined(HAVE_ORALDAP) ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_ldap_connect, 0, 0, LDAP\\Connection, MAY_BE_FALSE) @@ -217,7 +217,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_ldap_rename_ext, 0, 5, LDAP\ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ldap_get_option, 0, 2, _IS_BOOL, 0) - ZEND_ARG_OBJ_INFO(0, ldap, LDAP\\Connection, 0) + ZEND_ARG_OBJ_INFO(0, ldap, LDAP\\Connection, 1) ZEND_ARG_TYPE_INFO(0, option, IS_LONG, 0) ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, value, "null") ZEND_END_ARG_INFO() diff --git a/ext/ldap/tests/ldap_get_option_package_basic.phpt b/ext/ldap/tests/ldap_get_option_package_basic.phpt index 9135ea233701d..680c32490c153 100644 --- a/ext/ldap/tests/ldap_get_option_package_basic.phpt +++ b/ext/ldap/tests/ldap_get_option_package_basic.phpt @@ -7,6 +7,10 @@ ldap --FILE-- --EXPECT-- bool(true) +bool(true) From e726d917e79c965af09fedb43be683fe77f02b5d Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 15 May 2025 10:00:52 +0200 Subject: [PATCH 43/62] add more ldaps/tls tests with TLS_CACERTFILE --- ext/ldap/tests/ldap_start_tls_basic2.phpt | 36 +++++++++++++++ ext/ldap/tests/ldaps_basic2.phpt | 56 +++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 ext/ldap/tests/ldap_start_tls_basic2.phpt create mode 100644 ext/ldap/tests/ldaps_basic2.phpt diff --git a/ext/ldap/tests/ldap_start_tls_basic2.phpt b/ext/ldap/tests/ldap_start_tls_basic2.phpt new file mode 100644 index 0000000000000..56d8c980b6ffe --- /dev/null +++ b/ext/ldap/tests/ldap_start_tls_basic2.phpt @@ -0,0 +1,36 @@ +--TEST-- +ldap_start_tls() - Basic ldap_start_tls test with TLS_CACERTFILE +--EXTENSIONS-- +ldap +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/ext/ldap/tests/ldaps_basic2.phpt b/ext/ldap/tests/ldaps_basic2.phpt new file mode 100644 index 0000000000000..e464b09beba1e --- /dev/null +++ b/ext/ldap/tests/ldaps_basic2.phpt @@ -0,0 +1,56 @@ +--TEST-- +ldap_connect() - Basic ldaps test with TLS_CACERTFILE +--EXTENSIONS-- +ldap +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) From 8e2acc0df4fb2198798f9a6f4a3a4a9ff13c0e05 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Mon, 19 May 2025 07:46:40 +0200 Subject: [PATCH 44/62] NEWS + UPGRADING --- NEWS | 4 ++++ UPGRADING | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/NEWS b/NEWS index 09afc803601cd..807d70236c7f4 100644 --- a/NEWS +++ b/NEWS @@ -99,6 +99,10 @@ PHP NEWS . Added IntlListFormatter class to format a list of items with a locale , operands types and units. (BogdanUngureanu) +- LDAP: + . Allow ldap_get_option to retrieve global option by allowing NULL for + connection instance ($ldap). (Remi) + - MySQLi: . Fixed bugs GH-17900 and GH-8084 (calling mysqli::__construct twice). (nielsdos) diff --git a/UPGRADING b/UPGRADING index d6af5b0ca3a29..295d29cdc3fdf 100644 --- a/UPGRADING +++ b/UPGRADING @@ -239,6 +239,10 @@ PHP 8.5 UPGRADE NOTES have dropped the false from the return type union. Returning false was actually never possible. +- LDAP: + . ldap_get_option() now accept a NULL connection, as ldap_set_option(), + to allow retrieval of global options. + - libxml: . libxml_set_external_entity_loader() now has a formal return type of true. From 2d6b86945fe98f7f6a82ff0c194e0c5c1b06b1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 19 May 2025 09:36:30 +0200 Subject: [PATCH 45/62] zend_vm: Add OPcode specialization for `=== []` (#18571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * zend_vm: Add OPcode specialization for `=== []` Checking whether an array is empty with a strict comparison against the empty array is a common pattern in PHP. A GitHub search for `"=== []" language:PHP` reveals 44k hits. From the set of `!$a`, `count($a) === 0`, `empty($a)` and `$a === []` it however is also the slowest option. A test script: op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0), ZEND_IS_IDENTICAL_EMPTY_ARRAY, TMPVARCV, CONST, SPEC(SMART_BRANCH,COMMUTATIVE)) +{ + USE_OPLINE + zval *op1; + bool result; + + op1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R); + result = Z_TYPE_P(op1) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(op1)) == 0; + FREE_OP1(); + FREE_OP2(); + ZEND_VM_SMART_BRANCH(result, 0); +} + +ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_NOT_IDENTICAL, op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0), ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY, TMPVARCV, CONST, SPEC(SMART_BRANCH,COMMUTATIVE)) +{ + USE_OPLINE + zval *op1; + bool result; + + op1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R); + result = Z_TYPE_P(op1) != IS_ARRAY || zend_hash_num_elements(Z_ARR_P(op1)) > 0; + FREE_OP1(); + FREE_OP2(); + ZEND_VM_SMART_BRANCH(result, 0); +} + ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_IDENTICAL, op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF)), ZEND_IS_IDENTICAL_NOTHROW, CV, CONST|CV, SPEC(COMMUTATIVE)) { /* This is declared below the specializations for MAY_BE_LONG/MAY_BE_DOUBLE so those will be used instead if possible. */ diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 226e0446abbd1..a60ad82799896 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -13949,6 +13949,84 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_EQUAL_DOUBL ZEND_VM_SMART_BRANCH_JMPNZ(result, 0); } +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *op1; + bool result; + + op1 = _get_zval_ptr_tmpvarcv(opline->op1_type, opline->op1, BP_VAR_R EXECUTE_DATA_CC); + result = Z_TYPE_P(op1) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(op1)) == 0; + FREE_OP(opline->op1_type, opline->op1.var); + + ZEND_VM_SMART_BRANCH_NONE(result, 0); +} + +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *op1; + bool result; + + op1 = _get_zval_ptr_tmpvarcv(opline->op1_type, opline->op1, BP_VAR_R EXECUTE_DATA_CC); + result = Z_TYPE_P(op1) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(op1)) == 0; + FREE_OP(opline->op1_type, opline->op1.var); + + ZEND_VM_SMART_BRANCH_JMPZ(result, 0); +} + +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *op1; + bool result; + + op1 = _get_zval_ptr_tmpvarcv(opline->op1_type, opline->op1, BP_VAR_R EXECUTE_DATA_CC); + result = Z_TYPE_P(op1) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(op1)) == 0; + FREE_OP(opline->op1_type, opline->op1.var); + + ZEND_VM_SMART_BRANCH_JMPNZ(result, 0); +} + +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *op1; + bool result; + + op1 = _get_zval_ptr_tmpvarcv(opline->op1_type, opline->op1, BP_VAR_R EXECUTE_DATA_CC); + result = Z_TYPE_P(op1) != IS_ARRAY || zend_hash_num_elements(Z_ARR_P(op1)) > 0; + FREE_OP(opline->op1_type, opline->op1.var); + + ZEND_VM_SMART_BRANCH_NONE(result, 0); +} + +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *op1; + bool result; + + op1 = _get_zval_ptr_tmpvarcv(opline->op1_type, opline->op1, BP_VAR_R EXECUTE_DATA_CC); + result = Z_TYPE_P(op1) != IS_ARRAY || zend_hash_num_elements(Z_ARR_P(op1)) > 0; + FREE_OP(opline->op1_type, opline->op1.var); + + ZEND_VM_SMART_BRANCH_JMPZ(result, 0); +} + +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *op1; + bool result; + + op1 = _get_zval_ptr_tmpvarcv(opline->op1_type, opline->op1, BP_VAR_R EXECUTE_DATA_CC); + result = Z_TYPE_P(op1) != IS_ARRAY || zend_hash_num_elements(Z_ARR_P(op1)) > 0; + FREE_OP(opline->op1_type, opline->op1.var); + + ZEND_VM_SMART_BRANCH_JMPNZ(result, 0); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -58145,6 +58223,12 @@ ZEND_API void execute_ex(zend_execute_data *ex) (void*)&&ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_LABEL, (void*)&&ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ_LABEL, (void*)&&ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ_LABEL, + (void*)&&ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_LABEL, + (void*)&&ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ_LABEL, + (void*)&&ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ_LABEL, + (void*)&&ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_LABEL, + (void*)&&ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ_LABEL, + (void*)&&ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ_LABEL, (void*)&&ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_NULL_LABEL, @@ -60306,6 +60390,36 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) HYBRID_BREAK(); + HYBRID_CASE(ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST): + VM_TRACE(ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) + ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) + HYBRID_BREAK(); + HYBRID_CASE(ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ): + VM_TRACE(ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) + ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) + HYBRID_BREAK(); + HYBRID_CASE(ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ): + VM_TRACE(ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) + ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) + HYBRID_BREAK(); + HYBRID_CASE(ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST): + VM_TRACE(ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) + ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) + HYBRID_BREAK(); + HYBRID_CASE(ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ): + VM_TRACE(ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) + ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) + HYBRID_BREAK(); + HYBRID_CASE(ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ): + VM_TRACE(ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) + ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) + HYBRID_BREAK(); HYBRID_CASE(ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST): VM_TRACE(ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -67337,6 +67451,12 @@ void zend_vm_init(void) ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_HANDLER, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ_HANDLER, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ_HANDLER, + ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_HANDLER, + ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ_HANDLER, + ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ_HANDLER, + ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_HANDLER, + ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ_HANDLER, + ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ_HANDLER, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, @@ -67761,7 +67881,7 @@ void zend_vm_init(void) 1255, 1256 | SPEC_RULE_OP1, 1261 | SPEC_RULE_OP1, - 3487, + 3493, 1266 | SPEC_RULE_OP1, 1271 | SPEC_RULE_OP1, 1276 | SPEC_RULE_OP2, @@ -67795,7 +67915,7 @@ void zend_vm_init(void) 1559 | SPEC_RULE_OP1 | SPEC_RULE_OP2, 1584 | SPEC_RULE_OP1, 1589, - 3487, + 3493, 1590 | SPEC_RULE_OP1, 1595 | SPEC_RULE_OP1 | SPEC_RULE_OP2, 1620 | SPEC_RULE_OP1 | SPEC_RULE_OP2, @@ -67927,51 +68047,51 @@ void zend_vm_init(void) 2575, 2576, 2577, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, - 3487, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, + 3493, }; #if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) zend_opcode_handler_funcs = labels; @@ -68219,8 +68339,10 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t break; } spec = 2886 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + } else if (op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { + spec = 3111 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3111 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3117 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_IDENTICAL: @@ -68237,8 +68359,10 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t break; } spec = 3036 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + } else if (op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { + spec = 3114 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3116 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3122 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_EQUAL: @@ -68278,12 +68402,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3121 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3127 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3196 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3202 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_IS_SMALLER_OR_EQUAL: @@ -68291,49 +68415,49 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3271 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3277 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3346 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3352 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_QM_ASSIGN: if (op1_info == MAY_BE_LONG) { - spec = 3433 | SPEC_RULE_OP1; + spec = 3439 | SPEC_RULE_OP1; } else if (op1_info == MAY_BE_DOUBLE) { - spec = 3438 | SPEC_RULE_OP1; + spec = 3444 | SPEC_RULE_OP1; } else if ((op->op1_type == IS_CONST) ? !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)) : (!(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))))) { - spec = 3443 | SPEC_RULE_OP1; + spec = 3449 | SPEC_RULE_OP1; } break; case ZEND_PRE_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3421 | SPEC_RULE_RETVAL; + spec = 3427 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3423 | SPEC_RULE_RETVAL; + spec = 3429 | SPEC_RULE_RETVAL; } break; case ZEND_PRE_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3425 | SPEC_RULE_RETVAL; + spec = 3431 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3427 | SPEC_RULE_RETVAL; + spec = 3433 | SPEC_RULE_RETVAL; } break; case ZEND_POST_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3429; + spec = 3435; } else if (op1_info == MAY_BE_LONG) { - spec = 3430; + spec = 3436; } break; case ZEND_POST_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3431; + spec = 3437; } else if (op1_info == MAY_BE_LONG) { - spec = 3432; + spec = 3438; } break; case ZEND_JMP: @@ -68353,17 +68477,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t break; case ZEND_SEND_VAL: if (op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3483; + spec = 3489; } break; case ZEND_SEND_VAR_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3478 | SPEC_RULE_OP1; + spec = 3484 | SPEC_RULE_OP1; } break; case ZEND_FE_FETCH_R: if (op->op2_type == IS_CV && (op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 3485 | SPEC_RULE_RETVAL; + spec = 3491 | SPEC_RULE_RETVAL; } break; case ZEND_FETCH_DIM_R: @@ -68371,17 +68495,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3448 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 3454 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_SEND_VAL_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && op->op1_type == IS_CONST && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3484; + spec = 3490; } break; case ZEND_SEND_VAR: if (op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3473 | SPEC_RULE_OP1; + spec = 3479 | SPEC_RULE_OP1; } break; case ZEND_COUNT: diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h index cbbc6a4772788..33d951141550e 100644 --- a/Zend/zend_vm_handlers.h +++ b/Zend/zend_vm_handlers.h @@ -1640,235 +1640,241 @@ _(3108, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(3109, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3110, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3111, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3115, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3116, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3120, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3124, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3125, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3126, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3127, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3128, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3129, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3111, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ + _(3112, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3113, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3114, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ + _(3115, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3116, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3117, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3121, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3122, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3126, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3130, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3131, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3132, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ _(3133, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ _(3134, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ _(3135, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3136, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3137, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3138, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3139, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3140, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3141, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3142, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3144, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3139, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3140, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3141, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3142, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3144, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3145, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3146, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3147, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3148, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(3149, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3150, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3151, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3152, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3153, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ _(3154, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(3155, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3156, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3157, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3158, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3159, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3157, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3158, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3159, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3160, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3161, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3162, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3163, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(3164, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3165, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3181, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3182, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3183, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3184, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3185, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3186, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3187, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3188, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3189, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3169, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3170, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3171, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3187, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3188, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3189, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3190, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3191, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3192, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3193, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(3194, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3195, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3199, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3200, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3201, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3202, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3203, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3204, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3199, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3200, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3201, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3205, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3206, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3207, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ _(3208, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ _(3209, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3212, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3213, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3219, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3219, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3220, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3221, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3222, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3223, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(3224, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3226, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3227, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3228, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ _(3229, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(3230, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3231, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3232, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3233, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3234, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3232, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3233, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3234, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3235, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3236, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3237, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3238, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(3239, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3240, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3256, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3257, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3258, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3259, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3260, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3261, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3262, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3263, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3264, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3244, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3245, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3246, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3262, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3263, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3264, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3265, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3266, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3267, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3268, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(3269, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3270, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3274, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3275, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3276, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3277, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3278, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3279, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3274, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3275, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3276, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3280, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3281, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3282, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ _(3283, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ _(3284, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3287, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3288, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3294, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3294, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3295, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3296, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3297, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3298, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(3299, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3301, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3302, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3303, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ _(3304, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(3305, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3306, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3307, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3308, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3309, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3307, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3308, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3309, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3310, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3311, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3312, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3313, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(3314, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3315, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3331, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3332, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3333, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3334, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3335, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3336, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3337, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3338, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3339, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3319, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3320, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3321, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3337, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3338, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3339, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3340, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3341, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3342, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3343, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(3344, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3345, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3349, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3350, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3351, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3352, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3353, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3354, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3349, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3350, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3351, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3355, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3356, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3357, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ _(3358, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ _(3359, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3362, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3363, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3369, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3369, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3370, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3371, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3372, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3373, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(3374, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3376, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3377, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3378, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ _(3379, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(3380, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3381, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3382, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3383, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3384, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3382, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3383, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3384, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3385, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3386, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3387, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3388, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(3389, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3390, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3406, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3407, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3408, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3409, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3410, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3411, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3412, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3413, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3414, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3394, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3395, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3396, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3412, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3413, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3414, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3415, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3416, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3417, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ _(3418, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(3419, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ _(3420, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3421, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3422, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3423, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3424, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ - _(3425, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3426, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3427, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3428, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ - _(3429, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3430, ZEND_POST_INC_LONG_SPEC_CV) \ - _(3431, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3432, ZEND_POST_DEC_LONG_SPEC_CV) \ - _(3433, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ - _(3434, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3435, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3437, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3438, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ - _(3439, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3440, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3442, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3443, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ - _(3444, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3445, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3447, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3449, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3450, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3452, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3453, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3454, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3455, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3457, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3458, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3459, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3424, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3425, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3426, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3427, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3428, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3429, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3430, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ + _(3431, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3432, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3433, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3434, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ + _(3435, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3436, ZEND_POST_INC_LONG_SPEC_CV) \ + _(3437, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3438, ZEND_POST_DEC_LONG_SPEC_CV) \ + _(3439, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ + _(3440, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3441, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3443, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3444, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ + _(3445, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3446, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3448, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3449, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ + _(3450, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3451, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3453, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3455, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3456, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3458, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3459, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ _(3460, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3462, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3468, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ - _(3469, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3470, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3472, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3475, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ - _(3477, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ - _(3480, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ - _(3482, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ - _(3483, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ - _(3484, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ - _(3485, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ - _(3486, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ - _(3486+1, ZEND_NULL) + _(3461, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3463, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3464, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ + _(3465, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3466, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3468, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3474, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ + _(3475, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3476, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3478, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3481, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ + _(3483, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ + _(3486, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ + _(3488, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ + _(3489, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ + _(3490, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ + _(3491, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ + _(3492, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ + _(3492+1, ZEND_NULL) From 3cd0383d918a023885349fc22552ee417924b97b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 May 2025 18:05:11 +0200 Subject: [PATCH 46/62] Adjust default value of opcache.jit_hot_loop to a prime number Loops whose number of iterations + 1 is a factor of opcache.jit_hot_loop will always be traced at the exact moment the loop condition evaluates to false. As a result, these loops can never be JIT'ed successfully. Here I adjust the default value of opcache.jit_hot_loop to a prime number, so this can not happen (unless number of iterations+1 is opcache.jit_hot_loop). Closes GH-18573 --- NEWS | 3 ++- UPGRADING | 3 +++ ext/opcache/zend_accelerator_module.c | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 807d70236c7f4..d0a1d30fc9e2e 100644 --- a/NEWS +++ b/NEWS @@ -111,9 +111,10 @@ PHP NEWS . Added mysqlnd.collect_memory_statistics to ini quick reference. (hauk92) -- OPcache: +- Opcache: . Fixed ZTS OPcache build on Cygwin. (cmb) . Added opcache.file_cache_read_only. (Samuel Melrose) + . Updated default value of opcache.jit_hot_loop. (Arnaud) - Output: . Fixed calculation of aligned buffer size. (cmb) diff --git a/UPGRADING b/UPGRADING index 79ff29eae55a5..e120f445e24ca 100644 --- a/UPGRADING +++ b/UPGRADING @@ -460,6 +460,9 @@ PHP 8.5 UPGRADE NOTES Note: A cache generated with a different build of PHP, a different file path, or different settings (including which extensions are loaded), may be ignored. + . The default value of opcache.jit_hot_loop is now 61 (a prime) to prevent it + from being a multiple of loop iteration counts. + It is recommended that this parameter is set to a prime number. ======================================== 12. Windows Support diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 0c52b98dc453c..203a41d93b40a 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -323,7 +323,8 @@ ZEND_INI_BEGIN() STD_PHP_INI_ENTRY("opcache.jit_max_root_traces" , "1024", PHP_INI_SYSTEM, OnUpdateLong, max_root_traces, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_max_side_traces" , "128", PHP_INI_SYSTEM, OnUpdateLong, max_side_traces, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_max_exit_counters" , "8192", PHP_INI_SYSTEM, OnUpdateLong, max_exit_counters, zend_jit_globals, jit_globals) - STD_PHP_INI_ENTRY("opcache.jit_hot_loop" , "64", PHP_INI_SYSTEM, OnUpdateCounter, hot_loop, zend_jit_globals, jit_globals) + /* Defautl value should be a prime number, to reduce the chances of loop iterations being a factor of opcache.jit_hot_loop */ + STD_PHP_INI_ENTRY("opcache.jit_hot_loop" , "61", PHP_INI_SYSTEM, OnUpdateCounter, hot_loop, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_hot_func" , "127", PHP_INI_SYSTEM, OnUpdateCounter, hot_func, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_hot_return" , "8", PHP_INI_SYSTEM, OnUpdateCounter, hot_return, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_hot_side_exit" , "8", PHP_INI_ALL, OnUpdateCounter, hot_side_exit, zend_jit_globals, jit_globals) From 16ca097ef2825cbf668a8ea6610e46db5e8df6a7 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 16 May 2025 21:23:15 +0200 Subject: [PATCH 47/62] Do not exit to VM when setting undefined prop in constructor JIT'ed ASSIGN_OBJ expressions will exit to VM when the prop is undef. However, in a constructor it's very likely the case. Therefore most traces with `new` expressions will exit to VM. Here I ensure that we don't need to exit to VM when it's likely that the prop will be undef. In the function JIT we compile a slow path to handle such properties, but not in the tracing JIT, assumingly to reduce code size. Here I enable compilation of the slow path in the tracing JIT when it's likely the prop will be undef. Quite conveniently we already record the prop type during tracing, so I use that to make the decision. This results in a 1.20% wall time improvement on the symfony demo benchmark with 20 warmup requests. Closes GH-18576 --- ext/opcache/jit/zend_jit_ir.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 2a3b9baf28e67..1fdb1b9b3af91 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -14971,8 +14971,9 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, ZEND_ASSERT(slow_inputs == IR_UNUSED); goto slow_path; } + // Undefined property with potential magic __get()/__set() or lazy object - if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { + if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && prop_type != IS_UNDEF) { int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM); const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point); From 35455b17be9fce39c2072a5dcfd9b835507c754f Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Mon, 19 May 2025 09:45:28 -0600 Subject: [PATCH 48/62] fix: dangling opline in ZEND_INIT_ARRAY (#18578) This causes problems if an allocation profiler decides to walk the stack, or if the engine itself OOMs on this opcode, and it tries to print file and line information. --- Zend/zend_vm_def.h | 1 + Zend/zend_vm_execute.h | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 53aa7a821f697..19422fe5eebfd 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6281,6 +6281,7 @@ ZEND_VM_HANDLER(71, ZEND_INIT_ARRAY, CONST|TMP|VAR|CV|UNUSED, CONST|TMPVAR|UNUSE uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (OP1_TYPE != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 2c86a94134c08..209e6cdbe7dfe 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7424,6 +7424,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CONST_CONST_HA uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_CONST != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -9765,6 +9766,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CONST_TMPVAR_H uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_CONST != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -10695,6 +10697,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CONST_UNUSED_H uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_CONST != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -12161,6 +12164,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CONST_CV_HANDL uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_CONST != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -20189,6 +20193,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_TMP_CONST_HAND uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_TMP_VAR != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -20633,6 +20638,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_TMP_TMPVAR_HAN uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_TMP_VAR != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -21094,6 +21100,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_TMP_UNUSED_HAN uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_TMP_VAR != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -21498,6 +21505,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_TMP_CV_HANDLER uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_TMP_VAR != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -25327,6 +25335,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_VAR_CONST_HAND uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_VAR != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -27777,6 +27786,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_VAR_TMPVAR_HAN uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_VAR != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -29855,6 +29865,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_VAR_UNUSED_HAN uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_VAR != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -32165,6 +32176,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_VAR_CV_HANDLER uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_VAR != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -34399,6 +34411,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_UNUSED_CONST_H uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_UNUSED != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -36281,6 +36294,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_UNUSED_TMPVAR_ uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_UNUSED != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -36918,6 +36932,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_UNUSED_UNUSED_ uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_UNUSED != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -38776,6 +38791,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_UNUSED_CV_HAND uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_UNUSED != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -43871,6 +43887,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CV_CONST_HANDL uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_CV != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -47511,6 +47528,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CV_TMPVAR_HAND uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_CV != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -49480,6 +49498,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CV_UNUSED_HAND uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_CV != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; @@ -52998,6 +53017,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CV_CV_HANDLER( uint32_t size; USE_OPLINE + SAVE_OPLINE(); array = EX_VAR(opline->result.var); if (IS_CV != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; From 46ac878f6a0c19514085b81b185a574084f244cf Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 18 May 2025 14:30:35 +0200 Subject: [PATCH 49/62] Fix OSS-Fuzz #417078295 If the variable_ptr and fetched value are the same or overlap, then we get a UAF. Prevent this by delaying destruction. Closes GH-18588. --- NEWS | 1 + Zend/tests/oss_fuzz_417078295.phpt | 17 +++++++++++++++++ Zend/zend_vm_def.h | 3 ++- Zend/zend_vm_execute.h | 3 ++- 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/oss_fuzz_417078295.phpt diff --git a/NEWS b/NEWS index 25651e5ca7709..4a4259867a3fc 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ PHP NEWS (nielsdos/David Carlier) . Partially fixed GH-18572 (nested object comparisons leading to stack overflow). (David Carlier) + . Fixed OSS-Fuzz #417078295. (nielsdos) - Curl: . Fixed GH-18460 (curl_easy_setopt with CURLOPT_USERPWD/CURLOPT_USERNAME/ diff --git a/Zend/tests/oss_fuzz_417078295.phpt b/Zend/tests/oss_fuzz_417078295.phpt new file mode 100644 index 0000000000000..6e53f9478e137 --- /dev/null +++ b/Zend/tests/oss_fuzz_417078295.phpt @@ -0,0 +1,17 @@ +--TEST-- +OSS-Fuzz #417078295 +--FILE-- + +--EXPECT-- +object(stdClass)#1 (0) refcount(2){ +} diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 19422fe5eebfd..0eacdfe145d49 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8981,7 +8981,6 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, ANY, REF) value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))); if (opline->extended_value & ZEND_BIND_REF) { - i_zval_ptr_dtor(variable_ptr); if (UNEXPECTED(!Z_ISREF_P(value))) { zend_reference *ref = (zend_reference*)emalloc(sizeof(zend_reference)); GC_SET_REFCOUNT(ref, 2); @@ -8996,9 +8995,11 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, ANY, REF) ref->sources.ptr = NULL; Z_REF_P(value) = ref; Z_TYPE_INFO_P(value) = IS_REFERENCE_EX; + i_zval_ptr_dtor(variable_ptr); ZVAL_REF(variable_ptr, ref); } else { Z_ADDREF_P(value); + i_zval_ptr_dtor(variable_ptr); ZVAL_REF(variable_ptr, Z_REF_P(value)); if (OP2_TYPE != IS_UNUSED) { FREE_OP2(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 209e6cdbe7dfe..4ea6b302c8cfc 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -40564,7 +40564,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_HANDLER(ZE value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))); if (opline->extended_value & ZEND_BIND_REF) { - i_zval_ptr_dtor(variable_ptr); if (UNEXPECTED(!Z_ISREF_P(value))) { zend_reference *ref = (zend_reference*)emalloc(sizeof(zend_reference)); GC_SET_REFCOUNT(ref, 2); @@ -40579,9 +40578,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_HANDLER(ZE ref->sources.ptr = NULL; Z_REF_P(value) = ref; Z_TYPE_INFO_P(value) = IS_REFERENCE_EX; + i_zval_ptr_dtor(variable_ptr); ZVAL_REF(variable_ptr, ref); } else { Z_ADDREF_P(value); + i_zval_ptr_dtor(variable_ptr); ZVAL_REF(variable_ptr, Z_REF_P(value)); if (opline->op2_type != IS_UNUSED) { FREE_OP(opline->op2_type, opline->op2.var); From 98cb17f4fd19590f261c1916c0322a7aecc768b9 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 18 May 2025 14:59:27 +0200 Subject: [PATCH 50/62] Fix OSS-Fuzz #418106144 The VM assumes that an exception must be handled when the AST evaluation returns FAILURE. However, the comparison functions always return SUCCESS even if an exception happened. This can be fixed in zend_ast_evaluate_inner() or we can make is_smaller_function() etc check for the exception. I chose the former to avoid impact or API breaks. Perhaps in the future the comparison functions should either return void or return whether an exception happened, as to be not misleading. Closes GH-18589. --- NEWS | 1 + Zend/tests/gh418106144.phpt | 20 ++++++++++++++++++++ Zend/zend_ast.c | 3 ++- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/gh418106144.phpt diff --git a/NEWS b/NEWS index 4a4259867a3fc..f20891f344f79 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ PHP NEWS . Partially fixed GH-18572 (nested object comparisons leading to stack overflow). (David Carlier) . Fixed OSS-Fuzz #417078295. (nielsdos) + . Fixed OSS-Fuzz #418106144. (nielsdos) - Curl: . Fixed GH-18460 (curl_easy_setopt with CURLOPT_USERPWD/CURLOPT_USERNAME/ diff --git a/Zend/tests/gh418106144.phpt b/Zend/tests/gh418106144.phpt new file mode 100644 index 0000000000000..9357b0a179b1c --- /dev/null +++ b/Zend/tests/gh418106144.phpt @@ -0,0 +1,20 @@ +--TEST-- +OSS-Fuzz #418106144 +--FILE-- +''){ + var_dump(); +} +try { + test(); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Foo::__toString(): Return value must be of type string, none returned diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index bf602449e5e4e..0fb50e2eae1f5 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -564,9 +564,10 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( /* op1 > op2 is the same as op2 < op1 */ binary_op_type op = ast->kind == ZEND_AST_GREATER ? is_smaller_function : is_smaller_or_equal_function; - ret = op(result, &op2, &op1); + op(result, &op2, &op1); zval_ptr_dtor_nogc(&op1); zval_ptr_dtor_nogc(&op2); + ret = EG(exception) ? FAILURE : SUCCESS; } break; case ZEND_AST_UNARY_OP: From 92a0cc7d947424087b9ed82c949688acfd1643c8 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 May 2025 19:10:27 +0200 Subject: [PATCH 51/62] Fix deprecation warning for libxml SAX header (#18594) This header is deprecated, but fortunately it isn't actually used. --- ext/dom/document.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/dom/document.c b/ext/dom/document.c index 42d67d5739845..e622a09309b6e 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -22,7 +22,6 @@ #include "php.h" #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "php_dom.h" -#include #include #ifdef LIBXML_SCHEMAS_ENABLED #include From 41e11a627da684c79f14295410bbf169092b1fc2 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 16 May 2025 21:12:17 +0200 Subject: [PATCH 52/62] Fix GH-18567: Preloading with internal class alias triggers assertion failure The assertion is imprecise now, and the code assumed that from the moment an internal class was encountered that there were only internal classes remaining. This is wrong now, and we still have to continue if we encounter an internal class. We can only skip the remaining iterations if the entry in the hash table is not an alias. Closes GH-18575. --- NEWS | 2 ++ ext/opcache/ZendAccelerator.c | 35 +++++++++++++++++++++++---- ext/opcache/tests/gh18567.phpt | 27 +++++++++++++++++++++ ext/opcache/tests/preload_gh18567.inc | 2 ++ 4 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 ext/opcache/tests/gh18567.phpt create mode 100644 ext/opcache/tests/preload_gh18567.inc diff --git a/NEWS b/NEWS index f20891f344f79..0e3b53db3c22c 100644 --- a/NEWS +++ b/NEWS @@ -32,6 +32,8 @@ PHP NEWS - Opcache: . Fixed bug GH-18417 (Windows SHM reattachment fails when increasing memory_consumption or jit_buffer_size). (nielsdos) + . Fixed bug GH-18567 (Preloading with internal class alias triggers assertion + failure). (nielsdos) - PDO_OCI: . Fixed bug GH-18494 (PDO OCI segfault in statement GC). (nielsdos) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index e3f7ca18151e5..47e0bc08bd5cc 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3522,7 +3522,7 @@ static void preload_shutdown(void) if (EG(class_table)) { ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(class_table), zv) { zend_class_entry *ce = Z_PTR_P(zv); - if (ce->type == ZEND_INTERNAL_CLASS) { + if (ce->type == ZEND_INTERNAL_CLASS && Z_TYPE_P(zv) != IS_ALIAS_PTR) { break; } } ZEND_HASH_MAP_FOREACH_END_DEL(); @@ -3610,7 +3610,15 @@ static void preload_move_user_classes(HashTable *src, HashTable *dst) zend_hash_extend(dst, dst->nNumUsed + src->nNumUsed, 0); ZEND_HASH_MAP_FOREACH_BUCKET_FROM(src, p, EG(persistent_classes_count)) { zend_class_entry *ce = Z_PTR(p->val); - ZEND_ASSERT(ce->type == ZEND_USER_CLASS); + + /* Possible with internal class aliases */ + if (ce->type == ZEND_INTERNAL_CLASS) { + ZEND_ASSERT(Z_TYPE(p->val) == IS_ALIAS_PTR); + _zend_hash_append(dst, p->key, &p->val); + zend_hash_del_bucket(src, p); + continue; + } + if (ce->info.user.filename != filename) { filename = ce->info.user.filename; if (filename) { @@ -3904,7 +3912,12 @@ static void preload_link(void) ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_FROM(EG(class_table), key, zv, EG(persistent_classes_count)) { ce = Z_PTR_P(zv); - ZEND_ASSERT(ce->type != ZEND_INTERNAL_CLASS); + + /* Possible with internal class aliases */ + if (ce->type == ZEND_INTERNAL_CLASS) { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_ALIAS_PTR); + continue; + } if (!(ce->ce_flags & (ZEND_ACC_TOP_LEVEL|ZEND_ACC_ANON_CLASS)) || (ce->ce_flags & ZEND_ACC_LINKED)) { @@ -3990,9 +4003,15 @@ static void preload_link(void) ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(class_table), zv) { ce = Z_PTR_P(zv); + + /* Possible with internal class aliases */ if (ce->type == ZEND_INTERNAL_CLASS) { - break; + if (Z_TYPE_P(zv) != IS_ALIAS_PTR) { + break; /* can stop already */ + } + continue; } + if ((ce->ce_flags & ZEND_ACC_LINKED) && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { if (!(ce->ce_flags & ZEND_ACC_TRAIT)) { /* don't update traits */ CG(in_compilation) = true; /* prevent autoloading */ @@ -4009,7 +4028,13 @@ static void preload_link(void) ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_FROM( EG(class_table), key, zv, EG(persistent_classes_count)) { ce = Z_PTR_P(zv); - ZEND_ASSERT(ce->type != ZEND_INTERNAL_CLASS); + + /* Possible with internal class aliases */ + if (ce->type == ZEND_INTERNAL_CLASS) { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_ALIAS_PTR); + continue; + } + if ((ce->ce_flags & (ZEND_ACC_TOP_LEVEL|ZEND_ACC_ANON_CLASS)) && !(ce->ce_flags & ZEND_ACC_LINKED)) { zend_string *lcname = zend_string_tolower(ce->name); diff --git a/ext/opcache/tests/gh18567.phpt b/ext/opcache/tests/gh18567.phpt new file mode 100644 index 0000000000000..e1d2a68af6775 --- /dev/null +++ b/ext/opcache/tests/gh18567.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-18567 (Preloading with internal class alias triggers assertion failure) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.preload={PWD}/preload_gh18567.inc +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- +getInterfaces()); +?> +--EXPECT-- +array(1) { + ["Stringable"]=> + object(ReflectionClass)#2 (1) { + ["name"]=> + string(10) "Stringable" + } +} diff --git a/ext/opcache/tests/preload_gh18567.inc b/ext/opcache/tests/preload_gh18567.inc new file mode 100644 index 0000000000000..d277e83f83be6 --- /dev/null +++ b/ext/opcache/tests/preload_gh18567.inc @@ -0,0 +1,2 @@ + Date: Sun, 18 May 2025 13:45:16 +0200 Subject: [PATCH 53/62] Fix GH-18534: FPM exit code 70 with enabled opcache and hooked properties in traits The trait handling for property hooks in preloading did not exist, we add a check to skip trait clones and we add the necessary code to update the op arrays. Closes GH-18586. --- NEWS | 2 + Zend/Optimizer/zend_optimizer.c | 2 +- Zend/zend_inheritance.c | 1 + ext/opcache/ZendAccelerator.c | 71 +++++++++++++++++++++------ ext/opcache/tests/gh18534.phpt | 21 ++++++++ ext/opcache/tests/gh18534_preload.inc | 13 +++++ 6 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 ext/opcache/tests/gh18534.phpt create mode 100644 ext/opcache/tests/gh18534_preload.inc diff --git a/NEWS b/NEWS index 79a0538243351..c8c88984cb143 100644 --- a/NEWS +++ b/NEWS @@ -41,6 +41,8 @@ PHP NEWS (Arnaud) . Fixed bug GH-18567 (Preloading with internal class alias triggers assertion failure). (nielsdos) + . Fixed bug GH-18534 (FPM exit code 70 with enabled opcache and hooked + properties in traits). (nielsdos) - SPL: . Fixed bug GH-18421 (Integer overflow with large numbers in LimitIterator). diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 9f2e3115d3666..25f16d252a831 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -1579,7 +1579,7 @@ void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void if (property->ce == ce && property->hooks) { for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { zend_function *hook = hooks[i]; - if (hook && hook->common.scope == ce) { + if (hook && hook->common.scope == ce && !(hooks[i]->op_array.fn_flags & ZEND_ACC_TRAIT_CLONE)) { zend_foreach_op_array_helper(&hooks[i]->op_array, func, context); } } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index b1ed8e4b61675..6f399fbfec5ab 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2976,6 +2976,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } } ce->ce_flags |= ZEND_ACC_USE_GUARDS; + ce->num_hooked_props++; } } ZEND_HASH_FOREACH_END(); } diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 8d1ad71355466..249ade2a7aea6 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4227,12 +4227,52 @@ static void preload_remove_empty_includes(void) static void preload_register_trait_methods(zend_class_entry *ce) { zend_op_array *op_array; + zend_property_info *info; + ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) { if (!(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { ZEND_ASSERT(op_array->refcount && "Must have refcount pointer"); zend_shared_alloc_register_xlat_entry(op_array->refcount, op_array); } } ZEND_HASH_FOREACH_END(); + + if (ce->num_hooked_props > 0) { + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, info) { + if (info->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (info->hooks[i]) { + op_array = &info->hooks[i]->op_array; + if (!(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { + ZEND_ASSERT(op_array->refcount && "Must have refcount pointer"); + zend_shared_alloc_register_xlat_entry(op_array->refcount, op_array); + } + } + } + } + } ZEND_HASH_FOREACH_END(); + } +} + +static void preload_fix_trait_op_array(zend_op_array *op_array) +{ + if (!(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { + return; + } + + zend_op_array *orig_op_array = zend_shared_alloc_get_xlat_entry(op_array->refcount); + ZEND_ASSERT(orig_op_array && "Must be in xlat table"); + + zend_string *function_name = op_array->function_name; + zend_class_entry *scope = op_array->scope; + uint32_t fn_flags = op_array->fn_flags; + zend_function *prototype = op_array->prototype; + HashTable *ht = op_array->static_variables; + *op_array = *orig_op_array; + op_array->function_name = function_name; + op_array->scope = scope; + op_array->fn_flags = fn_flags; + op_array->prototype = prototype; + op_array->static_variables = ht; } static void preload_fix_trait_methods(zend_class_entry *ce) @@ -4240,23 +4280,22 @@ static void preload_fix_trait_methods(zend_class_entry *ce) zend_op_array *op_array; ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) { - if (op_array->fn_flags & ZEND_ACC_TRAIT_CLONE) { - zend_op_array *orig_op_array = zend_shared_alloc_get_xlat_entry(op_array->refcount); - ZEND_ASSERT(orig_op_array && "Must be in xlat table"); - - zend_string *function_name = op_array->function_name; - zend_class_entry *scope = op_array->scope; - uint32_t fn_flags = op_array->fn_flags; - zend_function *prototype = op_array->prototype; - HashTable *ht = op_array->static_variables; - *op_array = *orig_op_array; - op_array->function_name = function_name; - op_array->scope = scope; - op_array->fn_flags = fn_flags; - op_array->prototype = prototype; - op_array->static_variables = ht; - } + preload_fix_trait_op_array(op_array); } ZEND_HASH_FOREACH_END(); + + if (ce->num_hooked_props > 0) { + zend_property_info *info; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, info) { + if (info->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + if (info->hooks[i]) { + op_array = &info->hooks[i]->op_array; + preload_fix_trait_op_array(op_array); + } + } + } + } ZEND_HASH_FOREACH_END(); + } } static void preload_optimize(zend_persistent_script *script) diff --git a/ext/opcache/tests/gh18534.phpt b/ext/opcache/tests/gh18534.phpt new file mode 100644 index 0000000000000..87181470793bd --- /dev/null +++ b/ext/opcache/tests/gh18534.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-18534 (FPM exit code 70 with enabled opcache and hooked properties in traits) +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.preload={PWD}/gh18534_preload.inc +--FILE-- +dummyProperty2); + +?> +--EXPECT-- +NULL diff --git a/ext/opcache/tests/gh18534_preload.inc b/ext/opcache/tests/gh18534_preload.inc new file mode 100644 index 0000000000000..22cee6165e533 --- /dev/null +++ b/ext/opcache/tests/gh18534_preload.inc @@ -0,0 +1,13 @@ + null; + } +} + +class DummyModel +{ + use DummyTrait; +} From db3bf715e0d5719646485e8390c71f487e33433a Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 18 May 2025 19:50:30 +0200 Subject: [PATCH 54/62] Fix leak of accel_globals->key I don't know why this was guarded with ZTS, but it leaks on this test (and a few more): `./sapi/cli/php ./run-tests.php -c . --show-diff sapi/phpdbg/tests/stdin_001.phpt` Closes GH-18593. --- NEWS | 1 + ext/opcache/ZendAccelerator.c | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/NEWS b/NEWS index c8c88984cb143..44d7b87e6cae4 100644 --- a/NEWS +++ b/NEWS @@ -43,6 +43,7 @@ PHP NEWS failure). (nielsdos) . Fixed bug GH-18534 (FPM exit code 70 with enabled opcache and hooked properties in traits). (nielsdos) + . Fix leak of accel_globals->key. (nielsdos) - SPL: . Fixed bug GH-18421 (Integer overflow with large numbers in LimitIterator). diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 249ade2a7aea6..82ae1adce44c5 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2964,9 +2964,7 @@ static void accel_globals_ctor(zend_accel_globals *accel_globals) static void accel_globals_dtor(zend_accel_globals *accel_globals) { -#ifdef ZTS zend_string_free(accel_globals->key); -#endif if (accel_globals->preloaded_internal_run_time_cache) { pefree(accel_globals->preloaded_internal_run_time_cache, 1); } From 5bbe3d71c2e2452285f235f269d3a323bdf85a1e Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 May 2025 20:24:44 +0200 Subject: [PATCH 55/62] Improve performance of instantiating exceptions/errors (#18442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The class structure is fixed, so it makes no sense to go through all the logic of looking up property info etc if there are no hooks. This patch introduces a local function `zend_update_property_num_checked()` to help with that. For this benchmark: ```php for ($i = 0; $i < 1000000; $i++) new Error; ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php x.php Time (mean ± σ): 141.6 ms ± 9.3 ms [User: 138.7 ms, System: 2.0 ms] Range (min … max): 135.4 ms … 177.7 ms 20 runs Benchmark 2: ../RELx64_old/sapi/cli/php x.php Time (mean ± σ): 214.1 ms ± 7.0 ms [User: 207.6 ms, System: 5.0 ms] Range (min … max): 206.6 ms … 230.9 ms 13 runs Summary ./sapi/cli/php x.php ran 1.51 ± 0.11 times faster than ../RELx64_old/sapi/cli/php x.php ``` For this benchmark: ```php for ($i = 0; $i < 1000000; $i++) new Exception("message", 0, null); ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php x.php Time (mean ± σ): 184.3 ms ± 9.5 ms [User: 181.2 ms, System: 1.8 ms] Range (min … max): 173.8 ms … 205.1 ms 15 runs Benchmark 2: ../RELx64_old/sapi/cli/php x.php Time (mean ± σ): 253.7 ms ± 7.0 ms [User: 247.6 ms, System: 4.6 ms] Range (min … max): 245.7 ms … 263.7 ms 11 runs Summary ./sapi/cli/php x.php ran 1.38 ± 0.08 times faster than ../RELx64_old/sapi/cli/php x.php ``` For this benchmark: ```php for ($i = 0; $i < 1000000; $i++) new ErrorException("message", 0, 0, "xyz", 0, null); ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php x.php Time (mean ± σ): 223.6 ms ± 7.7 ms [User: 220.1 ms, System: 2.4 ms] Range (min … max): 216.9 ms … 242.5 ms 12 runs Benchmark 2: ../RELx64_old/sapi/cli/php x.php Time (mean ± σ): 343.5 ms ± 8.1 ms [User: 337.1 ms, System: 4.6 ms] Range (min … max): 337.3 ms … 362.8 ms 10 runs Summary ./sapi/cli/php x.php ran 1.54 ± 0.06 times faster than ../RELx64_old/sapi/cli/php x.php ``` --- UPGRADING | 1 + Zend/zend_exceptions.c | 77 ++++++++++++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/UPGRADING b/UPGRADING index e120f445e24ca..67b6dcb8e75c7 100644 --- a/UPGRADING +++ b/UPGRADING @@ -520,6 +520,7 @@ PHP 8.5 UPGRADE NOTES . Remove OPcodes for identity comparisons against booleans, particularly for the match(true) pattern. . Add OPcode specialization for `=== []` and `!== []` comparisons. + . Creating exception objects is now much faster. - ReflectionProperty: . Improved performance of the following methods: getValue(), getRawValue(), diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index f9d0ae8ea8173..82c9addc3d7d8 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -30,6 +30,14 @@ #include "zend_exceptions_arginfo.h" #include "zend_observer.h" +#define ZEND_EXCEPTION_MESSAGE_OFF 0 +#define ZEND_EXCEPTION_CODE_OFF 2 +#define ZEND_EXCEPTION_FILE_OFF 3 +#define ZEND_EXCEPTION_LINE_OFF 4 +#define ZEND_EXCEPTION_TRACE_OFF 5 +#define ZEND_EXCEPTION_PREVIOUS_OFF 6 +#define ZEND_EXCEPTION_SEVERITY_OFF 7 + ZEND_API zend_class_entry *zend_ce_throwable; ZEND_API zend_class_entry *zend_ce_exception; ZEND_API zend_class_entry *zend_ce_error_exception; @@ -254,11 +262,33 @@ ZEND_API void zend_clear_exception(void) /* {{{ */ } /* }}} */ +/* Same as writing to OBJ_PROP_NUM() when there are no hooks, + * but checks the offset is correct when Zend is built in debug mode. + * This is faster than going through the regular property write routine when the offset is known at compile time. */ +static void zend_update_property_num_checked(zend_object *object, uint32_t prop_num, zend_string *member, zval *value) +{ + if (UNEXPECTED(object->ce->num_hooked_props > 0)) { + /* Property may have been overridden with a hook. */ + zend_update_property_ex(object->ce, object, member, value); + zval_ptr_dtor(value); + return; + } +#if ZEND_DEBUG + zend_class_entry *old_scope = EG(fake_scope); + EG(fake_scope) = i_get_exception_base(object); + const zend_property_info *prop_info = zend_get_property_info(object->ce, member, true); + ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == prop_num); + EG(fake_scope) = old_scope; +#endif + zval *zv = OBJ_PROP_NUM(object, prop_num); + zval_ptr_safe_dtor(zv); + ZVAL_COPY_VALUE(zv, value); +} + static zend_object *zend_default_exception_new(zend_class_entry *class_type) /* {{{ */ { zval tmp; zval trace; - zend_class_entry *base_ce; zend_string *filename; zend_object *object = zend_objects_new(class_type); @@ -269,26 +299,23 @@ static zend_object *zend_default_exception_new(zend_class_entry *class_type) /* 0, EG(exception_ignore_args) ? DEBUG_BACKTRACE_IGNORE_ARGS : 0, 0); } else { - array_init(&trace); + ZVAL_EMPTY_ARRAY(&trace); } - Z_SET_REFCOUNT(trace, 0); - base_ce = i_get_exception_base(object); + zend_update_property_num_checked(object, ZEND_EXCEPTION_TRACE_OFF, ZSTR_KNOWN(ZEND_STR_TRACE), &trace); if (EXPECTED((class_type != zend_ce_parse_error && class_type != zend_ce_compile_error) || !(filename = zend_get_compiled_filename()))) { ZVAL_STRING(&tmp, zend_get_executed_filename()); - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_FILE), &tmp); - zval_ptr_dtor(&tmp); + zend_update_property_num_checked(object, ZEND_EXCEPTION_FILE_OFF, ZSTR_KNOWN(ZEND_STR_FILE), &tmp); ZVAL_LONG(&tmp, zend_get_executed_lineno()); - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_LINE), &tmp); + zend_update_property_num_checked(object, ZEND_EXCEPTION_LINE_OFF, ZSTR_KNOWN(ZEND_STR_LINE), &tmp); } else { - ZVAL_STR(&tmp, filename); - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_FILE), &tmp); + ZVAL_STR_COPY(&tmp, filename); + zend_update_property_num_checked(object, ZEND_EXCEPTION_FILE_OFF, ZSTR_KNOWN(ZEND_STR_FILE), &tmp); ZVAL_LONG(&tmp, zend_get_compiled_lineno()); - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_LINE), &tmp); + zend_update_property_num_checked(object, ZEND_EXCEPTION_LINE_OFF, ZSTR_KNOWN(ZEND_STR_LINE), &tmp); } - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_TRACE), &trace); return object; } @@ -308,27 +335,26 @@ ZEND_METHOD(Exception, __construct) zend_string *message = NULL; zend_long code = 0; zval tmp, *object, *previous = NULL; - zend_class_entry *base_ce; object = ZEND_THIS; - base_ce = i_get_exception_base(Z_OBJ_P(object)); if (zend_parse_parameters(ZEND_NUM_ARGS(), "|SlO!", &message, &code, &previous, zend_ce_throwable) == FAILURE) { RETURN_THROWS(); } if (message) { - ZVAL_STR(&tmp, message); - zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp); + ZVAL_STR_COPY(&tmp, message); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_MESSAGE_OFF, ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp); } if (code) { ZVAL_LONG(&tmp, code); - zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_CODE), &tmp); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_CODE_OFF, ZSTR_KNOWN(ZEND_STR_CODE), &tmp); } if (previous) { - zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous); + Z_ADDREF_P(previous); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_PREVIOUS_OFF, ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous); } } /* }}} */ @@ -368,34 +394,33 @@ ZEND_METHOD(ErrorException, __construct) if (message) { ZVAL_STR_COPY(&tmp, message); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp); - zval_ptr_dtor(&tmp); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_MESSAGE_OFF, ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp); } if (code) { ZVAL_LONG(&tmp, code); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_CODE), &tmp); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_CODE_OFF, ZSTR_KNOWN(ZEND_STR_CODE), &tmp); } if (previous) { - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous); + Z_ADDREF_P(previous); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_PREVIOUS_OFF, ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous); } ZVAL_LONG(&tmp, severity); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_SEVERITY), &tmp); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_SEVERITY_OFF, ZSTR_KNOWN(ZEND_STR_SEVERITY), &tmp); if (filename) { ZVAL_STR_COPY(&tmp, filename); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_FILE), &tmp); - zval_ptr_dtor(&tmp); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_FILE_OFF, ZSTR_KNOWN(ZEND_STR_FILE), &tmp); } if (!lineno_is_null) { ZVAL_LONG(&tmp, lineno); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_LINE), &tmp); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_LINE_OFF, ZSTR_KNOWN(ZEND_STR_LINE), &tmp); } else if (filename) { ZVAL_LONG(&tmp, 0); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_LINE), &tmp); + zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_LINE_OFF, ZSTR_KNOWN(ZEND_STR_LINE), &tmp); } } /* }}} */ From b2d78ae00cd462aefef3eb83525e0e24d82ef2b4 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 May 2025 19:37:34 +0200 Subject: [PATCH 56/62] Backport accel_globals->key leak fix (8.3) Closes GH-18602. --- NEWS | 1 + ext/opcache/ZendAccelerator.c | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 0e3b53db3c22c..e9c47a74a354e 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,7 @@ PHP NEWS memory_consumption or jit_buffer_size). (nielsdos) . Fixed bug GH-18567 (Preloading with internal class alias triggers assertion failure). (nielsdos) + . Fix leak of accel_globals->key. (nielsdos) - PDO_OCI: . Fixed bug GH-18494 (PDO OCI segfault in statement GC). (nielsdos) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 47e0bc08bd5cc..bd6b323871bcc 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2937,12 +2937,10 @@ static void accel_globals_ctor(zend_accel_globals *accel_globals) GC_MAKE_PERSISTENT_LOCAL(accel_globals->key); } -#ifdef ZTS static void accel_globals_dtor(zend_accel_globals *accel_globals) { zend_string_free(accel_globals->key); } -#endif #ifdef HAVE_HUGE_CODE_PAGES # ifndef _WIN32 @@ -3384,6 +3382,8 @@ void accel_shutdown(void) if (!ZCG(enabled) || !accel_startup_ok) { #ifdef ZTS ts_free_id(accel_globals_id); +#else + accel_globals_dtor(&accel_globals); #endif return; } @@ -3398,6 +3398,8 @@ void accel_shutdown(void) #ifdef ZTS ts_free_id(accel_globals_id); +#else + accel_globals_dtor(&accel_globals); #endif if (!_file_cache_only) { From 31ebb42268246f291ab44bd9124fdd1e0e17b81f Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 May 2025 21:29:00 +0200 Subject: [PATCH 57/62] Fix missing checks against php_set_blocking() in xp_ssl.c --- ext/openssl/xp_ssl.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 67846ad3aae83..5617d06759d7b 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2092,8 +2092,9 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si if (php_openssl_compare_timeval(elapsed_time, *timeout) > 0 ) { /* If the socket was originally blocking, set it back. */ if (began_blocked) { - php_set_sock_blocking(sslsock->s.socket, 1); - sslsock->s.is_blocked = 1; + if (php_set_sock_blocking(sslsock->s.socket, 1) == SUCCESS) { + sslsock->s.is_blocked = 1; + } } sslsock->s.timeout_event = 1; return -1; @@ -2520,8 +2521,9 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val if (php_openssl_compare_timeval(elapsed_time, *timeout) > 0 ) { /* If the socket was originally blocking, set it back. */ if (began_blocked) { - php_set_sock_blocking(sslsock->s.socket, 1); - sslsock->s.is_blocked = 1; + if (php_set_sock_blocking(sslsock->s.socket, 1) == SUCCESS) { + sslsock->s.is_blocked = 1; + } } sslsock->s.timeout_event = 1; return PHP_STREAM_OPTION_RETURN_ERR; @@ -2572,8 +2574,9 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val if (began_blocked && !sslsock->s.is_blocked) { // Set it back to blocking - php_set_sock_blocking(sslsock->s.socket, 1); - sslsock->s.is_blocked = 1; + if (php_set_sock_blocking(sslsock->s.socket, 1) == SUCCESS) { + sslsock->s.is_blocked = 1; + } } } else { #ifdef PHP_WIN32 From 1863014fbdd66f679ff149c30a90f7cc24d4700a Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 May 2025 21:59:07 +0200 Subject: [PATCH 58/62] Split off php_set_sock_blocking() and s.is_blocked to a separate function This makes it harder to forget the check and keeps the variable and function call consistent. Closes GH-18604. --- NEWS | 3 +++ ext/openssl/xp_ssl.c | 40 +++++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index e9c47a74a354e..aed9bd06181a0 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,9 @@ PHP NEWS failure). (nielsdos) . Fix leak of accel_globals->key. (nielsdos) +- OpenSSL: + . Fix missing checks against php_set_blocking() in xp_ssl.c. (nielsdos) + - PDO_OCI: . Fixed bug GH-18494 (PDO OCI segfault in statement GC). (nielsdos) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 5617d06759d7b..1c7aabefa1bcd 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -1901,6 +1901,15 @@ static int php_openssl_capture_peer_certs(php_stream *stream, } /* }}} */ +static zend_result php_openssl_set_blocking(php_openssl_netstream_data_t *sslsock, int block) +{ + zend_result result = php_set_sock_blocking(sslsock->s.socket, block); + if (EXPECTED(SUCCESS == result)) { + sslsock->s.is_blocked = block; + } + return result; +} + static int php_openssl_enable_crypto(php_stream *stream, php_openssl_netstream_data_t *sslsock, php_stream_xport_crypto_param *cparam) /* {{{ */ @@ -1929,8 +1938,7 @@ static int php_openssl_enable_crypto(php_stream *stream, sslsock->state_set = 1; } - if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0)) { - sslsock->s.is_blocked = 0; + if (SUCCESS == php_openssl_set_blocking(sslsock, 0)) { /* The following mode are added only if we are able to change socket * to non blocking mode which is also used for read and write */ SSL_set_mode(sslsock->ssl_handle, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); @@ -1983,8 +1991,8 @@ static int php_openssl_enable_crypto(php_stream *stream, } } while (retry); - if (sslsock->s.is_blocked != blocked && SUCCESS == php_set_sock_blocking(sslsock->s.socket, blocked)) { - sslsock->s.is_blocked = blocked; + if (sslsock->s.is_blocked != blocked) { + php_openssl_set_blocking(sslsock, blocked); } if (n == 1) { @@ -2067,8 +2075,8 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si timeout = &sslsock->s.timeout; } - if (timeout && php_set_sock_blocking(sslsock->s.socket, 0) == SUCCESS) { - sslsock->s.is_blocked = 0; + if (timeout) { + php_openssl_set_blocking(sslsock, 0); } if (!sslsock->s.is_blocked && timeout && (timeout->tv_sec > 0 || (timeout->tv_sec == 0 && timeout->tv_usec))) { @@ -2092,9 +2100,7 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si if (php_openssl_compare_timeval(elapsed_time, *timeout) > 0 ) { /* If the socket was originally blocking, set it back. */ if (began_blocked) { - if (php_set_sock_blocking(sslsock->s.socket, 1) == SUCCESS) { - sslsock->s.is_blocked = 1; - } + php_openssl_set_blocking(sslsock, 1); } sslsock->s.timeout_event = 1; return -1; @@ -2189,8 +2195,8 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si } /* And if we were originally supposed to be blocking, let's reset the socket to that. */ - if (began_blocked && php_set_sock_blocking(sslsock->s.socket, 1) == SUCCESS) { - sslsock->s.is_blocked = 1; + if (began_blocked) { + php_openssl_set_blocking(sslsock, 1); } return 0 > nr_bytes ? 0 : nr_bytes; @@ -2496,8 +2502,8 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val timeout = &tv; } - if (timeout && php_set_sock_blocking(sslsock->s.socket, 0) == SUCCESS) { - sslsock->s.is_blocked = 0; + if (timeout) { + php_openssl_set_blocking(sslsock, 0); } if (!sslsock->s.is_blocked && timeout && (timeout->tv_sec > 0 || (timeout->tv_sec == 0 && timeout->tv_usec))) { @@ -2521,9 +2527,7 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val if (php_openssl_compare_timeval(elapsed_time, *timeout) > 0 ) { /* If the socket was originally blocking, set it back. */ if (began_blocked) { - if (php_set_sock_blocking(sslsock->s.socket, 1) == SUCCESS) { - sslsock->s.is_blocked = 1; - } + php_openssl_set_blocking(sslsock, 1); } sslsock->s.timeout_event = 1; return PHP_STREAM_OPTION_RETURN_ERR; @@ -2574,9 +2578,7 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val if (began_blocked && !sslsock->s.is_blocked) { // Set it back to blocking - if (php_set_sock_blocking(sslsock->s.socket, 1) == SUCCESS) { - sslsock->s.is_blocked = 1; - } + php_openssl_set_blocking(sslsock, 1); } } else { #ifdef PHP_WIN32 From 3e0a4259a8eabe0c00b12178d0b15c3c00fa59f4 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Tue, 20 May 2025 13:13:34 -0300 Subject: [PATCH 59/62] PHP 8.4 is now for PHP-8.4.9-dev --- NEWS | 5 ++++- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index c54c42628e232..8ce2b87f1c8a2 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.4.8 +?? ??? ????, PHP 8.4.9 + + +06 Jun 2025, PHP 8.4.8 - Core: . Fixed GH-18480 (array_splice with large values for offset/length arguments). diff --git a/Zend/zend.h b/Zend/zend.h index 0ce9956c99c99..34a6a0258a261 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.4.8-dev" +#define ZEND_VERSION "4.4.9-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 6dc1e45b34fd6..3662d3e985b02 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.4.8-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.4.9-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index 9ac406dfc3208..7bd5e8c37f895 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 4 -#define PHP_RELEASE_VERSION 8 +#define PHP_RELEASE_VERSION 9 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.4.8-dev" -#define PHP_VERSION_ID 80408 +#define PHP_VERSION "8.4.9-dev" +#define PHP_VERSION_ID 80409 From 76791e90b9a26f707f4a5f3a0e7e7d5b17e2e820 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Tue, 20 May 2025 16:20:59 -0300 Subject: [PATCH 60/62] Use win32 glob implementation on all platforms (#18164) * Move glob to main/ from win32/ In preparation to make the Win32 reimplementation the standard cross-platform one. Currently, it doesn't do that and just passes through the original glob implementation. We could consider also having an option to use the standard glob for systems that have a sufficient one. * Enable building with win32 glob on non-windows Kind of broken. We're namespacing the function and struct, but not yet the GLOB_* defines. There are a lot of places callers check if i.e. NOMATCH is defined that would likely become redundant. Currently it also has php_glob and #defines glob php_glob (etc.) - I suspect doing the opposite and changing the callers would make more sense, just doing MVP to geet it to build (even if it fails tests). * Massive first pass at conversion to internal glob Have not tested yet. the big things are: - Should be invisible to userland PHP code. - A lot of :%s/GLOB_/PHP_GLOB_/g; the diff can be noisy as a result, especially in comments. - Prefixes everything with PHP_ to avoid conflicts with system glob in case it gets included transitively. - A lot of weird shared definitions that were sprawled out to other headers are now included in php_glob.h. - A lot of (but not yet all cases) of HAVE_GLOB are removed, since we can always fall back to php_glob. - Using the system glob is not wired up yet; it'll need more shim ifdefs for each flag type than just glob_t/glob/globfree defs. * Fix inclusion of GLOB_ONLYDIR This is a GNU extension, but we don't need to implement it, as the GNU implementation is flawed enough that callers have to manually filter it anyways; just provide a stub definition for the constant. We could consideer implementing this properly later. For now, fixes the basic glob constant tests. * Remove HAVE_GLOBs We now always have a glob implementation that works. HAVE_GLOB should only be used to check if we have a system implementation, for if we decide to wrap the system implementation instead. * We don't need to care about being POSIXly correct for internal glob * Check for reallocarray Ideally temporary until GH-17433. * Forgot to move this file from win32/ to main/ * Check for issetugid (BSD function) * Allow using the system glob with --enable-system-glob * Style fix after removing ifdef * Remove empty case for system glob --- Zend/Optimizer/zend_func_infos.h | 2 - configure.ac | 14 ++ ext/ffi/ffi.c | 30 +--- ext/opcache/zend_accelerator_blacklist.c | 24 +-- ext/spl/spl_directory.c | 14 +- ext/spl/spl_directory.stub.php | 2 - ext/spl/spl_directory_arginfo.h | 13 +- ext/standard/basic_functions.c | 2 - ext/standard/basic_functions.stub.php | 2 - ext/standard/basic_functions_arginfo.h | 8 +- ext/standard/dir.c | 26 ++- ext/standard/dir.stub.php | 34 ++-- ext/standard/dir_arginfo.h | 34 ++-- ext/standard/php_dir_int.h | 47 +----- ext/zip/php_zip.c | 65 ++------ {win32 => main}/charclass.h | 0 win32/glob.c => main/php_glob.c | 192 ++++++++++++----------- main/php_glob.h | 184 ++++++++++++++++++++++ main/streams/glob_wrapper.c | 31 +--- main/streams/plain_wrapper.c | 2 - sapi/fpm/fpm/fpm_conf.c | 27 +--- win32/build/config.w32 | 4 +- win32/build/config.w32.h.in | 1 - win32/glob.h | 100 ------------ 24 files changed, 395 insertions(+), 463 deletions(-) rename {win32 => main}/charclass.h (100%) rename win32/glob.c => main/php_glob.c (84%) create mode 100644 main/php_glob.h delete mode 100644 win32/glob.h diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 0fc33ae2f6e1c..3655e5fd21c35 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -518,9 +518,7 @@ static const func_info_t func_infos[] = { F1("getcwd", MAY_BE_STRING|MAY_BE_FALSE), F1("readdir", MAY_BE_STRING|MAY_BE_FALSE), F1("scandir", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE), -#if defined(HAVE_GLOB) F1("glob", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE), -#endif F1("exec", MAY_BE_STRING|MAY_BE_FALSE), F1("system", MAY_BE_STRING|MAY_BE_FALSE), F1("escapeshellcmd", MAY_BE_STRING), diff --git a/configure.ac b/configure.ac index 01d9ded69b920..61d1c350a82be 100644 --- a/configure.ac +++ b/configure.ac @@ -557,6 +557,7 @@ AC_CHECK_FUNCS(m4_normalize([ getwd glob gmtime_r + issetugid lchown localtime_r memcntl @@ -571,6 +572,7 @@ AC_CHECK_FUNCS(m4_normalize([ poll pthread_jit_write_protect_np putenv + reallocarray scandir setenv setitimer @@ -591,6 +593,17 @@ AC_CHECK_FUNCS(m4_normalize([ vasprintf ])) +PHP_ARG_ENABLE([system-glob], + [whether to use the system glob function], + [AS_HELP_STRING([--enable-system-glob], + [Use the system glob function instead of the PHP provided replacement.])], + [no], + [no]) + +AS_VAR_IF([PHP_SYSTEM_GLOB], [yes], + [AC_DEFINE([PHP_SYSTEM_GLOB], [1], + [Define to 1 if PHP will use the system glob function instead of php_glob.])]) + AC_CHECK_FUNC([inet_ntop],, [AC_MSG_FAILURE([Required inet_ntop not found.])]) AC_CHECK_FUNC([inet_pton],, [AC_MSG_FAILURE([Required inet_pton not found.])]) @@ -1631,6 +1644,7 @@ PHP_ADD_SOURCES([main], m4_normalize([ php_content_types.c php_ini_builder.c php_ini.c + php_glob.c php_odbc_utils.c php_open_temporary_file.c php_scandir.c diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 621e60c6f19a3..10fc11f52e70f 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -45,13 +45,7 @@ #endif #endif -#ifdef HAVE_GLOB -#ifdef PHP_WIN32 -#include "win32/glob.h" -#else -#include -#endif -#endif +#include "php_glob.h" #ifndef __BIGGEST_ALIGNMENT__ /* XXX need something better, perhaps with regard to SIMD, etc. */ @@ -5360,16 +5354,15 @@ ZEND_INI_END() static zend_result zend_ffi_preload_glob(const char *filename) /* {{{ */ { -#ifdef HAVE_GLOB - glob_t globbuf; + php_glob_t globbuf; int ret; unsigned int i; - memset(&globbuf, 0, sizeof(glob_t)); + memset(&globbuf, 0, sizeof(globbuf)); - ret = glob(filename, 0, NULL, &globbuf); -#ifdef GLOB_NOMATCH - if (ret == GLOB_NOMATCH || !globbuf.gl_pathc) { + ret = php_glob(filename, 0, NULL, &globbuf); +#ifdef PHP_GLOB_NOMATCH + if (ret == PHP_GLOB_NOMATCH || !globbuf.gl_pathc) { #else if (!globbuf.gl_pathc) { #endif @@ -5378,20 +5371,13 @@ static zend_result zend_ffi_preload_glob(const char *filename) /* {{{ */ for(i=0 ; i -#endif -#endif +#include "php_glob.h" #include "ext/pcre/php_pcre.h" @@ -320,16 +314,15 @@ static void zend_accel_blacklist_loadone(zend_blacklist *blacklist, char *filena void zend_accel_blacklist_load(zend_blacklist *blacklist, char *filename) { -#ifdef HAVE_GLOB - glob_t globbuf; + php_glob_t globbuf; int ret; unsigned int i; - memset(&globbuf, 0, sizeof(glob_t)); + memset(&globbuf, 0, sizeof(globbuf)); - ret = glob(filename, 0, NULL, &globbuf); -#ifdef GLOB_NOMATCH - if (ret == GLOB_NOMATCH || !globbuf.gl_pathc) { + ret = php_glob(filename, 0, NULL, &globbuf); +#ifdef PHP_GLOB_NOMATCH + if (ret == PHP_GLOB_NOMATCH || !globbuf.gl_pathc) { #else if (!globbuf.gl_pathc) { #endif @@ -338,11 +331,8 @@ void zend_accel_blacklist_load(zend_blacklist *blacklist, char *filename) for(i=0 ; itype == SPL_FS_DIR && spl_intern_is_glob(intern)) { size_t len = 0; char *tmp = php_glob_stream_get_path(intern->u.dir.dirp, &len); @@ -220,7 +219,6 @@ PHPAPI zend_string *spl_filesystem_object_get_path(const spl_filesystem_object * } return zend_string_init(tmp, len, /* persistent */ false); } -#endif if (!intern->path) { return NULL; } @@ -641,14 +639,12 @@ static inline HashTable *spl_filesystem_object_get_debug_info(zend_object *objec spl_set_private_debug_info_property(spl_ce_SplFileInfo, "fileName", strlen("fileName"), debug_info, &tmp); } if (intern->type == SPL_FS_DIR) { -#ifdef HAVE_GLOB if (spl_intern_is_glob(intern)) { ZVAL_STR_COPY(&tmp, intern->path); } else { ZVAL_FALSE(&tmp); } spl_set_private_debug_info_property(spl_ce_DirectoryIterator, "glob", strlen("glob"), debug_info, &tmp); -#endif if (intern->u.dir.sub_path) { ZVAL_STR_COPY(&tmp, intern->u.dir.sub_path); } else { @@ -721,16 +717,12 @@ static void spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAMETERS, zend_l /* spl_filesystem_dir_open() may emit an E_WARNING */ zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling); -#ifdef HAVE_GLOB if (SPL_HAS_FLAG(ctor_flags, DIT_CTOR_GLOB) && !zend_string_starts_with_literal(path, "glob://")) { path = zend_strpprintf(0, "glob://%s", ZSTR_VAL(path)); spl_filesystem_dir_open(intern, path); zend_string_release(path); - } else -#endif - { + } else { spl_filesystem_dir_open(intern, path); - } zend_restore_error_handling(&error_handling); } @@ -1582,7 +1574,6 @@ PHP_METHOD(RecursiveDirectoryIterator, __construct) } /* }}} */ -#ifdef HAVE_GLOB /* {{{ Cronstructs a new dir iterator from a glob expression (no glob:// needed). */ PHP_METHOD(GlobIterator, __construct) { @@ -1607,7 +1598,6 @@ PHP_METHOD(GlobIterator, count) } } /* }}} */ -#endif /* HAVE_GLOB */ /* {{{ forward declarations to the iterator handlers */ static void spl_filesystem_dir_it_dtor(zend_object_iterator *iter); @@ -2782,11 +2772,9 @@ PHP_MINIT_FUNCTION(spl_directory) spl_filesystem_object_check_handlers.clone_obj = NULL; spl_filesystem_object_check_handlers.get_method = spl_filesystem_object_get_method_check; -#ifdef HAVE_GLOB spl_ce_GlobIterator = register_class_GlobIterator(spl_ce_FilesystemIterator, zend_ce_countable); spl_ce_GlobIterator->create_object = spl_filesystem_object_new; spl_ce_GlobIterator->default_object_handlers = &spl_filesystem_object_check_handlers; -#endif spl_ce_SplFileObject = register_class_SplFileObject(spl_ce_SplFileInfo, spl_ce_RecursiveIterator, spl_ce_SeekableIterator); spl_ce_SplFileObject->default_object_handlers = &spl_filesystem_object_check_handlers; diff --git a/ext/spl/spl_directory.stub.php b/ext/spl/spl_directory.stub.php index e8ea5c4af9e81..6194a8617b438 100644 --- a/ext/spl/spl_directory.stub.php +++ b/ext/spl/spl_directory.stub.php @@ -207,7 +207,6 @@ public function getSubPath(): string {} public function getSubPathname(): string {} } -#ifdef HAVE_GLOB class GlobIterator extends FilesystemIterator implements Countable { public function __construct(string $pattern, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO) {} @@ -215,7 +214,6 @@ public function __construct(string $pattern, int $flags = FilesystemIterator::KE /** @tentative-return-type */ public function count(): int {} } -#endif class SplFileObject extends SplFileInfo implements RecursiveIterator, SeekableIterator { diff --git a/ext/spl/spl_directory_arginfo.h b/ext/spl/spl_directory_arginfo.h index e9287e7ea9483..55606b1a15397 100644 --- a/ext/spl/spl_directory_arginfo.h +++ b/ext/spl/spl_directory_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 06a7809f97dde10e800382fec03a9bb308918bb3 */ + * Stub hash: 802429d736404c2d66601f640942c827b6e6e94b */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFileInfo___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -151,15 +151,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_RecursiveDirectoryIterator_getSubPathname arginfo_class_SplFileInfo_getPath -#if defined(HAVE_GLOB) ZEND_BEGIN_ARG_INFO_EX(arginfo_class_GlobIterator___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, pattern, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_GlobIterator_count, 0, 0, IS_LONG, 0) -ZEND_END_ARG_INFO() -#endif +#define arginfo_class_GlobIterator_count arginfo_class_FilesystemIterator_getFlags ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFileObject___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -323,10 +320,8 @@ ZEND_METHOD(RecursiveDirectoryIterator, hasChildren); ZEND_METHOD(RecursiveDirectoryIterator, getChildren); ZEND_METHOD(RecursiveDirectoryIterator, getSubPath); ZEND_METHOD(RecursiveDirectoryIterator, getSubPathname); -#if defined(HAVE_GLOB) ZEND_METHOD(GlobIterator, __construct); ZEND_METHOD(GlobIterator, count); -#endif ZEND_METHOD(SplFileObject, __construct); ZEND_METHOD(SplFileObject, rewind); ZEND_METHOD(SplFileObject, eof); @@ -430,13 +425,11 @@ static const zend_function_entry class_RecursiveDirectoryIterator_methods[] = { ZEND_FE_END }; -#if defined(HAVE_GLOB) static const zend_function_entry class_GlobIterator_methods[] = { ZEND_ME(GlobIterator, __construct, arginfo_class_GlobIterator___construct, ZEND_ACC_PUBLIC) ZEND_ME(GlobIterator, count, arginfo_class_GlobIterator_count, ZEND_ACC_PUBLIC) ZEND_FE_END }; -#endif static const zend_function_entry class_SplFileObject_methods[] = { ZEND_ME(SplFileObject, __construct, arginfo_class_SplFileObject___construct, ZEND_ACC_PUBLIC) @@ -602,7 +595,6 @@ static zend_class_entry *register_class_RecursiveDirectoryIterator(zend_class_en return class_entry; } -#if defined(HAVE_GLOB) static zend_class_entry *register_class_GlobIterator(zend_class_entry *class_entry_FilesystemIterator, zend_class_entry *class_entry_Countable) { zend_class_entry ce, *class_entry; @@ -613,7 +605,6 @@ static zend_class_entry *register_class_GlobIterator(zend_class_entry *class_ent return class_entry; } -#endif static zend_class_entry *register_class_SplFileObject(zend_class_entry *class_entry_SplFileInfo, zend_class_entry *class_entry_RecursiveIterator, zend_class_entry *class_entry_SeekableIterator) { diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 389833af3eb69..f26bef3daa4e7 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -339,9 +339,7 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */ php_register_url_stream_wrapper("php", &php_stream_php_wrapper); php_register_url_stream_wrapper("file", &php_plain_files_wrapper); -#ifdef HAVE_GLOB php_register_url_stream_wrapper("glob", &php_glob_stream_wrapper); -#endif php_register_url_stream_wrapper("data", &php_stream_rfc2397_wrapper); php_register_url_stream_wrapper("http", &php_stream_http_wrapper); php_register_url_stream_wrapper("ftp", &php_stream_ftp_wrapper); diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 4bec9d07348c0..09f63860f1163 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -2697,13 +2697,11 @@ function readdir($dir_handle = null): string|false {} */ function scandir(string $directory, int $sorting_order = SCANDIR_SORT_ASCENDING, $context = null): array|false {} -#ifdef HAVE_GLOB /** * @return array|false * @refcount 1 */ function glob(string $pattern, int $flags = 0): array|false {} -#endif /* exec.c */ diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index c39ddafc827ec..d221a221f4132 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: f1fdd58097ccd7562c63aee8e9cc1ca88f5bdf31 */ + * Stub hash: dfd7d2cfd31312f7f6c5074c10cab54e9d1fbccc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -1133,12 +1133,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_scandir, 0, 1, MAY_BE_ARRAY|MAY_ ZEND_ARG_INFO_WITH_DEFAULT_VALUE(0, context, "null") ZEND_END_ARG_INFO() -#if defined(HAVE_GLOB) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_glob, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, pattern, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0") ZEND_END_ARG_INFO() -#endif ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_exec, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, command, IS_STRING, 0) @@ -2600,9 +2598,7 @@ ZEND_FUNCTION(getcwd); ZEND_FUNCTION(rewinddir); ZEND_FUNCTION(readdir); ZEND_FUNCTION(scandir); -#if defined(HAVE_GLOB) ZEND_FUNCTION(glob); -#endif ZEND_FUNCTION(exec); ZEND_FUNCTION(system); ZEND_FUNCTION(passthru); @@ -3203,9 +3199,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(rewinddir, arginfo_rewinddir) ZEND_FE(readdir, arginfo_readdir) ZEND_FE(scandir, arginfo_scandir) -#if defined(HAVE_GLOB) ZEND_FE(glob, arginfo_glob) -#endif ZEND_FE(exec, arginfo_exec) ZEND_FE(system, arginfo_system) ZEND_FE(passthru, arginfo_passthru) diff --git a/ext/standard/dir.c b/ext/standard/dir.c index b468681ac364a..cced88e4ac8a5 100644 --- a/ext/standard/dir.c +++ b/ext/standard/dir.c @@ -397,7 +397,6 @@ PHP_FUNCTION(getcwd) } /* }}} */ -#ifdef HAVE_GLOB /* {{{ Find pathnames matching a pattern */ PHP_FUNCTION(glob) { @@ -410,7 +409,7 @@ PHP_FUNCTION(glob) char *pattern = NULL; size_t pattern_len; zend_long flags = 0; - glob_t globbuf; + php_glob_t globbuf; size_t n; int ret; bool basedir_limit = 0; @@ -427,7 +426,7 @@ PHP_FUNCTION(glob) RETURN_FALSE; } - if ((GLOB_AVAILABLE_FLAGS & flags) != flags) { + if ((PHP_GLOB_AVAILABLE_FLAGS & flags) != flags) { php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform"); RETURN_FALSE; } @@ -451,14 +450,14 @@ PHP_FUNCTION(glob) #endif - memset(&globbuf, 0, sizeof(glob_t)); + memset(&globbuf, 0, sizeof(globbuf)); globbuf.gl_offs = 0; - if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) { -#ifdef GLOB_NOMATCH - if (GLOB_NOMATCH == ret) { + if (0 != (ret = php_glob(pattern, flags & PHP_GLOB_FLAGMASK, NULL, &globbuf))) { +#ifdef PHP_GLOB_NOMATCH + if (PHP_GLOB_NOMATCH == ret) { /* Some glob implementation simply return no data if no matches - were found, others return the GLOB_NOMATCH error code. - We don't want to treat GLOB_NOMATCH as an error condition + were found, others return the PHP_GLOB_NOMATCH error code. + We don't want to treat PHP_GLOB_NOMATCH as an error condition so that PHP glob() behaves the same on both types of implementations and so that 'foreach (glob() as ...' can be used for simple glob() calls without further error @@ -472,7 +471,7 @@ PHP_FUNCTION(glob) /* now catch the FreeBSD style of "no matches" */ if (!globbuf.gl_pathc || !globbuf.gl_pathv) { -#ifdef GLOB_NOMATCH +#ifdef PHP_GLOB_NOMATCH no_results: #endif array_init(return_value); @@ -487,7 +486,7 @@ PHP_FUNCTION(glob) continue; } } - /* we need to do this every time since GLOB_ONLYDIR does not guarantee that + /* we need to do this every time since PHP_GLOB_ONLYDIR does not guarantee that * all directories will be filtered. GNU libc documentation states the * following: * If the information about the type of the file is easily available @@ -495,7 +494,7 @@ PHP_FUNCTION(glob) * determine the information for each file. I.e., the caller must still be * able to filter directories out. */ - if (flags & GLOB_ONLYDIR) { + if (flags & PHP_GLOB_ONLYDIR) { zend_stat_t s = {0}; if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) { @@ -510,7 +509,7 @@ PHP_FUNCTION(glob) zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); } - globfree(&globbuf); + php_globfree(&globbuf); if (basedir_limit && !zend_hash_num_elements(Z_ARRVAL_P(return_value))) { zend_array_destroy(Z_ARR_P(return_value)); @@ -518,7 +517,6 @@ PHP_FUNCTION(glob) } } /* }}} */ -#endif /* {{{ List files & directories inside the specified path */ PHP_FUNCTION(scandir) diff --git a/ext/standard/dir.stub.php b/ext/standard/dir.stub.php index afca1fcacc421..6b5ef51c13bbb 100644 --- a/ext/standard/dir.stub.php +++ b/ext/standard/dir.stub.php @@ -13,64 +13,62 @@ */ const PATH_SEPARATOR = UNKNOWN; -#ifdef HAVE_GLOB -#if (defined(GLOB_BRACE) && GLOB_BRACE != 0) +#if (defined(PHP_GLOB_BRACE) && PHP_GLOB_BRACE != 0) /** * @var int - * @cvalue GLOB_BRACE + * @cvalue PHP_GLOB_BRACE */ const GLOB_BRACE = UNKNOWN; #endif -#if (defined(GLOB_ERR) && GLOB_ERR != 0) +#if (defined(PHP_GLOB_ERR) && PHP_GLOB_ERR != 0) /** * @var int - * @cvalue GLOB_ERR + * @cvalue PHP_GLOB_ERR */ const GLOB_ERR = UNKNOWN; #endif -#if (defined(GLOB_MARK) && GLOB_MARK != 0) +#if (defined(PHP_GLOB_MARK) && PHP_GLOB_MARK != 0) /** * @var int - * @cvalue GLOB_MARK + * @cvalue PHP_GLOB_MARK */ const GLOB_MARK = UNKNOWN; #endif -#if (defined(GLOB_NOCHECK) && GLOB_NOCHECK != 0) +#if (defined(PHP_GLOB_NOCHECK) && PHP_GLOB_NOCHECK != 0) /** * @var int - * @cvalue GLOB_NOCHECK + * @cvalue PHP_GLOB_NOCHECK */ const GLOB_NOCHECK = UNKNOWN; #endif -#if (defined(GLOB_NOESCAPE) && GLOB_NOESCAPE != 0) +#if (defined(PHP_GLOB_NOESCAPE) && PHP_GLOB_NOESCAPE != 0) /** * @var int - * @cvalue GLOB_NOESCAPE + * @cvalue PHP_GLOB_NOESCAPE */ const GLOB_NOESCAPE = UNKNOWN; #endif -#if (defined(GLOB_NOSORT) && GLOB_NOSORT != 0) +#if (defined(PHP_GLOB_NOSORT) && PHP_GLOB_NOSORT != 0) /** * @var int - * @cvalue GLOB_NOSORT + * @cvalue PHP_GLOB_NOSORT */ const GLOB_NOSORT = UNKNOWN; #endif -#ifdef GLOB_ONLYDIR +#if (defined(PHP_GLOB_ONLYDIR) && PHP_GLOB_ONLYDIR != 0) /** * @var int - * @cvalue GLOB_ONLYDIR + * @cvalue PHP_GLOB_ONLYDIR */ const GLOB_ONLYDIR = UNKNOWN; #endif -#ifdef GLOB_AVAILABLE_FLAGS +#ifdef PHP_GLOB_AVAILABLE_FLAGS /** * @var int - * @cvalue GLOB_AVAILABLE_FLAGS + * @cvalue PHP_GLOB_AVAILABLE_FLAGS */ const GLOB_AVAILABLE_FLAGS = UNKNOWN; #endif -#endif /** * @var int * @cvalue PHP_SCANDIR_SORT_ASCENDING diff --git a/ext/standard/dir_arginfo.h b/ext/standard/dir_arginfo.h index f73b0e5d86ad1..703088d1eb0d5 100644 --- a/ext/standard/dir_arginfo.h +++ b/ext/standard/dir_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 543d0d12062ed88dab7a3ac4354499682c5c7166 */ + * Stub hash: e21d382cd4001001874c49d8c5244efb57613910 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Directory_close, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() @@ -24,29 +24,29 @@ static void register_dir_symbols(int module_number) { REGISTER_STRING_CONSTANT("DIRECTORY_SEPARATOR", dirsep_str, CONST_PERSISTENT); REGISTER_STRING_CONSTANT("PATH_SEPARATOR", pathsep_str, CONST_PERSISTENT); -#if defined(HAVE_GLOB) && (defined(GLOB_BRACE) && GLOB_BRACE != 0) - REGISTER_LONG_CONSTANT("GLOB_BRACE", GLOB_BRACE, CONST_PERSISTENT); +#if (defined(PHP_GLOB_BRACE) && PHP_GLOB_BRACE != 0) + REGISTER_LONG_CONSTANT("GLOB_BRACE", PHP_GLOB_BRACE, CONST_PERSISTENT); #endif -#if defined(HAVE_GLOB) && (defined(GLOB_ERR) && GLOB_ERR != 0) - REGISTER_LONG_CONSTANT("GLOB_ERR", GLOB_ERR, CONST_PERSISTENT); +#if (defined(PHP_GLOB_ERR) && PHP_GLOB_ERR != 0) + REGISTER_LONG_CONSTANT("GLOB_ERR", PHP_GLOB_ERR, CONST_PERSISTENT); #endif -#if defined(HAVE_GLOB) && (defined(GLOB_MARK) && GLOB_MARK != 0) - REGISTER_LONG_CONSTANT("GLOB_MARK", GLOB_MARK, CONST_PERSISTENT); +#if (defined(PHP_GLOB_MARK) && PHP_GLOB_MARK != 0) + REGISTER_LONG_CONSTANT("GLOB_MARK", PHP_GLOB_MARK, CONST_PERSISTENT); #endif -#if defined(HAVE_GLOB) && (defined(GLOB_NOCHECK) && GLOB_NOCHECK != 0) - REGISTER_LONG_CONSTANT("GLOB_NOCHECK", GLOB_NOCHECK, CONST_PERSISTENT); +#if (defined(PHP_GLOB_NOCHECK) && PHP_GLOB_NOCHECK != 0) + REGISTER_LONG_CONSTANT("GLOB_NOCHECK", PHP_GLOB_NOCHECK, CONST_PERSISTENT); #endif -#if defined(HAVE_GLOB) && (defined(GLOB_NOESCAPE) && GLOB_NOESCAPE != 0) - REGISTER_LONG_CONSTANT("GLOB_NOESCAPE", GLOB_NOESCAPE, CONST_PERSISTENT); +#if (defined(PHP_GLOB_NOESCAPE) && PHP_GLOB_NOESCAPE != 0) + REGISTER_LONG_CONSTANT("GLOB_NOESCAPE", PHP_GLOB_NOESCAPE, CONST_PERSISTENT); #endif -#if defined(HAVE_GLOB) && (defined(GLOB_NOSORT) && GLOB_NOSORT != 0) - REGISTER_LONG_CONSTANT("GLOB_NOSORT", GLOB_NOSORT, CONST_PERSISTENT); +#if (defined(PHP_GLOB_NOSORT) && PHP_GLOB_NOSORT != 0) + REGISTER_LONG_CONSTANT("GLOB_NOSORT", PHP_GLOB_NOSORT, CONST_PERSISTENT); #endif -#if defined(HAVE_GLOB) && defined(GLOB_ONLYDIR) - REGISTER_LONG_CONSTANT("GLOB_ONLYDIR", GLOB_ONLYDIR, CONST_PERSISTENT); +#if (defined(PHP_GLOB_ONLYDIR) && PHP_GLOB_ONLYDIR != 0) + REGISTER_LONG_CONSTANT("GLOB_ONLYDIR", PHP_GLOB_ONLYDIR, CONST_PERSISTENT); #endif -#if defined(HAVE_GLOB) && defined(GLOB_AVAILABLE_FLAGS) - REGISTER_LONG_CONSTANT("GLOB_AVAILABLE_FLAGS", GLOB_AVAILABLE_FLAGS, CONST_PERSISTENT); +#if defined(PHP_GLOB_AVAILABLE_FLAGS) + REGISTER_LONG_CONSTANT("GLOB_AVAILABLE_FLAGS", PHP_GLOB_AVAILABLE_FLAGS, CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SCANDIR_SORT_ASCENDING", PHP_SCANDIR_SORT_ASCENDING, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SCANDIR_SORT_DESCENDING", PHP_SCANDIR_SORT_DESCENDING, CONST_PERSISTENT); diff --git a/ext/standard/php_dir_int.h b/ext/standard/php_dir_int.h index f9d63e78661a1..06faf4e678f42 100644 --- a/ext/standard/php_dir_int.h +++ b/ext/standard/php_dir_int.h @@ -17,52 +17,7 @@ #ifndef PHP_DIR_INT_H #define PHP_DIR_INT_H -#ifdef HAVE_GLOB -#ifndef PHP_WIN32 -#include -#else -#include "win32/glob.h" -#endif -#endif - -#ifdef HAVE_GLOB - -#ifndef GLOB_BRACE -#define GLOB_BRACE 0 -#endif - -#ifndef GLOB_ERR -#define GLOB_ERR 0 -#endif - -#ifndef GLOB_MARK -#define GLOB_MARK 0 -#endif - -#ifndef GLOB_NOCHECK -#define GLOB_NOCHECK 0 -#endif - -#ifndef GLOB_NOESCAPE -#define GLOB_NOESCAPE 0 -#endif - -#ifndef GLOB_NOSORT -#define GLOB_NOSORT 0 -#endif - -#ifndef GLOB_ONLYDIR -#define GLOB_ONLYDIR (1<<30) -#define GLOB_EMULATE_ONLYDIR -#define GLOB_FLAGMASK (~GLOB_ONLYDIR) -#else -#define GLOB_FLAGMASK (~0) -#endif - -/* This is used for checking validity of passed flags (passing invalid flags causes segfault in glob()!! */ -#define GLOB_AVAILABLE_FLAGS (0 | GLOB_BRACE | GLOB_MARK | GLOB_NOSORT | GLOB_NOCHECK | GLOB_NOESCAPE | GLOB_ERR | GLOB_ONLYDIR) - -#endif /* HAVE_GLOB */ +#include "php_glob.h" char dirsep_str[2], pathsep_str[2]; diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index a251bc4ea0bea..5272a161ae292 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -30,13 +30,7 @@ #include "php_zip.h" #include "php_zip_arginfo.h" -#ifdef HAVE_GLOB -#ifndef PHP_WIN32 -#include -#else -#include "win32/glob.h" -#endif -#endif +#include "php_glob.h" /* {{{ Resource le */ static int le_zip_dir; @@ -580,48 +574,15 @@ static char * php_zipobj_get_zip_comment(ze_zip_object *obj, int *len) /* {{{ */ } /* }}} */ -#ifdef HAVE_GLOB /* {{{ */ -#ifndef GLOB_ONLYDIR -#define GLOB_ONLYDIR (1<<30) -#define GLOB_EMULATE_ONLYDIR -#define GLOB_FLAGMASK (~GLOB_ONLYDIR) -#else -#define GLOB_FLAGMASK (~0) -#endif -#ifndef GLOB_BRACE -# define GLOB_BRACE 0 -#endif -#ifndef GLOB_MARK -# define GLOB_MARK 0 -#endif -#ifndef GLOB_NOSORT -# define GLOB_NOSORT 0 -#endif -#ifndef GLOB_NOCHECK -# define GLOB_NOCHECK 0 -#endif -#ifndef GLOB_NOESCAPE -# define GLOB_NOESCAPE 0 -#endif -#ifndef GLOB_ERR -# define GLOB_ERR 0 -#endif - -/* This is used for checking validity of passed flags (passing invalid flags causes segfault in glob()!! */ -#define GLOB_AVAILABLE_FLAGS (0 | GLOB_BRACE | GLOB_MARK | GLOB_NOSORT | GLOB_NOCHECK | GLOB_NOESCAPE | GLOB_ERR | GLOB_ONLYDIR) - -#endif /* }}} */ - int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_value) /* {{{ */ { -#ifdef HAVE_GLOB int cwd_skip = 0; #ifdef ZTS char cwd[MAXPATHLEN]; char work_pattern[MAXPATHLEN]; char *result; #endif - glob_t globbuf; + php_glob_t globbuf; size_t n; int ret; @@ -630,7 +591,7 @@ int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_v return -1; } - if ((GLOB_AVAILABLE_FLAGS & flags) != flags) { + if ((PHP_GLOB_AVAILABLE_FLAGS & flags) != flags) { php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform"); return -1; @@ -655,12 +616,12 @@ int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_v #endif globbuf.gl_offs = 0; - if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) { -#ifdef GLOB_NOMATCH - if (GLOB_NOMATCH == ret) { + if (0 != (ret = php_glob(pattern, flags & PHP_GLOB_FLAGMASK, NULL, &globbuf))) { +#ifdef PHP_GLOB_NOMATCH + if (PHP_GLOB_NOMATCH == ret) { /* Some glob implementation simply return no data if no matches - were found, others return the GLOB_NOMATCH error code. - We don't want to treat GLOB_NOMATCH as an error condition + were found, others return the PHP_GLOB_NOMATCH error code. + We don't want to treat PHP_GLOB_NOMATCH as an error condition so that PHP glob() behaves the same on both types of implementations and so that 'foreach (glob() as ...' can be used for simple glob() calls without further error @@ -687,7 +648,7 @@ int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_v array_init(return_value); for (n = 0; n < globbuf.gl_pathc; n++) { - /* we need to do this every time since GLOB_ONLYDIR does not guarantee that + /* we need to do this every time since PHP_GLOB_ONLYDIR does not guarantee that * all directories will be filtered. GNU libc documentation states the * following: * If the information about the type of the file is easily available @@ -695,7 +656,7 @@ int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_v * determine the information for each file. I.e., the caller must still be * able to filter directories out. */ - if (flags & GLOB_ONLYDIR) { + if (flags & PHP_GLOB_ONLYDIR) { zend_stat_t s = {0}; if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) { @@ -710,12 +671,8 @@ int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_v } ret = globbuf.gl_pathc; - globfree(&globbuf); + php_globfree(&globbuf); return ret; -#else - zend_throw_error(NULL, "Glob support is not available"); - return 0; -#endif /* HAVE_GLOB */ } /* }}} */ diff --git a/win32/charclass.h b/main/charclass.h similarity index 100% rename from win32/charclass.h rename to main/charclass.h diff --git a/win32/glob.c b/main/php_glob.c similarity index 84% rename from win32/glob.c rename to main/php_glob.c index 8a04b8de18c24..8757ec0783f6d 100644 --- a/win32/glob.c +++ b/main/php_glob.c @@ -37,24 +37,28 @@ * * Optional extra services, controlled by flags not defined by POSIX: * - * GLOB_QUOTE: + * PHP_GLOB_QUOTE: * Escaping convention: \ inhibits any special meaning the following * character might have (except \ at end of string is retained). - * GLOB_MAGCHAR: + * PHP_GLOB_MAGCHAR: * Set in gl_flags if pattern contained a globbing character. - * GLOB_NOMAGIC: - * Same as GLOB_NOCHECK, but it will only append pattern if it did + * PHP_GLOB_NOMAGIC: + * Same as PHP_GLOB_NOCHECK, but it will only append pattern if it did * not contain any magic characters. [Used in csh style globbing] - * GLOB_ALTDIRFUNC: + * PHP_GLOB_ALTDIRFUNC: * Use alternately specified directory access functions. - * GLOB_TILDE: + * PHP_GLOB_TILDE: * expand ~user/foo to the /home/dir/of/user/foo - * GLOB_BRACE: + * PHP_GLOB_BRACE: * expand {1,2}{a,b} to 1a 1b 2a 2b * gl_matchc: * Number of matches in the current invocation of glob. */ +#include "php_glob.h" + +#if !(defined(HAVE_GLOB) && defined(PHP_SYSTEM_GLOB)) + #ifdef PHP_WIN32 #if _MSC_VER < 1800 # define _POSIX_ @@ -79,6 +83,11 @@ # endif #endif +#ifndef _PW_BUF_LEN +/* XXX: Should be sysconf(_SC_GETPW_R_SIZE_MAX), but then VLA */ +#define _PW_BUF_LEN 4096 +#endif + #include "php.h" #include @@ -90,7 +99,6 @@ #include #endif #include -#include "glob.h" #include #include #include @@ -149,9 +157,9 @@ typedef char Char; #define M_CLASS META(':') #define ismeta(c) (((c)&M_QUOTE) != 0) -#define GLOB_LIMIT_MALLOC 65536 -#define GLOB_LIMIT_STAT 2048 -#define GLOB_LIMIT_READDIR 16384 +#define PHP_GLOB_LIMIT_MALLOC 65536 +#define PHP_GLOB_LIMIT_STAT 2048 +#define PHP_GLOB_LIMIT_READDIR 16384 struct glob_lim { size_t glim_malloc; @@ -164,6 +172,7 @@ struct glob_path_stat { zend_stat_t *gps_stat; }; +#ifndef HAVE_REALLOCARRAY /* * XXX: This is temporary to avoid having reallocarray be imported and part of * PHP's public API. Since it's only needed here and on Windows, we can just @@ -204,33 +213,34 @@ reallocarray(void *optr, size_t nmemb, size_t size) } return realloc(optr, size * nmemb); } +#endif static int compare(const void *, const void *); static int compare_gps(const void *, const void *); static int g_Ctoc(const Char *, char *, size_t); -static int g_lstat(Char *, zend_stat_t *, glob_t *); -static DIR *g_opendir(Char *, glob_t *); +static int g_lstat(Char *, zend_stat_t *, php_glob_t *); +static DIR *g_opendir(Char *, php_glob_t *); static Char *g_strchr(const Char *, int); static int g_strncmp(const Char *, const char *, size_t); -static int g_stat(Char *, zend_stat_t *, glob_t *); -static int glob0(const Char *, glob_t *, struct glob_lim *); -static int glob1(Char *, Char *, glob_t *, struct glob_lim *); +static int g_stat(Char *, zend_stat_t *, php_glob_t *); +static int glob0(const Char *, php_glob_t *, struct glob_lim *); +static int glob1(Char *, Char *, php_glob_t *, struct glob_lim *); static int glob2(Char *, Char *, Char *, Char *, Char *, Char *, - glob_t *, struct glob_lim *); + php_glob_t *, struct glob_lim *); static int glob3(Char *, Char *, Char *, Char *, Char *, - Char *, Char *, glob_t *, struct glob_lim *); -static int globextend(const Char *, glob_t *, struct glob_lim *, + Char *, Char *, php_glob_t *, struct glob_lim *); +static int globextend(const Char *, php_glob_t *, struct glob_lim *, zend_stat_t *); -static const Char *globtilde(const Char *, Char *, size_t, glob_t *); -static int globexp1(const Char *, glob_t *, struct glob_lim *); -static int globexp2(const Char *, const Char *, glob_t *, +static const Char *globtilde(const Char *, Char *, size_t, php_glob_t *); +static int globexp1(const Char *, php_glob_t *, struct glob_lim *); +static int globexp2(const Char *, const Char *, php_glob_t *, struct glob_lim *); static int match(Char *, Char *, Char *); #ifdef DEBUG static void qprintf(const char *, Char *); #endif -PHPAPI int glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) +PHPAPI int php_glob(const char *pattern, int flags, int (*errfunc)(const char *, int), php_glob_t *pglob) { const uint8_t *patnext; int c; @@ -241,31 +251,31 @@ PHPAPI int glob(const char *pattern, int flags, int (*errfunc)(const char *, int /* Force skipping escape sequences on windows * due to the ambiguity with path backslashes */ - flags |= GLOB_NOESCAPE; + flags |= PHP_GLOB_NOESCAPE; #endif patnext = (uint8_t *) pattern; - if (!(flags & GLOB_APPEND)) { + if (!(flags & PHP_GLOB_APPEND)) { pglob->gl_pathc = 0; pglob->gl_pathv = NULL; pglob->gl_statv = NULL; - if (!(flags & GLOB_DOOFFS)) + if (!(flags & PHP_GLOB_DOOFFS)) pglob->gl_offs = 0; } - pglob->gl_flags = flags & ~GLOB_MAGCHAR; + pglob->gl_flags = flags & ~PHP_GLOB_MAGCHAR; pglob->gl_errfunc = errfunc; pglob->gl_matchc = 0; if (strnlen(pattern, PATH_MAX) == PATH_MAX) - return(GLOB_NOMATCH); + return(PHP_GLOB_NOMATCH); if (pglob->gl_offs >= SSIZE_MAX || pglob->gl_pathc >= SSIZE_MAX || pglob->gl_pathc >= SSIZE_MAX - pglob->gl_offs - 1) - return GLOB_NOSPACE; + return PHP_GLOB_NOSPACE; bufnext = patbuf; bufend = bufnext + PATH_MAX - 1; - if (flags & GLOB_NOESCAPE) + if (flags & PHP_GLOB_NOESCAPE) while (bufnext < bufend && (c = *patnext++) != EOS) *bufnext++ = c; else { @@ -282,7 +292,7 @@ PHPAPI int glob(const char *pattern, int flags, int (*errfunc)(const char *, int } *bufnext = EOS; - if (flags & GLOB_BRACE) + if (flags & PHP_GLOB_BRACE) return globexp1(patbuf, pglob, &limit); else return glob0(patbuf, pglob, &limit); @@ -293,7 +303,7 @@ PHPAPI int glob(const char *pattern, int flags, int (*errfunc)(const char *, int * invoke the standard globbing routine to glob the rest of the magic * characters */ -static int globexp1(const Char *pattern, glob_t *pglob, struct glob_lim *limitp) +static int globexp1(const Char *pattern, php_glob_t *pglob, struct glob_lim *limitp) { const Char* ptr = pattern; @@ -313,7 +323,7 @@ static int globexp1(const Char *pattern, glob_t *pglob, struct glob_lim *limitp) * If it succeeds then it invokes globexp1 with the new pattern. * If it fails then it tries to glob the rest of the pattern and returns. */ -static int globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, struct glob_lim *limitp) +static int globexp2(const Char *ptr, const Char *pattern, php_glob_t *pglob, struct glob_lim *limitp) { int i, rv; Char *lm, *ls; @@ -398,7 +408,7 @@ static int globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, struct qprintf("globexp2:", patbuf); #endif rv = globexp1(patbuf, pglob, limitp); - if (rv && rv != GLOB_NOMATCH) + if (rv && rv != PHP_GLOB_NOMATCH) return rv; /* move after the comma, to the next string */ @@ -418,7 +428,7 @@ static int globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, struct /* * expand tilde from the passwd file. */ -static const Char *globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob) +static const Char *globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, php_glob_t *pglob) { #ifndef PHP_WIN32 struct passwd pwstore, *pwd = NULL; @@ -428,7 +438,7 @@ static const Char *globtilde(const Char *pattern, Char *patbuf, size_t patbuf_le const Char *p; Char *b, *eb; - if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE)) + if (*pattern != TILDE || !(pglob->gl_flags & PHP_GLOB_TILDE)) return pattern; /* Copy up to the end of the string or / */ @@ -450,7 +460,11 @@ static const Char *globtilde(const Char *pattern, Char *patbuf, size_t patbuf_le * handle a plain ~ or ~/ by expanding $HOME * first and then trying the password file */ +#ifdef HAVE_ISSETUGID if (issetugid() != 0 || (h = getenv("HOME")) == NULL) { +#else + if ((h = getenv("HOME")) == NULL) { +#endif getpwuid_r(getuid(), &pwstore, pwbuf, sizeof(pwbuf), &pwd); if (pwd == NULL) @@ -536,7 +550,7 @@ static int g_charclass(const Char **patternp, Char **bufnextp) * if things went well, nonzero if errors occurred. It is not an error * to find no matches. */ -static int glob0(const Char *pattern, glob_t *pglob, struct glob_lim *limitp) +static int glob0(const Char *pattern, php_glob_t *pglob, struct glob_lim *limitp) { const Char *qpatnext; int c, err; @@ -575,8 +589,8 @@ static int glob0(const Char *pattern, glob_t *pglob, struct glob_lim *limitp) c = *qpatnext++; } while (c == LBRACKET && *qpatnext == ':'); if (err == -1 && - !(pglob->gl_flags & GLOB_NOCHECK)) - return GLOB_NOMATCH; + !(pglob->gl_flags & PHP_GLOB_NOCHECK)) + return PHP_GLOB_NOMATCH; if (c == RBRACKET) break; } @@ -588,15 +602,15 @@ static int glob0(const Char *pattern, glob_t *pglob, struct glob_lim *limitp) qpatnext += 2; } } while ((c = *qpatnext++) != RBRACKET); - pglob->gl_flags |= GLOB_MAGCHAR; + pglob->gl_flags |= PHP_GLOB_MAGCHAR; *bufnext++ = M_END; break; case QUESTION: - pglob->gl_flags |= GLOB_MAGCHAR; + pglob->gl_flags |= PHP_GLOB_MAGCHAR; *bufnext++ = M_ONE; break; case STAR: - pglob->gl_flags |= GLOB_MAGCHAR; + pglob->gl_flags |= PHP_GLOB_MAGCHAR; /* collapse adjacent stars to one, * to avoid exponential behavior */ @@ -618,20 +632,20 @@ static int glob0(const Char *pattern, glob_t *pglob, struct glob_lim *limitp) /* * If there was no match we are going to append the pattern - * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified + * if PHP_GLOB_NOCHECK was specified or if PHP_GLOB_NOMAGIC was specified * and the pattern did not contain any magic characters - * GLOB_NOMAGIC is there just for compatibility with csh. + * PHP_GLOB_NOMAGIC is there just for compatibility with csh. */ if (pglob->gl_pathc == oldpathc) { - if ((pglob->gl_flags & GLOB_NOCHECK) || - ((pglob->gl_flags & GLOB_NOMAGIC) && - !(pglob->gl_flags & GLOB_MAGCHAR))) + if ((pglob->gl_flags & PHP_GLOB_NOCHECK) || + ((pglob->gl_flags & PHP_GLOB_NOMAGIC) && + !(pglob->gl_flags & PHP_GLOB_MAGCHAR))) return(globextend(pattern, pglob, limitp, NULL)); else - return(GLOB_NOMATCH); + return(PHP_GLOB_NOMATCH); } - if (!(pglob->gl_flags & GLOB_NOSORT)) { - if ((pglob->gl_flags & GLOB_KEEPSTAT)) { + if (!(pglob->gl_flags & PHP_GLOB_NOSORT)) { + if ((pglob->gl_flags & PHP_GLOB_KEEPSTAT)) { /* Keep the paths and stat info synced during sort */ struct glob_path_stat *path_stat; size_t i; @@ -639,7 +653,7 @@ static int glob0(const Char *pattern, glob_t *pglob, struct glob_lim *limitp) size_t o = pglob->gl_offs + oldpathc; if ((path_stat = calloc(n, sizeof(*path_stat))) == NULL) - return GLOB_NOSPACE; + return PHP_GLOB_NOSPACE; for (i = 0; i < n; i++) { path_stat[i].gps_path = pglob->gl_pathv[o + i]; path_stat[i].gps_stat = pglob->gl_statv[o + i]; @@ -672,7 +686,7 @@ static int compare_gps(const void *_p, const void *_q) return(strcmp(p->gps_path, q->gps_path)); } -static int glob1(Char *pattern, Char *pattern_last, glob_t *pglob, struct glob_lim *limitp) +static int glob1(Char *pattern, Char *pattern_last, php_glob_t *pglob, struct glob_lim *limitp) { Char pathbuf[PATH_MAX]; @@ -689,7 +703,7 @@ static int glob1(Char *pattern, Char *pattern_last, glob_t *pglob, struct glob_l * of recursion for each segment in the pattern that contains one or more * meta characters. */ -static int glob2(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last, Char *pattern, Char *pattern_last, glob_t *pglob, struct glob_lim *limitp) +static int glob2(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last, Char *pattern, Char *pattern_last, php_glob_t *pglob, struct glob_lim *limitp) { zend_stat_t sb; Char *p, *q; @@ -703,17 +717,17 @@ static int glob2(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend if (*pattern == EOS) { /* End of pattern? */ *pathend = EOS; - if ((pglob->gl_flags & GLOB_LIMIT) && - limitp->glim_stat++ >= GLOB_LIMIT_STAT) { + if ((pglob->gl_flags & PHP_GLOB_LIMIT) && + limitp->glim_stat++ >= PHP_GLOB_LIMIT_STAT) { errno = 0; *pathend++ = SEP; *pathend = EOS; - return(GLOB_NOSPACE); + return(PHP_GLOB_NOSPACE); } if (g_lstat(pathbuf, &sb, pglob)) return(0); - if (((pglob->gl_flags & GLOB_MARK) && + if (((pglob->gl_flags & PHP_GLOB_MARK) && pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) || (S_ISLNK(sb.st_mode) && (g_stat(pathbuf, &sb, pglob) == 0) && @@ -755,7 +769,7 @@ static int glob2(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend /* NOTREACHED */ } -static int glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last, Char *pattern, Char *restpattern, Char *restpattern_last, glob_t *pglob, struct glob_lim *limitp) +static int glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last, Char *pattern, Char *restpattern, Char *restpattern_last, php_glob_t *pglob, struct glob_lim *limitp) { struct dirent *dp; DIR *dirp; @@ -779,10 +793,10 @@ static int glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend /* TODO: don't call for ENOENT or ENOTDIR? */ if (pglob->gl_errfunc) { if (g_Ctoc(pathbuf, buf, sizeof(buf))) - return(GLOB_ABORTED); + return(PHP_GLOB_ABORTED); if (pglob->gl_errfunc(buf, errno) || - pglob->gl_flags & GLOB_ERR) - return(GLOB_ABORTED); + pglob->gl_flags & PHP_GLOB_ERR) + return(PHP_GLOB_ABORTED); } return(0); } @@ -790,7 +804,7 @@ static int glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend err = 0; /* Search directory for matching names. */ - if (pglob->gl_flags & GLOB_ALTDIRFUNC) + if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC) readdirfunc = pglob->gl_readdir; else readdirfunc = (struct dirent *(*)(void *))readdir; @@ -798,12 +812,12 @@ static int glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend uint8_t *sc; Char *dc; - if ((pglob->gl_flags & GLOB_LIMIT) && - limitp->glim_readdir++ >= GLOB_LIMIT_READDIR) { + if ((pglob->gl_flags & PHP_GLOB_LIMIT) && + limitp->glim_readdir++ >= PHP_GLOB_LIMIT_READDIR) { errno = 0; *pathend++ = SEP; *pathend = EOS; - err = GLOB_NOSPACE; + err = PHP_GLOB_NOSPACE; break; } @@ -830,7 +844,7 @@ static int glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend break; } - if (pglob->gl_flags & GLOB_ALTDIRFUNC) + if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC) (*pglob->gl_closedir)(dirp); else closedir(dirp); @@ -839,7 +853,7 @@ static int glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend /* - * Extend the gl_pathv member of a glob_t structure to accommodate a new item, + * Extend the gl_pathv member of a php_glob_t structure to accommodate a new item, * add the new item, and update gl_pathc. * * This assumes the BSD realloc, which only copies the block when its size @@ -848,11 +862,11 @@ static int glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend * * Return 0 if new item added, error code if memory couldn't be allocated. * - * Invariant of the glob_t structure: + * Invariant of the php_glob_t structure: * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and * gl_pathv points to (gl_offs + gl_pathc + 1) items. */ -static int globextend(const Char *path, glob_t *pglob, struct glob_lim *limitp, zend_stat_t *sb) +static int globextend(const Char *path, php_glob_t *pglob, struct glob_lim *limitp, zend_stat_t *sb) { char **pathv; size_t i, newn, len; @@ -870,7 +884,7 @@ static int globextend(const Char *path, glob_t *pglob, struct glob_lim *limitp, for (i = pglob->gl_offs; i < newn - 2; i++) { if (pglob->gl_pathv && pglob->gl_pathv[i]) free(pglob->gl_pathv[i]); - if ((pglob->gl_flags & GLOB_KEEPSTAT) != 0 && + if ((pglob->gl_flags & PHP_GLOB_KEEPSTAT) != 0 && pglob->gl_pathv && pglob->gl_pathv[i]) free(pglob->gl_statv[i]); } @@ -878,7 +892,7 @@ static int globextend(const Char *path, glob_t *pglob, struct glob_lim *limitp, pglob->gl_pathv = NULL; free(pglob->gl_statv); pglob->gl_statv = NULL; - return(GLOB_NOSPACE); + return(PHP_GLOB_NOSPACE); } pathv = reallocarray(pglob->gl_pathv, newn, sizeof(*pathv)); @@ -892,7 +906,7 @@ static int globextend(const Char *path, glob_t *pglob, struct glob_lim *limitp, } pglob->gl_pathv = pathv; - if ((pglob->gl_flags & GLOB_KEEPSTAT) != 0) { + if ((pglob->gl_flags & PHP_GLOB_KEEPSTAT) != 0) { statv = reallocarray(pglob->gl_statv, newn, sizeof(*statv)); if (statv == NULL) goto nospace; @@ -907,10 +921,10 @@ static int globextend(const Char *path, glob_t *pglob, struct glob_lim *limitp, statv[pglob->gl_offs + pglob->gl_pathc] = NULL; else { limitp->glim_malloc += sizeof(**statv); - if ((pglob->gl_flags & GLOB_LIMIT) && - limitp->glim_malloc >= GLOB_LIMIT_MALLOC) { + if ((pglob->gl_flags & PHP_GLOB_LIMIT) && + limitp->glim_malloc >= PHP_GLOB_LIMIT_MALLOC) { errno = 0; - return(GLOB_NOSPACE); + return(PHP_GLOB_NOSPACE); } if ((statv[pglob->gl_offs + pglob->gl_pathc] = malloc(sizeof(**statv))) == NULL) @@ -928,20 +942,20 @@ static int globextend(const Char *path, glob_t *pglob, struct glob_lim *limitp, if ((copy = malloc(len)) != NULL) { if (g_Ctoc(path, copy, len)) { free(copy); - return(GLOB_NOSPACE); + return(PHP_GLOB_NOSPACE); } pathv[pglob->gl_offs + pglob->gl_pathc++] = copy; } pathv[pglob->gl_offs + pglob->gl_pathc] = NULL; - if ((pglob->gl_flags & GLOB_LIMIT) && + if ((pglob->gl_flags & PHP_GLOB_LIMIT) && (newn * sizeof(*pathv)) + limitp->glim_malloc > - GLOB_LIMIT_MALLOC) { + PHP_GLOB_LIMIT_MALLOC) { errno = 0; - return(GLOB_NOSPACE); + return(PHP_GLOB_NOSPACE); } copy_error: - return(copy == NULL ? GLOB_NOSPACE : 0); + return(copy == NULL ? PHP_GLOB_NOSPACE : 0); } @@ -1023,8 +1037,8 @@ static int match(Char *name, Char *pat, Char *patend) return(0); } -/* Free allocated data belonging to a glob_t structure. */ -PHPAPI void globfree(glob_t *pglob) +/* Free allocated data belonging to a php_glob_t structure. */ +PHPAPI void php_globfree(php_glob_t *pglob) { size_t i; char **pp; @@ -1045,7 +1059,7 @@ PHPAPI void globfree(glob_t *pglob) } } -static DIR *g_opendir(Char *str, glob_t *pglob) +static DIR *g_opendir(Char *str, php_glob_t *pglob) { char buf[PATH_MAX]; @@ -1056,30 +1070,30 @@ static DIR *g_opendir(Char *str, glob_t *pglob) return(NULL); } - if (pglob->gl_flags & GLOB_ALTDIRFUNC) + if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC) return((*pglob->gl_opendir)(buf)); return(opendir(buf)); } -static int g_lstat(Char *fn, zend_stat_t *sb, glob_t *pglob) +static int g_lstat(Char *fn, zend_stat_t *sb, php_glob_t *pglob) { char buf[PATH_MAX]; if (g_Ctoc(fn, buf, sizeof(buf))) return(-1); - if (pglob->gl_flags & GLOB_ALTDIRFUNC) + if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC) return((*pglob->gl_lstat)(buf, sb)); return(php_sys_lstat(buf, sb)); } -static int g_stat(Char *fn, zend_stat_t *sb, glob_t *pglob) +static int g_stat(Char *fn, zend_stat_t *sb, php_glob_t *pglob) { char buf[PATH_MAX]; if (g_Ctoc(fn, buf, sizeof(buf))) return(-1); - if (pglob->gl_flags & GLOB_ALTDIRFUNC) + if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC) return((*pglob->gl_stat)(buf, sb)); return(php_sys_stat(buf, sb)); } @@ -1120,3 +1134,5 @@ static void qprintf(const char *str, Char *s) (void)printf("\n"); } #endif + +#endif /* defined(HAVE_GLOB) */ diff --git a/main/php_glob.h b/main/php_glob.h new file mode 100644 index 0000000000000..853a8b31f5ce0 --- /dev/null +++ b/main/php_glob.h @@ -0,0 +1,184 @@ +/* $OpenBSD: glob.h,v 1.14 2019/02/04 16:45:40 millert Exp $ */ +/* $NetBSD: glob.h,v 1.5 1994/10/26 00:55:56 cgd Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)glob.h 8.1 (Berkeley) 6/2/93 + */ + +#ifdef PHP_WIN32 +#include "config.w32.h" +#else +#include +#endif + +#ifndef _PHP_GLOB_H_ +#define _PHP_GLOB_H_ + +#if defined(HAVE_GLOB) && defined(PHP_SYSTEM_GLOB) +#include + +#ifdef GLOB_APPEND +#define PHP_GLOB_APPEND GLOB_APPEND +#endif +#ifdef GLOB_DOOFFS +#define PHP_GLOB_DOOFFS GLOB_DOOFFS +#endif +#ifdef GLOB_ERR +#define PHP_GLOB_ERR GLOB_ERR +#endif +#ifdef GLOB_MARK +#define PHP_GLOB_MARK GLOB_MARK +#endif +#ifdef GLOB_NOCHECK +#define PHP_GLOB_NOCHECK GLOB_NOCHECK +#endif +#ifdef GLOB_NOSORT +#define PHP_GLOB_NOSORT GLOB_NOSORT +#endif +#ifdef GLOB_NOESCAPE +#define PHP_GLOB_NOESCAPE GLOB_NOESCAPE +#endif +#ifdef GLOB_NOSPACE +#define PHP_GLOB_NOSPACE GLOB_NOSPACE +#endif +#ifdef GLOB_ABORTED +#define PHP_GLOB_ABORTED GLOB_ABORTED +#endif +#ifdef GLOB_NOMATCH +#define PHP_GLOB_NOMATCH GLOB_NOMATCH +#endif +#ifdef GLOB_NOSYS +#define PHP_GLOB_NOSYS GLOB_NOSYS +#endif +#ifdef GLOB_ALTDIRFUNC +#define PHP_GLOB_ALTDIRFUNC GLOB_ALTDIRFUNC +#endif +#ifdef GLOB_BRACE +#define PHP_GLOB_BRACE GLOB_BRACE +#endif +#ifdef GLOB_MAGCHAR +#define PHP_GLOB_MAGCHAR GLOB_MAGCHAR +#endif +#ifdef GLOB_NOMAGIC +#define PHP_GLOB_NOMAGIC GLOB_NOMAGIC +#endif +#ifdef GLOB_QUOTE +#define PHP_GLOB_QUOTE GLOB_QUOTE +#endif +#ifdef GLOB_TILDE +#define PHP_GLOB_TILDE GLOB_TILDE +#endif +#ifdef GLOB_LIMIT +#define PHP_GLOB_LIMIT GLOB_LIMIT +#endif +#ifdef GLOB_KEEPSTAT +#define PHP_GLOB_KEEPSTAT GLOB_KEEPSTAT +#endif + +#define php_glob_t glob_t +#define php_glob glob +#define php_globfree globfree +#else + +#include "php.h" + +#ifndef PHP_WIN32 +# include +#endif + +#include "Zend/zend_stream.h" + +typedef struct { + size_t gl_pathc; /* Count of total paths so far. */ + size_t gl_matchc; /* Count of paths matching pattern. */ + size_t gl_offs; /* Reserved at beginning of gl_pathv. */ + int gl_flags; /* Copy of flags parameter to glob. */ + char **gl_pathv; /* List of paths matching pattern. */ + zend_stat_t **gl_statv; /* Stat entries corresponding to gl_pathv */ + /* Copy of errfunc parameter to glob. */ + int (*gl_errfunc)(const char *, int); + + /* + * Alternate filesystem access methods for glob; replacement + * versions of closedir(3), readdir(3), opendir(3), stat(2) + * and lstat(2). + */ + void (*gl_closedir)(void *); + struct dirent *(*gl_readdir)(void *); + void *(*gl_opendir)(const char *); + int (*gl_lstat)(const char *, zend_stat_t *); + int (*gl_stat)(const char *, zend_stat_t *); +} php_glob_t; + +#define PHP_GLOB_APPEND 0x0001 /* Append to output from previous call. */ +#define PHP_GLOB_DOOFFS 0x0002 /* Use gl_offs. */ +#define PHP_GLOB_ERR 0x0004 /* Return on error. */ +#define PHP_GLOB_MARK 0x0008 /* Append / to matching directories. */ +#define PHP_GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */ +#define PHP_GLOB_NOSORT 0x0020 /* Don't sort. */ +#define PHP_GLOB_NOESCAPE 0x1000 /* Disable backslash escaping. */ + +#define PHP_GLOB_NOSPACE (-1) /* Malloc call failed. */ +#define PHP_GLOB_ABORTED (-2) /* Unignored error. */ +#define PHP_GLOB_NOMATCH (-3) /* No match and PHP_GLOB_NOCHECK not set. */ +#define PHP_GLOB_NOSYS (-4) /* Function not supported. */ + +#define PHP_GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */ +#define PHP_GLOB_BRACE 0x0080 /* Expand braces ala csh. */ +#define PHP_GLOB_MAGCHAR 0x0100 /* Pattern had globbing characters. */ +#define PHP_GLOB_NOMAGIC 0x0200 /* PHP_GLOB_NOCHECK without magic chars (csh). */ +#define PHP_GLOB_QUOTE 0x0400 /* Quote special chars with \. */ +#define PHP_GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */ +#define PHP_GLOB_LIMIT 0x2000 /* Limit pattern match output to ARG_MAX */ +#define PHP_GLOB_KEEPSTAT 0x4000 /* Retain stat data for paths in gl_statv. */ + +BEGIN_EXTERN_C() +PHPAPI int php_glob(const char *__restrict, int, int (*)(const char *, int), + php_glob_t *__restrict); +PHPAPI void php_globfree(php_glob_t *); +END_EXTERN_C() + +#endif /* defined(HAVE_GLOB) */ + +/* These were copied from dir and zip */ + +#ifndef PHP_GLOB_ONLYDIR +#define PHP_GLOB_ONLYDIR (1<<30) +#define PHP_GLOB_FLAGMASK (~PHP_GLOB_ONLYDIR) +#else +#define PHP_GLOB_FLAGMASK (~0) +#endif + +#define PHP_GLOB_AVAILABLE_FLAGS (0 | PHP_GLOB_BRACE | PHP_GLOB_MARK | PHP_GLOB_NOSORT | PHP_GLOB_NOCHECK | PHP_GLOB_NOESCAPE | PHP_GLOB_ERR | PHP_GLOB_ONLYDIR) + +#endif /* !_GLOB_H_ */ diff --git a/main/streams/glob_wrapper.c b/main/streams/glob_wrapper.c index 8772850f784f3..fb056ce889fa4 100644 --- a/main/streams/glob_wrapper.c +++ b/main/streams/glob_wrapper.c @@ -17,24 +17,10 @@ #include "php.h" #include "php_streams_int.h" -#ifdef HAVE_GLOB -# ifndef PHP_WIN32 -# include -# else -# include "win32/glob.h" -# endif -#endif - -#ifdef HAVE_GLOB -#ifndef GLOB_ONLYDIR -#define GLOB_ONLYDIR (1<<30) -#define GLOB_FLAGMASK (~GLOB_ONLYDIR) -#else -#define GLOB_FLAGMASK (~0) -#endif +#include "php_glob.h" typedef struct { - glob_t glob; + php_glob_t glob; size_t index; int flags; char *path; @@ -147,7 +133,7 @@ static ssize_t php_glob_stream_read(php_stream *stream, char *buf, size_t count) if (pglob->index < (size_t) glob_result_count) { index = pglob->open_basedir_used && pglob->open_basedir_indexmap ? pglob->open_basedir_indexmap[pglob->index] : pglob->index; - php_glob_stream_path_split(pglob, pglob->glob.gl_pathv[index], pglob->flags & GLOB_APPEND, &path); + php_glob_stream_path_split(pglob, pglob->glob.gl_pathv[index], pglob->flags & PHP_GLOB_APPEND, &path); ++pglob->index; PHP_STRLCPY(ent->d_name, path, sizeof(ent->d_name), strlen(path)); ent->d_type = DT_UNKNOWN; @@ -170,7 +156,7 @@ static int php_glob_stream_close(php_stream *stream, int close_handle) /* {{{ * if (pglob) { pglob->index = 0; - globfree(&pglob->glob); + php_globfree(&pglob->glob); if (pglob->path) { efree(pglob->path); } @@ -250,9 +236,9 @@ static php_stream *php_glob_stream_opener(php_stream_wrapper *wrapper, const cha pglob = ecalloc(1, sizeof(*pglob)); - if (0 != (ret = glob(pattern, pglob->flags & GLOB_FLAGMASK, NULL, &pglob->glob))) { -#ifdef GLOB_NOMATCH - if (GLOB_NOMATCH != ret) + if (0 != (ret = php_glob(pattern, pglob->flags & PHP_GLOB_FLAGMASK, NULL, &pglob->glob))) { +#ifdef PHP_GLOB_NOMATCH + if (PHP_GLOB_NOMATCH != ret) #endif { efree(pglob); @@ -302,7 +288,7 @@ static php_stream *php_glob_stream_opener(php_stream_wrapper *wrapper, const cha pglob->pattern_len = strlen(pos); pglob->pattern = estrndup(pos, pglob->pattern_len); - pglob->flags |= GLOB_APPEND; + pglob->flags |= PHP_GLOB_APPEND; if (pglob->glob.gl_pathc) { php_glob_stream_path_split(pglob, pglob->glob.gl_pathv[0], 1, &tmp); @@ -333,4 +319,3 @@ const php_stream_wrapper php_glob_stream_wrapper = { NULL, 0 }; -#endif /* HAVE_GLOB */ diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 9396d68ecea80..a8fd70b8d6ac2 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -1088,11 +1088,9 @@ static php_stream *php_plain_files_dir_opener(php_stream_wrapper *wrapper, const DIR *dir = NULL; php_stream *stream = NULL; -#ifdef HAVE_GLOB if (options & STREAM_USE_GLOB_DIR_OPEN) { return php_glob_stream_wrapper.wops->dir_opener((php_stream_wrapper*)&php_glob_stream_wrapper, path, mode, options, opened_path, context STREAMS_REL_CC); } -#endif if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) { return NULL; diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c index ac4343bbf889e..e18cfebda08f5 100644 --- a/sapi/fpm/fpm/fpm_conf.c +++ b/sapi/fpm/fpm/fpm_conf.c @@ -10,9 +10,6 @@ #include #include #include -#ifdef HAVE_GLOB -# include -#endif #include #include @@ -22,6 +19,7 @@ #include "zend_globals.h" #include "zend_stream.h" #include "php_syslog.h" +#include "php_glob.h" #include "fpm.h" #include "fpm_conf.h" @@ -1387,26 +1385,23 @@ static void fpm_conf_ini_parser_include(char *inc, void *arg) /* {{{ */ { char *filename; int *error = (int *)arg; -#ifdef HAVE_GLOB - glob_t g; -#endif + php_glob_t g; size_t i; if (!inc || !arg) return; if (*error) return; /* We got already an error. Switch to the end. */ spprintf(&filename, 0, "%s", ini_filename); -#ifdef HAVE_GLOB { g.gl_offs = 0; - if ((i = glob(inc, GLOB_ERR | GLOB_MARK, NULL, &g)) != 0) { -#ifdef GLOB_NOMATCH - if (i == GLOB_NOMATCH) { + if ((i = php_glob(inc, PHP_GLOB_ERR | PHP_GLOB_MARK, NULL, &g)) != 0) { +#ifdef PHP_GLOB_NOMATCH + if (i == PHP_GLOB_NOMATCH) { zlog(ZLOG_WARNING, "Nothing matches the include pattern '%s' from %s at line %d.", inc, filename, ini_lineno); efree(filename); return; } -#endif /* GLOB_NOMATCH */ +#endif /* PHP_GLOB_NOMATCH */ zlog(ZLOG_ERROR, "Unable to globalize '%s' (ret=%zd) from %s at line %d.", inc, i, filename, ini_lineno); *error = 1; efree(filename); @@ -1424,16 +1419,8 @@ static void fpm_conf_ini_parser_include(char *inc, void *arg) /* {{{ */ return; } } - globfree(&g); - } -#else /* HAVE_GLOB */ - if (0 > fpm_conf_load_ini_file(inc)) { - zlog(ZLOG_ERROR, "Unable to include %s from %s at line %d", inc, filename, ini_lineno); - *error = 1; - efree(filename); - return; + php_globfree(&g); } -#endif /* HAVE_GLOB */ efree(filename); } diff --git a/win32/build/config.w32 b/win32/build/config.w32 index c9da328e2e799..f509e4eae9337 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -293,7 +293,7 @@ if (VS_TOOLSET && VCVERS >= 1914) { //AC_DEFINE('ZEND_DVAL_TO_LVAL_CAST_OK', 1); ADD_SOURCES("main", "main.c snprintf.c spprintf.c getopt.c fopen_wrappers.c \ - php_ini_builder.c \ + php_ini_builder.c php_glob.c \ php_scandir.c php_ini.c SAPI.c rfc1867.c php_content_types.c strlcpy.c \ strlcat.c reentrancy.c php_variables.c php_ticks.c network.c \ php_open_temporary_file.c output.c internal_functions.c \ @@ -314,7 +314,7 @@ if (VS_TOOLSET && VCVERS >= 1914) { ADD_FLAG("CFLAGS_BD_MAIN_STREAMS", "/d2FuncCache1"); } -ADD_SOURCES("win32", "dllmain.c glob.c readdir.c \ +ADD_SOURCES("win32", "dllmain.c readdir.c \ registry.c select.c sendmail.c time.c winutil.c wsyslog.c globals.c \ getrusage.c ftok.c ioutil.c codepage.c nice.c \ fnmatch.c sockets.c console.c signal.c"); diff --git a/win32/build/config.w32.h.in b/win32/build/config.w32.h.in index c3d2fba966bc2..40a4264cf396f 100644 --- a/win32/build/config.w32.h.in +++ b/win32/build/config.w32.h.in @@ -92,7 +92,6 @@ #endif #define SIZEOF_OFF_T 4 #define HAVE_FNMATCH -#define HAVE_GLOB #define PHP_SHLIB_SUFFIX "dll" #define PHP_SHLIB_EXT_PREFIX "php_" diff --git a/win32/glob.h b/win32/glob.h deleted file mode 100644 index 0975c507388d5..0000000000000 --- a/win32/glob.h +++ /dev/null @@ -1,100 +0,0 @@ -/* $OpenBSD: glob.h,v 1.14 2019/02/04 16:45:40 millert Exp $ */ -/* $NetBSD: glob.h,v 1.5 1994/10/26 00:55:56 cgd Exp $ */ - -/* - * Copyright (c) 1989, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Guido van Rossum. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)glob.h 8.1 (Berkeley) 6/2/93 - */ - -#ifndef _GLOB_H_ -#define _GLOB_H_ - -#ifndef PHP_WIN32 -# include -#endif - -#include "Zend/zend_stream.h" - -typedef struct { - size_t gl_pathc; /* Count of total paths so far. */ - size_t gl_matchc; /* Count of paths matching pattern. */ - size_t gl_offs; /* Reserved at beginning of gl_pathv. */ - int gl_flags; /* Copy of flags parameter to glob. */ - char **gl_pathv; /* List of paths matching pattern. */ - zend_stat_t **gl_statv; /* Stat entries corresponding to gl_pathv */ - /* Copy of errfunc parameter to glob. */ - int (*gl_errfunc)(const char *, int); - - /* - * Alternate filesystem access methods for glob; replacement - * versions of closedir(3), readdir(3), opendir(3), stat(2) - * and lstat(2). - */ - void (*gl_closedir)(void *); - struct dirent *(*gl_readdir)(void *); - void *(*gl_opendir)(const char *); - int (*gl_lstat)(const char *, zend_stat_t *); - int (*gl_stat)(const char *, zend_stat_t *); -} glob_t; - -#define GLOB_APPEND 0x0001 /* Append to output from previous call. */ -#define GLOB_DOOFFS 0x0002 /* Use gl_offs. */ -#define GLOB_ERR 0x0004 /* Return on error. */ -#define GLOB_MARK 0x0008 /* Append / to matching directories. */ -#define GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */ -#define GLOB_NOSORT 0x0020 /* Don't sort. */ -#define GLOB_NOESCAPE 0x1000 /* Disable backslash escaping. */ - -#define GLOB_NOSPACE (-1) /* Malloc call failed. */ -#define GLOB_ABORTED (-2) /* Unignored error. */ -#define GLOB_NOMATCH (-3) /* No match and GLOB_NOCHECK not set. */ -#define GLOB_NOSYS (-4) /* Function not supported. */ - -#ifndef _POSIX_SOURCE -#define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */ -#define GLOB_BRACE 0x0080 /* Expand braces ala csh. */ -#define GLOB_MAGCHAR 0x0100 /* Pattern had globbing characters. */ -#define GLOB_NOMAGIC 0x0200 /* GLOB_NOCHECK without magic chars (csh). */ -#define GLOB_QUOTE 0x0400 /* Quote special chars with \. */ -#define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */ -#define GLOB_LIMIT 0x2000 /* Limit pattern match output to ARG_MAX */ -#define GLOB_KEEPSTAT 0x4000 /* Retain stat data for paths in gl_statv. */ -#define GLOB_ABEND GLOB_ABORTED /* backward compatibility */ -#endif - -BEGIN_EXTERN_C() -PHPAPI int glob(const char *__restrict, int, int (*)(const char *, int), - glob_t *__restrict); -PHPAPI void globfree(glob_t *); -END_EXTERN_C() - -#endif /* !_GLOB_H_ */ From 40e667280bcd5d6618e30ed405000a42f4d4e601 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 20 May 2025 20:36:37 +0200 Subject: [PATCH 61/62] Fix GH-18597: Heap-buffer-overflow in zend_alloc.c when assigning string with UTF-8 bytes xmlSave() also can flush in some cases. When the encoding is not available this can fail for short inputs, resulting in an empty string which is interned but then wrongly tagged by RETURN_NEW_STR. Fix this by checking the error condition and switching to RETURN_STR for defense-in-depth. This issue also exists on 8.3, but does not crash; however, due to the different API usage internally I cannot easily fix it on 8.3. There it gives a partial output. Closes GH-18606. --- NEWS | 3 +++ ext/dom/inner_html_mixin.c | 2 +- ext/dom/xml_document.c | 4 ++-- ext/libxml/libxml.c | 2 +- ext/simplexml/simplexml.c | 3 ++- ext/simplexml/tests/gh18597.phpt | 17 +++++++++++++++++ 6 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 ext/simplexml/tests/gh18597.phpt diff --git a/NEWS b/NEWS index 8ce2b87f1c8a2..2829db92cfc02 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.9 +- SimpleXML: + . Fixed bug GH-18597 (Heap-buffer-overflow in zend_alloc.c when assigning + string with UTF-8 bytes). (nielsdos) 06 Jun 2025, PHP 8.4.8 diff --git a/ext/dom/inner_html_mixin.c b/ext/dom/inner_html_mixin.c index e72b205bf4628..0af47e2cf019f 100644 --- a/ext/dom/inner_html_mixin.c +++ b/ext/dom/inner_html_mixin.c @@ -98,7 +98,7 @@ zend_result dom_element_inner_html_read(dom_object *obj, zval *retval) status |= xmlOutputBufferFlush(out); status |= xmlOutputBufferClose(out); } - (void) xmlSaveClose(ctxt); + status |= xmlSaveClose(ctxt); xmlCharEncCloseFunc(handler); } if (UNEXPECTED(status < 0)) { diff --git a/ext/dom/xml_document.c b/ext/dom/xml_document.c index 2bd3d908d7093..4d941de0f0686 100644 --- a/ext/dom/xml_document.c +++ b/ext/dom/xml_document.c @@ -282,7 +282,7 @@ static zend_string *php_new_dom_dump_node_to_str_ex(xmlNodePtr node, int options } else { xmlCharEncCloseFunc(handler); } - (void) xmlSaveClose(ctxt); + status |= xmlSaveClose(ctxt); } if (UNEXPECTED(status < 0)) { @@ -319,7 +319,7 @@ zend_long php_new_dom_dump_node_to_file(const char *filename, xmlDocPtr doc, xml if (EXPECTED(ctxt != NULL)) { status = dom_xml_serialize(ctxt, out, node, format, false, get_private_data_from_node(node)); status |= xmlOutputBufferFlush(out); - (void) xmlSaveClose(ctxt); + status |= xmlSaveClose(ctxt); } size_t offset = php_stream_tell(stream); diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 5ad67d1244987..c637d2cebf6a4 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -1519,7 +1519,7 @@ static zend_string *php_libxml_default_dump_doc_to_str(xmlDocPtr doc, int option } long status = xmlSaveDoc(ctxt, doc); - (void) xmlSaveClose(ctxt); + status |= xmlSaveClose(ctxt); if (status < 0) { smart_str_free_ex(&str, false); return NULL; diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c index 28923c4cb3925..619f627e8532a 100644 --- a/ext/simplexml/simplexml.c +++ b/ext/simplexml/simplexml.c @@ -1404,7 +1404,8 @@ PHP_METHOD(SimpleXMLElement, asXML) if (!result) { RETURN_FALSE; } else { - RETURN_NEW_STR(result); + /* Defense-in-depth: don't use the NEW variant in case somehow an empty string gets returned */ + RETURN_STR(result); } } /* }}} */ diff --git a/ext/simplexml/tests/gh18597.phpt b/ext/simplexml/tests/gh18597.phpt new file mode 100644 index 0000000000000..e9176bf7ae041 --- /dev/null +++ b/ext/simplexml/tests/gh18597.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-18597 (Heap-buffer-overflow in zend_alloc.c when assigning string with UTF-8 bytes) +--EXTENSIONS-- +simplexml +--FILE-- +"); +$sx1->node[0] = 'node1'; +$node = $sx1->node[0]; + +$node[0] = '��c'; + +$sx1->asXML(); // Depends on the available system encodings whether this fails or not, point is, it should not crash +echo "Done\n"; +?> +--EXPECT-- +Done From 8e2c2be7a5476aad61c449993fbca93a7d3de98e Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 21 May 2025 00:39:56 +0200 Subject: [PATCH 62/62] PHP-8.3 is now for PHP 8.3.23-dev --- NEWS | 5 ++++- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index aed9bd06181a0..b0188da1c3a61 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.22 +?? ??? ????, PHP 8.3.23 + + +05 Jun 2025, PHP 8.3.22 - Core: . Fixed GH-18480 (array_splice with large values for offset/length arguments). diff --git a/Zend/zend.h b/Zend/zend.h index 99b88992475bf..704df32e9c145 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.22-dev" +#define ZEND_VERSION "4.3.23-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 278dbd110d248..965c0bdd853ee 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.3.22-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.23-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index 6cec820ba471b..28e4162421ded 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 3 -#define PHP_RELEASE_VERSION 22 +#define PHP_RELEASE_VERSION 23 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.22-dev" -#define PHP_VERSION_ID 80322 +#define PHP_VERSION "8.3.23-dev" +#define PHP_VERSION_ID 80323