From ea49b5c444b3fa1ad8668d2c3271543c86888786 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Sun, 6 Sep 2026 20:23:05 +0800 Subject: [PATCH] Standard: Validate numeric values passed to pack() Use zval_try_get_long() and zval_try_get_double() for integer and floating-point format codes, and report conversion failures instead of silently coercing them. --- NEWS | 2 + UPGRADING | 9 + ext/gmp/tests/pack_float_conversion.phpt | 42 +++++ ext/standard/pack.c | 110 ++++++++++-- ext/standard/tests/strings/pack_float.phpt | 24 --- .../tests/strings/pack_float_conversion.phpt | 162 ++++++++++++++++++ .../strings/pack_float_conversion_error.phpt | 162 ++++++++++++++++++ .../strings/pack_integer_conversion.phpt | 102 +++++++++++ .../pack_integer_conversion_error.phpt | 132 ++++++++++++++ 9 files changed, 710 insertions(+), 35 deletions(-) create mode 100644 ext/gmp/tests/pack_float_conversion.phpt create mode 100644 ext/standard/tests/strings/pack_float_conversion.phpt create mode 100644 ext/standard/tests/strings/pack_float_conversion_error.phpt create mode 100644 ext/standard/tests/strings/pack_integer_conversion.phpt create mode 100644 ext/standard/tests/strings/pack_integer_conversion_error.phpt diff --git a/NEWS b/NEWS index 98c8d3b3106b..6802ce86d061 100644 --- a/NEWS +++ b/NEWS @@ -84,6 +84,8 @@ PHP NEWS - Standard: . Fixed a segfault when a stream filter callback unsets StreamBucket::$data before re-attaching the bucket. (iliaal) + . Fixed pack() accepting values that cannot be converted to int or float for + integer and floating-point format codes. (Weilin Du) . Fixed an out-of-bounds read when following a redirect response with an empty Location header. (iliaal) . Fixed read buffer compaction in php_stream_filter_flush(). (crystarm) diff --git a/UPGRADING b/UPGRADING index c795bc347dfd..af04deecccf7 100644 --- a/UPGRADING +++ b/UPGRADING @@ -234,6 +234,15 @@ PHP 8.6 UPGRADE NOTES and DirectoryIterator::current() returns string|SplFileInfo|static. - Standard: + . pack() now throws a TypeError when a value for an integer or floating-point + format code cannot be converted to int or float, instead of silently + coercing it. Floating-point format codes use PHP numeric-string syntax, so + "INF" and "NAN" strings are no longer accepted; the corresponding float + values remain accepted. Integer format codes now emit E_DEPRECATED when + converting a float or float-string to int loses precision. Integer and + floating-point format codes emit E_WARNING for leading-numeric strings. + These diagnostics are propagated if an error handler converts them to + exceptions. . array_intersect() with at least two arrays now converts values to strings while scanning its inputs instead of during sort comparisons. This can change the number and order of conversion warnings and __toString() calls, diff --git a/ext/gmp/tests/pack_float_conversion.phpt b/ext/gmp/tests/pack_float_conversion.phpt new file mode 100644 index 000000000000..3daa3e199e9a --- /dev/null +++ b/ext/gmp/tests/pack_float_conversion.phpt @@ -0,0 +1,42 @@ +--TEST-- +pack() accepts GMP values for numeric format codes +--EXTENSIONS-- +gmp +--FILE-- += 8) { + array_push($integerFormats, 'q', 'Q', 'J', 'P'); +} +$floatFormats = ['f', 'g', 'G', 'd', 'e', 'E']; +$value = gmp_init(42); + +$passed = true; +foreach ($integerFormats as $format) { + $actual = unpack($format, pack($format, $value))[1]; + if ($actual !== 42) { + echo "Unexpected result for $format: "; + var_dump($actual); + $passed = false; + } +} +echo "integer formats: "; +var_dump($passed); + +$passed = true; +foreach ($floatFormats as $format) { + $actual = unpack($format, pack($format, $value))[1]; + if ($actual !== 42.0) { + echo "Unexpected result for $format: "; + var_dump($actual); + $passed = false; + } +} +echo "floating-point formats: "; +var_dump($passed); + +?> +--EXPECT-- +integer formats: bool(true) +floating-point formats: bool(true) diff --git a/ext/standard/pack.c b/ext/standard/pack.c index 1da93228f71f..69c2f0951cfa 100644 --- a/ext/standard/pack.c +++ b/ext/standard/pack.c @@ -55,9 +55,21 @@ typedef ZEND_SET_ALIGNED(1, unsigned int unaligned_uint); typedef ZEND_SET_ALIGNED(1, int unaligned_int); /* {{{ php_pack */ -static void php_pack(const zval *val, size_t size, php_pack_endianness endianness, char *output) +static bool php_pack( + const zval *val, uint32_t arg_num, char format_code, size_t size, + php_pack_endianness endianness, char *output +) { - zend_ulong zl = zval_get_long(val); + bool failed; + zend_ulong zl = zval_try_get_long(val, &failed); + + if (UNEXPECTED(failed)) { + zend_argument_type_error( + arg_num, "must be of type int for format code '%c', %s given", + format_code, zend_zval_value_name(val) + ); + return false; + } if ((endianness == PHP_LITTLE_ENDIAN) != MACHINE_LITTLE_ENDIAN) { zl = PHP_LONG_BSWAP(zl); @@ -71,9 +83,28 @@ static void php_pack(const zval *val, size_t size, php_pack_endianness endiannes } memcpy(output, (const char *) &zl, size); + return true; } /* }}} */ +static bool php_pack_try_get_double( + const zval *value, uint32_t arg_num, char format_code, double *result +) +{ + bool failed; + + *result = zval_try_get_double(value, &failed); + if (UNEXPECTED(failed)) { + zend_argument_type_error( + arg_num, "must be of type float for format code '%c', %s given", + format_code, zend_zval_value_name(value) + ); + return false; + } + + return true; +} + ZEND_ATTRIBUTE_CONST static inline uint16_t php_pack_reverse_int16(uint16_t arg) { return ((arg & 0xFF) << 8) | ((arg >> 8) & 0xFF); @@ -211,6 +242,7 @@ PHP_FUNCTION(pack) size_t formatcount = 0; int outputpos = 0, outputsize = 0; zend_string *output; + bool conversion_failed = false; ZEND_PARSE_PARAMETERS_START(1, -1) Z_PARAM_STRING(format, formatlen) @@ -614,7 +646,14 @@ PHP_FUNCTION(pack) case 'c': case 'C': while (arg-- > 0) { - php_pack(&argv[currentarg++], 1, PHP_MACHINE_ENDIAN, &ZSTR_VAL(output)[outputpos]); + uint32_t arg_num = currentarg + 2; + if (!php_pack( + &argv[currentarg], arg_num, code, 1, PHP_MACHINE_ENDIAN, &ZSTR_VAL(output)[outputpos] + )) { + conversion_failed = true; + goto cleanup; + } + currentarg++; outputpos++; } break; @@ -636,7 +675,14 @@ PHP_FUNCTION(pack) } while (arg-- > 0) { - php_pack(&argv[currentarg++], 2, endianness, &ZSTR_VAL(output)[outputpos]); + uint32_t arg_num = currentarg + 2; + if (!php_pack( + &argv[currentarg], arg_num, code, 2, endianness, &ZSTR_VAL(output)[outputpos] + )) { + conversion_failed = true; + goto cleanup; + } + currentarg++; outputpos += 2; } break; @@ -645,7 +691,15 @@ PHP_FUNCTION(pack) case 'i': case 'I': while (arg-- > 0) { - php_pack(&argv[currentarg++], sizeof(int), PHP_MACHINE_ENDIAN, &ZSTR_VAL(output)[outputpos]); + uint32_t arg_num = currentarg + 2; + if (!php_pack( + &argv[currentarg], arg_num, code, sizeof(int), PHP_MACHINE_ENDIAN, + &ZSTR_VAL(output)[outputpos] + )) { + conversion_failed = true; + goto cleanup; + } + currentarg++; outputpos += sizeof(int); } break; @@ -667,7 +721,14 @@ PHP_FUNCTION(pack) } while (arg-- > 0) { - php_pack(&argv[currentarg++], 4, endianness, &ZSTR_VAL(output)[outputpos]); + uint32_t arg_num = currentarg + 2; + if (!php_pack( + &argv[currentarg], arg_num, code, 4, endianness, &ZSTR_VAL(output)[outputpos] + )) { + conversion_failed = true; + goto cleanup; + } + currentarg++; outputpos += 4; } break; @@ -691,7 +752,14 @@ PHP_FUNCTION(pack) } while (arg-- > 0) { - php_pack(&argv[currentarg++], 8, endianness, &ZSTR_VAL(output)[outputpos]); + uint32_t arg_num = currentarg + 2; + if (!php_pack( + &argv[currentarg], arg_num, code, 8, endianness, &ZSTR_VAL(output)[outputpos] + )) { + conversion_failed = true; + goto cleanup; + } + currentarg++; outputpos += 8; } break; @@ -702,7 +770,15 @@ PHP_FUNCTION(pack) case 'g': case 'G': { while (arg-- > 0) { - float v = (float) zval_get_double(&argv[currentarg++]); + double d; + float v; + uint32_t arg_num = currentarg + 2; + if (!php_pack_try_get_double(&argv[currentarg], arg_num, code, &d)) { + conversion_failed = true; + goto cleanup; + } + currentarg++; + v = (float) d; if (code == 'g' || formatendian[i] == PHP_LITTLE_ENDIAN) { php_pack_copy_float(1, &ZSTR_VAL(output)[outputpos], v); } else if (code == 'G' || formatendian[i] == PHP_BIG_ENDIAN) { @@ -719,7 +795,13 @@ PHP_FUNCTION(pack) case 'e': case 'E': { while (arg-- > 0) { - double v = zval_get_double(&argv[currentarg++]); + double v; + uint32_t arg_num = currentarg + 2; + if (!php_pack_try_get_double(&argv[currentarg], arg_num, code, &v)) { + conversion_failed = true; + goto cleanup; + } + currentarg++; if (code == 'e' || formatendian[i] == PHP_LITTLE_ENDIAN) { php_pack_copy_double(1, &ZSTR_VAL(output)[outputpos], v); } else if (code == 'E' || formatendian[i] == PHP_BIG_ENDIAN) { @@ -754,11 +836,17 @@ PHP_FUNCTION(pack) } } + ZSTR_VAL(output)[outputpos] = '\0'; + ZSTR_LEN(output) = outputpos; + +cleanup: efree(formatcodes); efree(formatargs); efree(formatendian); - ZSTR_VAL(output)[outputpos] = '\0'; - ZSTR_LEN(output) = outputpos; + if (UNEXPECTED(conversion_failed)) { + zend_string_release(output); + RETURN_THROWS(); + } RETURN_NEW_STR(output); } /* }}} */ diff --git a/ext/standard/tests/strings/pack_float.phpt b/ext/standard/tests/strings/pack_float.phpt index 11a228d74894..b4a979b3a737 100644 --- a/ext/standard/tests/strings/pack_float.phpt +++ b/ext/standard/tests/strings/pack_float.phpt @@ -4,9 +4,6 @@ pack()/unpack(): float/double tests [null, 0.0], + 'false' => [false, 0.0], + 'true' => [true, 1.0], + 'int' => [42, 42.0], + 'float' => [42.5, 42.5], + 'numeric integer string' => ['42', 42.0], + 'numeric float string' => ['42.5', 42.5], + 'numeric scientific string' => ['1e3', 1000.0], + 'leading whitespace' => [' 42.5', 42.5], + 'trailing whitespace' => ["42.5 \t", 42.5], +]; + +foreach ($formats as $format) { + echo "$format:\n"; + foreach ($values as $name => [$value, $expected]) { + $actual = unpack($format, pack($format, $value))[1]; + echo "$name: "; + var_dump($actual === $expected); + } +} + +echo "references:\n"; +$value = 1.5; +$reference =& $value; +foreach ($formats as $format) { + $actual = unpack($format, pack($format, $reference))[1]; + echo "$format: "; + var_dump($actual === 1.5); +} + +echo "special float values:\n"; +foreach ($formats as $format) { + $infinity = unpack($format, pack($format, INF))[1]; + $nan = unpack($format, pack($format, NAN))[1]; + echo "$format: "; + var_dump($infinity === INF && is_nan($nan)); +} + +echo "signed zero integer strings:\n"; +foreach ($formats as $format) { + $actual = unpack($format, pack($format, '-0'))[1]; + echo "$format: "; + var_dump($actual === 0.0 && fdiv(1.0, $actual) === -INF); +} + +echo "leading-numeric strings:\n"; +foreach ($formats as $format) { + $diagnostic = null; + set_error_handler(static function (int $errno, string $errstr) use (&$diagnostic): bool { + $diagnostic = [$errno, $errstr]; + return true; + }); + $actual = unpack($format, pack($format, '42 with trailing data'))[1]; + restore_error_handler(); + + echo "$format: "; + var_dump($diagnostic === [E_WARNING, 'A non-numeric value encountered'] && $actual === 42.0); +} + +?> +--EXPECT-- +f: +null: bool(true) +false: bool(true) +true: bool(true) +int: bool(true) +float: bool(true) +numeric integer string: bool(true) +numeric float string: bool(true) +numeric scientific string: bool(true) +leading whitespace: bool(true) +trailing whitespace: bool(true) +g: +null: bool(true) +false: bool(true) +true: bool(true) +int: bool(true) +float: bool(true) +numeric integer string: bool(true) +numeric float string: bool(true) +numeric scientific string: bool(true) +leading whitespace: bool(true) +trailing whitespace: bool(true) +G: +null: bool(true) +false: bool(true) +true: bool(true) +int: bool(true) +float: bool(true) +numeric integer string: bool(true) +numeric float string: bool(true) +numeric scientific string: bool(true) +leading whitespace: bool(true) +trailing whitespace: bool(true) +d: +null: bool(true) +false: bool(true) +true: bool(true) +int: bool(true) +float: bool(true) +numeric integer string: bool(true) +numeric float string: bool(true) +numeric scientific string: bool(true) +leading whitespace: bool(true) +trailing whitespace: bool(true) +e: +null: bool(true) +false: bool(true) +true: bool(true) +int: bool(true) +float: bool(true) +numeric integer string: bool(true) +numeric float string: bool(true) +numeric scientific string: bool(true) +leading whitespace: bool(true) +trailing whitespace: bool(true) +E: +null: bool(true) +false: bool(true) +true: bool(true) +int: bool(true) +float: bool(true) +numeric integer string: bool(true) +numeric float string: bool(true) +numeric scientific string: bool(true) +leading whitespace: bool(true) +trailing whitespace: bool(true) +references: +f: bool(true) +g: bool(true) +G: bool(true) +d: bool(true) +e: bool(true) +E: bool(true) +special float values: +f: bool(true) +g: bool(true) +G: bool(true) +d: bool(true) +e: bool(true) +E: bool(true) +signed zero integer strings: +f: bool(true) +g: bool(true) +G: bool(true) +d: bool(true) +e: bool(true) +E: bool(true) +leading-numeric strings: +f: bool(true) +g: bool(true) +G: bool(true) +d: bool(true) +e: bool(true) +E: bool(true) diff --git a/ext/standard/tests/strings/pack_float_conversion_error.phpt b/ext/standard/tests/strings/pack_float_conversion_error.phpt new file mode 100644 index 000000000000..d54509f7e02f --- /dev/null +++ b/ext/standard/tests/strings/pack_float_conversion_error.phpt @@ -0,0 +1,162 @@ +--TEST-- +pack() float and double value conversion errors +--FILE-- + ['', 'string'], + 'whitespace string' => [' ', 'string'], + 'non-numeric string' => ['not numeric', 'string'], + 'INF string' => ['INF', 'string'], + 'NAN string' => ['NAN', 'string'], + 'empty array' => [[], 'array'], + 'non-empty array' => [[1], 'array'], + 'object' => [new stdClass(), 'stdClass'], + 'resource' => [$resource, 'resource'], +]; + +foreach ($formats as $format) { + echo "$format:\n"; + foreach ($invalidValues as $name => [$value, $type]) { + echo "$name: "; + try { + pack($format, $value); + echo "accepted\n"; + } catch (Throwable $e) { + $expected = "pack(): Argument #2 must be of type float for format code '$format', $type given"; + var_dump($e instanceof TypeError && $e->getMessage() === $expected); + } + } +} + +echo "invalid reference:\n"; +$value = []; +$reference =& $value; +foreach ($formats as $format) { + echo "$format: "; + try { + pack($format, $reference); + echo "accepted\n"; + } catch (Throwable $e) { + $expected = "pack(): Argument #2 must be of type float for format code '$format', array given"; + var_dump($e instanceof TypeError && $e->getMessage() === $expected); + } +} + +echo "argument numbers:\n"; +$cases = [ + ['f2', [1.0, []], 3, 'f'], + ['d*', [1.0, []], 3, 'd'], + ['C2g', [1, 2, []], 4, 'g'], + ['f2d2', [1.0, 2.0, 3.0, []], 5, 'd'], +]; +foreach ($cases as [$format, $arguments, $argumentNumber, $valueFormat]) { + echo "$format: "; + try { + pack($format, ...$arguments); + echo "accepted\n"; + } catch (Throwable $e) { + $expected = "pack(): Argument #$argumentNumber must be of type float " + . "for format code '$valueFormat', array given"; + var_dump($e instanceof TypeError && $e->getMessage() === $expected); + } +} + +echo "warning converted to exception:\n"; +set_error_handler(static function (int $errno, string $errstr): never { + throw new Exception($errstr); +}); +foreach ($formats as $format) { + try { + pack($format, '42 with trailing data'); + echo "$format: accepted\n"; + } catch (Throwable $e) { + echo "$format: ", $e::class, ': ', $e->getMessage(), "\n"; + } +} +restore_error_handler(); +fclose($resource); + +?> +--EXPECT-- +f: +empty string: bool(true) +whitespace string: bool(true) +non-numeric string: bool(true) +INF string: bool(true) +NAN string: bool(true) +empty array: bool(true) +non-empty array: bool(true) +object: bool(true) +resource: bool(true) +g: +empty string: bool(true) +whitespace string: bool(true) +non-numeric string: bool(true) +INF string: bool(true) +NAN string: bool(true) +empty array: bool(true) +non-empty array: bool(true) +object: bool(true) +resource: bool(true) +G: +empty string: bool(true) +whitespace string: bool(true) +non-numeric string: bool(true) +INF string: bool(true) +NAN string: bool(true) +empty array: bool(true) +non-empty array: bool(true) +object: bool(true) +resource: bool(true) +d: +empty string: bool(true) +whitespace string: bool(true) +non-numeric string: bool(true) +INF string: bool(true) +NAN string: bool(true) +empty array: bool(true) +non-empty array: bool(true) +object: bool(true) +resource: bool(true) +e: +empty string: bool(true) +whitespace string: bool(true) +non-numeric string: bool(true) +INF string: bool(true) +NAN string: bool(true) +empty array: bool(true) +non-empty array: bool(true) +object: bool(true) +resource: bool(true) +E: +empty string: bool(true) +whitespace string: bool(true) +non-numeric string: bool(true) +INF string: bool(true) +NAN string: bool(true) +empty array: bool(true) +non-empty array: bool(true) +object: bool(true) +resource: bool(true) +invalid reference: +f: bool(true) +g: bool(true) +G: bool(true) +d: bool(true) +e: bool(true) +E: bool(true) +argument numbers: +f2: bool(true) +d*: bool(true) +C2g: bool(true) +f2d2: bool(true) +warning converted to exception: +f: Exception: A non-numeric value encountered +g: Exception: A non-numeric value encountered +G: Exception: A non-numeric value encountered +d: Exception: A non-numeric value encountered +e: Exception: A non-numeric value encountered +E: Exception: A non-numeric value encountered diff --git a/ext/standard/tests/strings/pack_integer_conversion.phpt b/ext/standard/tests/strings/pack_integer_conversion.phpt new file mode 100644 index 000000000000..0b52f1d0c64a --- /dev/null +++ b/ext/standard/tests/strings/pack_integer_conversion.phpt @@ -0,0 +1,102 @@ +--TEST-- +pack() integer value conversions +--FILE-- += 8) { + array_push($formats, 'q', 'Q', 'J', 'P'); +} + +$values = [ + 'null' => [null, 0], + 'false' => [false, 0], + 'true' => [true, 1], + 'int' => [42, 42], + 'integral float' => [42.0, 42], + 'numeric integer string' => ['42', 42], + 'numeric float string' => ['42.0', 42], + 'numeric scientific string' => ['4.2e1', 42], + 'leading whitespace' => [' 42', 42], + 'trailing whitespace' => ["42 \t", 42], +]; + +$passed = true; +foreach ($formats as $format) { + foreach ($values as $name => [$value, $expected]) { + $actual = unpack($format, pack($format, $value))[1]; + if ($actual !== $expected) { + echo "Unexpected result for $format/$name: "; + var_dump($actual); + $passed = false; + } + } +} +echo "valid conversions: "; +var_dump($passed); + +$passed = true; +$value = 42; +$reference =& $value; +foreach ($formats as $format) { + $actual = unpack($format, pack($format, $reference))[1]; + if ($actual !== 42) { + echo "Unexpected result for $format/reference: "; + var_dump($actual); + $passed = false; + } +} +echo "references: "; +var_dump($passed); + +$precisionLossValues = [ + 'float' => [42.5, 'Implicit conversion from float 42.5 to int loses precision'], + 'float string' => ['42.5', 'Implicit conversion from float-string "42.5" to int loses precision'], +]; + +$passed = true; +foreach ($formats as $format) { + foreach ($precisionLossValues as $name => [$value, $message]) { + $diagnostic = null; + set_error_handler(static function (int $errno, string $errstr) use (&$diagnostic): bool { + $diagnostic = [$errno, $errstr]; + return true; + }); + $actual = unpack($format, pack($format, $value))[1]; + restore_error_handler(); + + if ($actual !== 42 || $diagnostic !== [E_DEPRECATED, $message]) { + echo "Unexpected precision-loss result for $format/$name: "; + var_dump($actual, $diagnostic); + $passed = false; + } + } +} +echo "precision loss: "; +var_dump($passed); + +$passed = true; +foreach ($formats as $format) { + $diagnostic = null; + set_error_handler(static function (int $errno, string $errstr) use (&$diagnostic): bool { + $diagnostic = [$errno, $errstr]; + return true; + }); + $actual = unpack($format, pack($format, '42 with trailing data'))[1]; + restore_error_handler(); + + if ($actual !== 42 || $diagnostic !== [E_WARNING, 'A non-numeric value encountered']) { + echo "Unexpected trailing-data result for $format: "; + var_dump($actual, $diagnostic); + $passed = false; + } +} +echo "trailing data: "; +var_dump($passed); + +?> +--EXPECT-- +valid conversions: bool(true) +references: bool(true) +precision loss: bool(true) +trailing data: bool(true) diff --git a/ext/standard/tests/strings/pack_integer_conversion_error.phpt b/ext/standard/tests/strings/pack_integer_conversion_error.phpt new file mode 100644 index 000000000000..753108b5b770 --- /dev/null +++ b/ext/standard/tests/strings/pack_integer_conversion_error.phpt @@ -0,0 +1,132 @@ +--TEST-- +pack() integer value conversion errors +--FILE-- += 8) { + array_push($formats, 'q', 'Q', 'J', 'P'); +} + +$passed = true; +foreach ($formats as $format) { + try { + pack($format, []); + echo "Unexpectedly accepted an array for $format\n"; + $passed = false; + } catch (Throwable $e) { + $expected = "pack(): Argument #2 must be of type int for format code '$format', array given"; + if (!$e instanceof TypeError || $e->getMessage() !== $expected) { + echo "Unexpected exception for $format: ", $e::class, ': ', $e->getMessage(), "\n"; + $passed = false; + } + } +} +echo "all formats: "; +var_dump($passed); + +$resource = fopen(__FILE__, 'r'); +$invalidValues = [ + 'empty string' => ['', 'string'], + 'whitespace string' => [' ', 'string'], + 'non-numeric string' => ['not numeric', 'string'], + 'empty array' => [[], 'array'], + 'non-empty array' => [[1], 'array'], + 'object' => [new stdClass(), 'stdClass'], + 'resource' => [$resource, 'resource'], +]; + +$passed = true; +foreach ($invalidValues as $name => [$value, $type]) { + try { + pack('i', $value); + echo "Unexpectedly accepted $name\n"; + $passed = false; + } catch (Throwable $e) { + $expected = "pack(): Argument #2 must be of type int for format code 'i', $type given"; + if (!$e instanceof TypeError || $e->getMessage() !== $expected) { + echo "Unexpected exception for $name: ", $e::class, ': ', $e->getMessage(), "\n"; + $passed = false; + } + } +} +echo "invalid values: "; +var_dump($passed); + +$passed = true; +$value = []; +$reference =& $value; +foreach ($formats as $format) { + try { + pack($format, $reference); + echo "Unexpectedly accepted an invalid reference for $format\n"; + $passed = false; + } catch (Throwable $e) { + $expected = "pack(): Argument #2 must be of type int for format code '$format', array given"; + if (!$e instanceof TypeError || $e->getMessage() !== $expected) { + echo "Unexpected exception for $format/reference: ", $e::class, ': ', $e->getMessage(), "\n"; + $passed = false; + } + } +} +echo "invalid references: "; +var_dump($passed); + +$passed = true; +$cases = [ + ['i2', [1, []], 3, 'i'], + ['i*', [1, []], 3, 'i'], + ['C2i', [1, 2, []], 4, 'i'], + ['i2l2', [1, 2, 3, []], 5, 'l'], +]; +foreach ($cases as [$format, $arguments, $argumentNumber, $valueFormat]) { + try { + pack($format, ...$arguments); + echo "Unexpectedly accepted an invalid argument for $format\n"; + $passed = false; + } catch (Throwable $e) { + $expected = "pack(): Argument #$argumentNumber must be of type int for format code '$valueFormat', array given"; + if (!$e instanceof TypeError || $e->getMessage() !== $expected) { + echo "Unexpected exception for $format: ", $e::class, ': ', $e->getMessage(), "\n"; + $passed = false; + } + } +} +echo "argument numbers: "; +var_dump($passed); + +$passed = true; +$diagnosticValues = [ + 'float' => [42.5, E_DEPRECATED, 'Implicit conversion from float 42.5 to int loses precision'], + 'float string' => ['42.5', E_DEPRECATED, 'Implicit conversion from float-string "42.5" to int loses precision'], + 'leading-numeric string' => ['42 with trailing data', E_WARNING, 'A non-numeric value encountered'], +]; +set_error_handler(static function (int $errno, string $errstr): never { + throw new Exception($errstr, $errno); +}); +foreach ($formats as $format) { + foreach ($diagnosticValues as $name => [$value, $severity, $message]) { + try { + pack($format, $value); + echo "Unexpectedly accepted $name for $format\n"; + $passed = false; + } catch (Throwable $e) { + if (!$e instanceof Exception || $e->getCode() !== $severity || $e->getMessage() !== $message) { + echo "Unexpected exception for $format/$name: ", $e::class, ': ', $e->getMessage(), "\n"; + $passed = false; + } + } + } +} +restore_error_handler(); +fclose($resource); +echo "exception propagation: "; +var_dump($passed); + +?> +--EXPECT-- +all formats: bool(true) +invalid values: bool(true) +invalid references: bool(true) +argument numbers: bool(true) +exception propagation: bool(true)