@@ -882,8 +882,23 @@ static zend_always_inline bcmath_number_obj_t *get_bcmath_number_from_zval(const
882882 return get_bcmath_number_from_obj (Z_OBJ_P (zv ));
883883}
884884
885+ /* A BcMath\Number whose constructor or __unserialize() never ran has a NULL
886+ * bc_num. This cannot happen through normal PHP (the class is final with a
887+ * custom create_object, so newInstanceWithoutConstructor is rejected and
888+ * unserialize() routes through __unserialize()), but C code may build one via
889+ * create_object directly. Every operation dereferences ->num, so guard the
890+ * entry points and throw rather than crash. */
891+ static zend_never_inline ZEND_COLD void bcmath_number_throw_uninitialized (void )
892+ {
893+ zend_throw_error (NULL , "The BcMath\\Number object has not been correctly initialized by its constructor" );
894+ }
895+
885896static zend_always_inline zend_string * bcmath_number_value_to_str (bcmath_number_obj_t * intern )
886897{
898+ if (UNEXPECTED (intern -> num == NULL )) {
899+ bcmath_number_throw_uninitialized ();
900+ return zend_empty_string ;
901+ }
887902 if (intern -> value == NULL ) {
888903 intern -> value = bc_num2str_ex (intern -> num , intern -> scale );
889904 }
@@ -921,6 +936,11 @@ static zend_object *bcmath_number_clone(zend_object *obj)
921936 bcmath_number_obj_t * original = get_bcmath_number_from_obj (obj );
922937 bcmath_number_obj_t * clone = get_bcmath_number_from_obj (bcmath_number_create (bcmath_number_ce ));
923938
939+ if (UNEXPECTED (original -> num == NULL )) {
940+ bcmath_number_throw_uninitialized ();
941+ return & clone -> std ;
942+ }
943+
924944 clone -> num = bc_copy_num (original -> num );
925945 if (original -> value ) {
926946 clone -> value = zend_string_copy (original -> value );
@@ -993,6 +1013,10 @@ static int bcmath_number_has_property(zend_object *obj, zend_string *name, int c
9931013 bcmath_number_obj_t * intern = get_bcmath_number_from_obj (obj );
9941014
9951015 if (zend_string_equals (name , ZSTR_KNOWN (ZEND_STR_VALUE ))) {
1016+ if (UNEXPECTED (intern -> num == NULL )) {
1017+ bcmath_number_throw_uninitialized ();
1018+ return 0 ;
1019+ }
9961020 return !bc_is_zero (intern -> num );
9971021 }
9981022
@@ -1007,6 +1031,14 @@ static zend_result bcmath_number_cast_object(zend_object *obj, zval *ret, int ty
10071031{
10081032 if (type == _IS_BOOL ) {
10091033 bcmath_number_obj_t * intern = get_bcmath_number_from_obj (obj );
1034+ if (UNEXPECTED (intern -> num == NULL )) {
1035+ /* Return SUCCESS with a placeholder so the engine does not add its
1036+ * own "could not be converted to bool" error on top of ours; the
1037+ * pending exception propagates and the value is discarded. */
1038+ bcmath_number_throw_uninitialized ();
1039+ ZVAL_FALSE (ret );
1040+ return SUCCESS ;
1041+ }
10101042 ZVAL_BOOL (ret , !bc_is_zero (intern -> num ));
10111043 return SUCCESS ;
10121044 }
@@ -1212,6 +1244,10 @@ static zend_result bc_num_from_obj_or_str_or_long(
12121244{
12131245 if (obj ) {
12141246 const bcmath_number_obj_t * intern = get_bcmath_number_from_obj (obj );
1247+ if (UNEXPECTED (intern -> num == NULL )) {
1248+ bcmath_number_throw_uninitialized ();
1249+ return FAILURE ;
1250+ }
12151251 * num = intern -> num ;
12161252 if (full_scale ) {
12171253 * full_scale = intern -> scale ;
@@ -1259,11 +1295,15 @@ static zend_result bcmath_number_do_operation(uint8_t opcode, zval *ret_val, zva
12591295 size_t n1_full_scale ;
12601296 size_t n2_full_scale ;
12611297 if (UNEXPECTED (bc_num_from_obj_or_str_or_long (& n1 , & n1_full_scale , obj1 , str1 , lval1 ) == FAILURE )) {
1262- zend_value_error ("Left string operand cannot be converted to BcMath\\Number" );
1298+ if (!EG (exception )) {
1299+ zend_value_error ("Left string operand cannot be converted to BcMath\\Number" );
1300+ }
12631301 goto fail ;
12641302 }
12651303 if (UNEXPECTED (bc_num_from_obj_or_str_or_long (& n2 , & n2_full_scale , obj2 , str2 , lval2 ) == FAILURE )) {
1266- zend_value_error ("Right string operand cannot be converted to BcMath\\Number" );
1304+ if (!EG (exception )) {
1305+ zend_value_error ("Right string operand cannot be converted to BcMath\\Number" );
1306+ }
12671307 goto fail ;
12681308 }
12691309
@@ -1394,7 +1434,9 @@ static zend_always_inline zend_result bc_num_from_obj_or_str_or_long_with_err(
13941434{
13951435 size_t full_scale = 0 ;
13961436 if (UNEXPECTED (bc_num_from_obj_or_str_or_long (num , & full_scale , obj , str , lval ) == FAILURE )) {
1397- zend_argument_value_error (arg_num , "is not well-formed" );
1437+ if (!EG (exception )) {
1438+ zend_argument_value_error (arg_num , "is not well-formed" );
1439+ }
13981440 return FAILURE ;
13991441 }
14001442 if (UNEXPECTED (CHECK_SCALE_OVERFLOW (full_scale ))) {
@@ -1459,6 +1501,10 @@ static void bcmath_number_calc_method(INTERNAL_FUNCTION_PARAMETERS, uint8_t opco
14591501 bc_num ret = NULL ;
14601502 size_t scale = scale_lval ;
14611503 bcmath_number_obj_t * intern = get_bcmath_number_from_zval (ZEND_THIS );
1504+ if (UNEXPECTED (intern -> num == NULL )) {
1505+ bcmath_number_throw_uninitialized ();
1506+ goto fail ;
1507+ }
14621508
14631509 switch (opcode ) {
14641510 case ZEND_ADD :
@@ -1561,6 +1607,10 @@ PHP_METHOD(BcMath_Number, divmod)
15611607 bc_num rem = NULL ;
15621608 size_t scale = scale_lval ;
15631609 bcmath_number_obj_t * intern = get_bcmath_number_from_zval (ZEND_THIS );
1610+ if (UNEXPECTED (intern -> num == NULL )) {
1611+ bcmath_number_throw_uninitialized ();
1612+ goto fail ;
1613+ }
15641614
15651615 if (scale_is_null ) {
15661616 scale = MAX (intern -> scale , num_full_scale );
@@ -1626,6 +1676,10 @@ PHP_METHOD(BcMath_Number, powmod)
16261676 }
16271677
16281678 bcmath_number_obj_t * intern = get_bcmath_number_from_zval (ZEND_THIS );
1679+ if (UNEXPECTED (intern -> num == NULL )) {
1680+ bcmath_number_throw_uninitialized ();
1681+ goto cleanup ;
1682+ }
16291683 bc_num ret = NULL ;
16301684 size_t scale = scale_lval ;
16311685 raise_mod_status status = bc_raisemod (intern -> num , exponent_num , modulus_num , & ret , scale );
@@ -1687,6 +1741,10 @@ PHP_METHOD(BcMath_Number, sqrt)
16871741 }
16881742
16891743 bcmath_number_obj_t * intern = get_bcmath_number_from_zval (ZEND_THIS );
1744+ if (UNEXPECTED (intern -> num == NULL )) {
1745+ bcmath_number_throw_uninitialized ();
1746+ RETURN_THROWS ();
1747+ }
16901748
16911749 size_t scale ;
16921750 if (scale_is_null ) {
@@ -1742,6 +1800,10 @@ PHP_METHOD(BcMath_Number, compare)
17421800
17431801 size_t scale ;
17441802 bcmath_number_obj_t * intern = get_bcmath_number_from_zval (ZEND_THIS );
1803+ if (UNEXPECTED (intern -> num == NULL )) {
1804+ bcmath_number_throw_uninitialized ();
1805+ goto fail ;
1806+ }
17451807 if (scale_is_null ) {
17461808 scale = MAX (intern -> num -> n_scale , num -> n_scale );
17471809 } else {
@@ -1766,6 +1828,10 @@ static void bcmath_number_floor_or_ceil(INTERNAL_FUNCTION_PARAMETERS, bool is_fl
17661828 ZEND_PARSE_PARAMETERS_NONE ();
17671829
17681830 bcmath_number_obj_t * intern = get_bcmath_number_from_zval (ZEND_THIS );
1831+ if (UNEXPECTED (intern -> num == NULL )) {
1832+ bcmath_number_throw_uninitialized ();
1833+ RETURN_THROWS ();
1834+ }
17691835
17701836 bc_num ret = bc_floor_or_ceil (intern -> num , is_floor );
17711837
@@ -1811,6 +1877,10 @@ PHP_METHOD(BcMath_Number, round)
18111877 }
18121878
18131879 bcmath_number_obj_t * intern = get_bcmath_number_from_zval (ZEND_THIS );
1880+ if (UNEXPECTED (intern -> num == NULL )) {
1881+ bcmath_number_throw_uninitialized ();
1882+ RETURN_THROWS ();
1883+ }
18141884
18151885 bc_num ret = NULL ;
18161886 size_t scale = bc_round (intern -> num , precision , rounding_mode , & ret );
0 commit comments