Skip to content

Commit ca0c8ad

Browse files
authored
Merge branch 'main' into dependabot/github_actions/shivammathur/setup-php-2.37.2
2 parents d4227c5 + 47b3f6f commit ca0c8ad

4 files changed

Lines changed: 60 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ jobs:
9797
run: |
9898
# scale_ini test skipped: bcmath.scale INI setting ignored without native extension
9999
# gh20006 test skipped: requires BcMath\Number OOP API (not supported by polyfill)
100-
docker run --rm -v $PWD:/app bcmath-phpt-test:${{ matrix.php-version }} --skip bcdivmod,bug54598,bug72093,bug75178,gh15968,gh16262,scale_ini,bcmul_check_overflow,bug78878,bcpow_error1,bcpow_error2,bcpow_error3,bcpowmod_error,bcpowmod_with_mod_1,bcpowmod_zero_modulus,bcround_all,bcround_away_from_zero,bcround_ceiling,bcround_early_return,bcround_floor,bcround_toward_zero,bcscale_variation003,bcdivmod_by_zero,gh20006
100+
# bcround_precision_bounds test skipped: requires BcMath\Number OOP API (not supported by polyfill)
101+
docker run --rm -v $PWD:/app bcmath-phpt-test:${{ matrix.php-version }} --skip bcdivmod,bug54598,bug72093,bug75178,gh15968,gh16262,scale_ini,bcmul_check_overflow,bug78878,bcpow_error1,bcpow_error2,bcpow_error3,bcpowmod_error,bcpowmod_with_mod_1,bcpowmod_zero_modulus,bcround_all,bcround_away_from_zero,bcround_ceiling,bcround_early_return,bcround_floor,bcround_toward_zero,bcscale_variation003,bcdivmod_by_zero,gh20006,bcround_precision_bounds
101102
strategy:
102103
fail-fast: false
103104
matrix:

phpstan.neon.dist

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ parameters:
4242
identifier: function.impossibleType
4343
path: tests/BCMathTest.php
4444
message: "#Call to function in_array\\(\\) with arguments RoundingMode, array\\{.*\\} and true will always evaluate to false\\.#"
45+
# PHPStan binds `\RoundingMode` to Rector's scoped polyfill class (not an
46+
# enum), so `enum_exists('RoundingMode')` is statically inferred as always
47+
# false. At runtime our enum polyfill in lib/RoundingMode.php is the one in
48+
# use, so these guards are legitimate. Same static-analysis artifact as above.
49+
-
50+
identifier: function.impossibleType
51+
message: "#Call to function enum_exists\\(\\) with 'RoundingMode' will always evaluate to false\\.#"
52+
paths:
53+
- src/BCMath.php
54+
- tests/BCMathTest.php
55+
- tests/fixtures/clean-load.php
4556
-
4657
identifier: encapsedStringPart.nonString
4758
path: tests/BCMathTest.php

src/BCMath.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,16 @@ public static function ceil(string $num): string
10121012
*/
10131013
public static function round(string $num, int $precision = 0, $mode = PHP_ROUND_HALF_UP): string
10141014
{
1015+
// PHP's native bcround() (8.4+) rejects a $precision above INT_MAX before
1016+
// performing any computation. Without this guard a huge precision triggers
1017+
// an out-of-memory fatal via str_repeat() in bcroundHelper(). Mirror
1018+
// php-src's bcmath_check_precision(): only the upper bound overflows int.
1019+
if ($precision > 2147483647) {
1020+
throw new \ValueError(
1021+
'bcround(): Argument #2 ($precision) must be between '.PHP_INT_MIN.' and 2147483647'
1022+
);
1023+
}
1024+
10151025
self::validateNumberString($num, 'bcround', 1, 'num');
10161026

10171027
if (!is_numeric($num)) {

tests/BCMathTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,43 @@ public function testRoundNegativeScale(): void
826826
}
827827
}
828828

829+
/**
830+
* bcround() must reject a $precision above INT_MAX with a ValueError,
831+
* mirroring PHP 8.4's native behaviour. Previously such values triggered
832+
* an out-of-memory fatal via str_repeat() (php-src bcround_precision_bounds).
833+
*/
834+
public function testRoundPrecisionAboveIntMaxThrows(): void
835+
{
836+
if (PHP_INT_SIZE !== 8) {
837+
$this->markTestSkipped('Requires a 64-bit platform where PHP_INT_MAX exceeds INT_MAX.');
838+
}
839+
840+
$expectedMessage = 'bcround(): Argument #2 ($precision) must be between '.PHP_INT_MIN.' and 2147483647';
841+
842+
foreach ([PHP_INT_MAX, 2147483648] as $precision) {
843+
$caught = null;
844+
845+
try {
846+
BCMath::round('1', $precision);
847+
} catch (\ValueError $e) {
848+
$caught = $e;
849+
}
850+
$this->assertInstanceOf(
851+
\ValueError::class,
852+
$caught,
853+
"Expected ValueError for precision={$precision}"
854+
);
855+
$this->assertSame($expectedMessage, $caught->getMessage());
856+
}
857+
858+
// Note: we intentionally do not cross-check against the native bcround(),
859+
// because the precision bounds guard is a php-src master (8.6) addition and
860+
// released bcmath (e.g. 8.5) still OOMs on such precision values.
861+
862+
// A normal in-range precision must still work and not be over-rejected.
863+
$this->assertSame('1.00', BCMath::round('1', 2));
864+
}
865+
829866
/**
830867
* Test sqrt bug reproduction cases.
831868
*

0 commit comments

Comments
 (0)