Skip to content

Add ZEND_IN_RANGE_OF() and deprecate the zend_long/size_t vs. int range check macros - #23688

Open
marc-mabe wants to merge 3 commits into
php:masterfrom
marc-mabe:fix-zend-long-uint-ovfl
Open

marc-mabe wants to merge 3 commits into
php:masterfrom
marc-mabe:fix-zend-long-uint-ovfl

Conversation

@marc-mabe

@marc-mabe marc-mabe commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Add ZEND_IN_RANGE_OF() and deprecate the zend_long/size_t vs. int range check macros

It adds a generic range check for any integer type, migrates every caller of the old range check macros to it and deprecates them. It deprecates but otherwise does not change the old macros.

The bug fixes found along the way are separate PRs against the lowest supported branch. They should be merged first; afterwards this PR gets rebased (tests and NEWS entries drop out of it)

PR Target Fix
#24778 PHP-8.4 ZEND_LONG_UINT_OVFL() accepting negative values on 32-bit platforms (negative mbstring.regex_stack_limit/regex_retry_limit, negative codepage for sapi_windows_cp_set()/sapi_windows_cp_conv())
#24779 PHP-8.6 Test for Phar::buildFromIterator() with a negative getMTime() on 32-bit (fixed by the PR above once merged up; the check only exists since 8.6)
#24781 PHP-8.4 IntlDateFormatter parse functions truncating an $offset below INT_MIN
#24780 PHP-8.4 imagetruecolortopalette() error message for $num_colors

ZEND_IN_RANGE_OF(x, T)

Whether the value of the integer expression x is representable in the integer type T:

if (!ZEND_IN_RANGE_OF(id, DWORD)) {
	zend_argument_value_error(1, "must be between 0 and %u", MAXDWORD);
	RETURN_THROWS();
}
  • x and T may be any signed or unsigned integer types not wider than intmax_t; anything else is a compile error (static assertion).
  • x is evaluated exactly once.
  • C: _Generic on the promoted type of x, comparing in intmax_t or uintmax_t.
  • C++ (ext/intl): g++ and MSVC have no _Generic, so a function template deduces the type of x there.

Also added: ZEND_TYPE_MIN(T) and ZEND_TYPE_MAX(T) as integer constant expressions, for types without a constant of their own.

@alexdowad

Copy link
Copy Markdown
Contributor

LGTM. @youkidearitai?

@marc-mabe
marc-mabe changed the base branch from master to PHP-8.6 September 23, 2026 14:03
@marc-mabe

Copy link
Copy Markdown
Contributor Author

@bukka @LamentXU123 Is this fine for PHP-8.6, given it's a bug fix with internals BC break - or should this target master?

@youkidearitai youkidearitai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry for late.
My side looks good to me.

@LamentXU123 LamentXU123 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my perspective, I'd suggest to preserve the original macro sementics, and mergeup from 8.4 as a bugfix. And, merge the new macro changes (which cause the internal breaks) to master.
wdyt? @youkidearitai

@youkidearitai

Copy link
Copy Markdown
Contributor

@LamentXU123 I see.
I would suggest to PHP-8.4 branch too.

@LamentXU123

LamentXU123 commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

@youkidearitai Would you prefer me to create 4 seperate commit about this?
a. mbstring fix from 8.4
b. Windows codepage fixes from 8.4
c. phar fix from 8.6
d. standard cleanup to the macro directly to master
wdyt?
also /cc @arnaud-lb

@arnaud-lb arnaud-lb left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should target 8.4 with a minimal version of this PR (no behavior change for ZEND_LONG_UINT_OVFL(), less new macros).

Something I don't like about the existing macros is that they are limited to a small set of types, so in a few cases we check the wrong type (unsigned int instead of DWORD or uint32_t), relying on assumption that they are probably equal in practice.

As a follow up we should refactor to just one generic macro that accepts any integer type, in master:

ZEND_IN_RANGE_OF(value, DWORD);
#define ZEND_TYPE_IS_SIGNED(T)  ((T)-1 < 0)

#define ZEND_EXPR_IS_SIGNED(x)  (0 * (x) - 1 < 0)

#define ZEND_TYPE_MAX(T) \
    (ZEND_TYPE_IS_SIGNED(T) \
        ? (((((T)1 << (sizeof(T) * CHAR_BIT - 2)) - 1) * 2) + 1) \
        : (T)-1)

#define ZEND_TYPE_MIN(T) \
    (ZEND_TYPE_IS_SIGNED(T) ? -ZEND_TYPE_MAX(T) - 1 : (T)0)

#define ZEND_IN_RANGE_OF(x, T) \
    (ZEND_TYPE_IS_SIGNED(T) \
        ? ((uintmax_t)ZEND_TYPE_MAX(T) <= INTMAX_MAX \
            ? (intmax_t)(x) >= (intmax_t)ZEND_TYPE_MIN(T) \
              && (intmax_t)(x) <= (intmax_t)ZEND_TYPE_MAX(T) \
            : (x) >= 0) /* T is a uintmax_t-wide unsigned type */ \
        : (uintmax_t)(x) <= (uintmax_t)ZEND_TYPE_MAX(T))

Comment thread Zend/zend_range_check.h Outdated
# define ZEND_LONG_EXCEEDS_INT(zlong) UNEXPECTED(ZEND_LONG_INT_OVFL(zlong) || ZEND_LONG_INT_UDFL(zlong))
# define ZEND_LONG_UINT_OVFL(zlong) UNEXPECTED((zlong) < 0 || (zlong) > (zend_long)UINT_MAX)
# define ZEND_LONG_EXCEEDS_INT(zlong) UNEXPECTED((zlong) < (zend_long)INT_MIN || (zlong) > (zend_long)INT_MAX)
# define ZEND_LONG_UINT_OVFL(zlong) UNEXPECTED((zlong) > (zend_long)UINT_MAX)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep the (zlong) < 0 check in case some external code relies on this behavior. All uses of ZEND_LONG_UINT_OVFL() in php-src relied on this.

Comment thread ext/phar/phar_object.c
@marc-mabe

Copy link
Copy Markdown
Contributor Author

Hi @arnaud-lb,

nice idea actually - This can be used for zend_long against any integer type 👍
Your version isn't fully correct:

  • The inner ?: sits in the wrong branch: for a signed T, (uintmax_t)ZEND_TYPE_MAX(T) <= INTMAX_MAX is always true, so the (x) >= 0 branch ("T is a uintmax_t-wide unsigned type") is dead code.
  • Negative values are accepted for unsigned types as wide as uintmax_t, because (uintmax_t)-1 == UINTMAX_MAX: ZEND_IN_RANGE_OF(-1, uint64_t) and ZEND_IN_RANGE_OF(-1, size_t) are true.
  • Large unsigned values wrap into signed ranges: (intmax_t)(x) of e.g. SIZE_MAX is -1, so ZEND_IN_RANGE_OF(SIZE_MAX, int) is true.
  • ZEND_EXPR_IS_SIGNED() is never used, and it is what is needed to fix the two cases above.
  • x is evaluated twice in the signed branch (>= MIN && <= MAX), so ZEND_IN_RANGE_OF(func(), T) calls func() twice.
  • Only the two-sided check exists, but most callers need one side only: they report the lower bound with their own error first (hash.c, io_poll.c, openssl, dom), or clamp each side differently (round() in math.c).

Additionally, these macros should start with ZEND_LONG_*, to make clear what is compared against T.

The following should work

/* Limits of an integer type, derived from the type itself.
   All of them are integer constant expressions. */
#define ZEND_TYPE_IS_SIGNED(T) ((T)-1 < (T)0)
#define ZEND_TYPE_MAX(T) (ZEND_TYPE_IS_SIGNED(T) \
	? (((((T)1 << (sizeof(T) * CHAR_BIT - 2)) - 1) * 2) + 1) \
	: (T)-1)
#define ZEND_TYPE_MIN(T) (ZEND_TYPE_IS_SIGNED(T) ? -ZEND_TYPE_MAX(T) - 1 : (T)0)

/* zend_long vs. any integer type T.

   Every ?: below is decided by T alone, so it is folded at compile time,
   even without optimization. Where a check is not needed the whole macro is
   a constant 0 or 1 and zlong is not evaluated, as with the former (0)
   variants. Otherwise a single comparison remains, and only the selected
   branch evaluates zlong, so zlong is never evaluated more than once.
   A bound is only compared against when it is within the zend_long range,
   so the casts of the bound to zend_long never change its value. */

/* zlong > max of T */
#define ZEND_LONG_OVFL(zlong, T) UNEXPECTED( \
	(uintmax_t)ZEND_TYPE_MAX(T) < (uintmax_t)ZEND_LONG_MAX \
	? (zend_long)(zlong) > (zend_long)ZEND_TYPE_MAX(T) \
	: 0)

/* zlong < min of T */
#define ZEND_LONG_UDFL(zlong, T) UNEXPECTED( \
	(intmax_t)ZEND_TYPE_MIN(T) > (intmax_t)ZEND_LONG_MIN \
	? (zend_long)(zlong) < (zend_long)ZEND_TYPE_MIN(T) \
	: 0)

/* min of T <= zlong <= max of T
   With both bounds needed, a single unsigned comparison of the distance
   from min against the width of the range checks both. */
#define ZEND_LONG_FITS(zlong, T) EXPECTED( \
	(intmax_t)ZEND_TYPE_MIN(T) > (intmax_t)ZEND_LONG_MIN \
	? ((uintmax_t)ZEND_TYPE_MAX(T) < (uintmax_t)ZEND_LONG_MAX \
		? (zend_ulong)(zlong) - (zend_ulong)(zend_long)ZEND_TYPE_MIN(T) \
			<= (zend_ulong)ZEND_TYPE_MAX(T) - (zend_ulong)(zend_long)ZEND_TYPE_MIN(T) \
		: (zend_long)(zlong) >= (zend_long)ZEND_TYPE_MIN(T)) \
	: ((uintmax_t)ZEND_TYPE_MAX(T) < (uintmax_t)ZEND_LONG_MAX \
		? (zend_long)(zlong) <= (zend_long)ZEND_TYPE_MAX(T) \
		: 1))

/* zlong < min of T || zlong > max of T
   The exact negation of ZEND_LONG_FITS(). */
#define ZEND_LONG_EXCEEDS(zlong, T) UNEXPECTED( \
	(intmax_t)ZEND_TYPE_MIN(T) > (intmax_t)ZEND_LONG_MIN \
	? ((uintmax_t)ZEND_TYPE_MAX(T) < (uintmax_t)ZEND_LONG_MAX \
		? (zend_ulong)(zlong) - (zend_ulong)(zend_long)ZEND_TYPE_MIN(T) \
			> (zend_ulong)ZEND_TYPE_MAX(T) - (zend_ulong)(zend_long)ZEND_TYPE_MIN(T) \
		: (zend_long)(zlong) < (zend_long)ZEND_TYPE_MIN(T)) \
	: ((uintmax_t)ZEND_TYPE_MAX(T) < (uintmax_t)ZEND_LONG_MAX \
		? (zend_long)(zlong) > (zend_long)ZEND_TYPE_MAX(T) \
		: 0))
  • value gets used only once or never
  • No more instructions on -O0|1|2 compared to current macros

zend_ulong could be simple (if needed)

#define ZEND_ULONG_OVFL(zulong, T) UNEXPECTED( \
	(uintmax_t)ZEND_TYPE_MAX(T) < (uintmax_t)ZEND_ULONG_MAX \
	? (zend_ulong)(zulong) > (zend_ulong)ZEND_TYPE_MAX(T) \
	: 0)

#define ZEND_ULONG_FITS(zulong, T) EXPECTED( \
	(uintmax_t)ZEND_TYPE_MAX(T) < (uintmax_t)ZEND_ULONG_MAX \
	? (zend_ulong)(zulong) <= (zend_ulong)ZEND_TYPE_MAX(T) \
	: 1)

#define ZEND_ULONG_EXCEEDS(zulong, T) ZEND_ULONG_OVFL(zulong, T)

@arnaud-lb

Copy link
Copy Markdown
Member

Thank you for checking that code.

ZEND_EXPR_IS_SIGNED() is never used, and it is what is needed to fix the two cases above.

That was the problem: Replacing TYPE_IS_SIGNED(T) by EXPR_IS_SIGNED(x) in the first condition fixes all the issues you've found (except the fact that x is evaluated multiple times).

I didn't check this code thoroughly, however, so take this with caution.

Additionally, these macros should start with ZEND_LONG_*, to make clear what is compared against T.

I believe that the macro behaves as expected with any integer expression and type (as large as uintmax_t, so no __uint128_t), signed or unsigned. Do you see any drawbacks with that?

Only the two-sided check exists, but most callers need one side only: they report the lower bound with their own error first (hash.c, io_poll.c, openssl, dom), or clamp each side differently (round() in math.c).

Is this a functional issue or an optimization issue?

The compiler would no-op the lower-bound check after a manual bound check, so it shouldn't add overhead.

x is evaluated twice in the signed branch (>= MIN && <= MAX), so ZEND_IN_RANGE_OF(func(), T) calls func() twice.

We could probably overcome this:

static inline bool in_range_s(intmax_t v, intmax_t min, uintmax_t max) {
    if (max <= INTMAX_MAX) {
        return v >= min && v <= (intmax_t)max;
    }
    return v >= 0; /* T is a uintmax_t-wide unsigned type */
}

static inline bool in_range_u(uintmax_t v, uintmax_t max) {
    return v <= max;
}

#define ZEND_IN_RANGE_S(x, T) in_range_s((x), ZEND_TYPE_MIN(T), ZEND_TYPE_MAX(T))
#define ZEND_IN_RANGE_U(x, T) in_range_u((x), ZEND_TYPE_MAX(T))

#define ZEND_IN_RANGE_OF(x, T) _Generic((x) + 0, \
    int:                ZEND_IN_RANGE_S(x, T), \
    long:               ZEND_IN_RANGE_S(x, T), \
    long long:          ZEND_IN_RANGE_S(x, T), \
    unsigned:           ZEND_IN_RANGE_U(x, T), \
    unsigned long:      ZEND_IN_RANGE_U(x, T), \
    unsigned long long: ZEND_IN_RANGE_U(x, T))

@marc-mabe

Copy link
Copy Markdown
Contributor Author

Thank you for checking that code.

ZEND_EXPR_IS_SIGNED() is never used, and it is what is needed to fix the two cases above.

That was the problem: Replacing TYPE_IS_SIGNED(T) by EXPR_IS_SIGNED(x) in the first condition fixes all the issues you've found (except the fact that x is evaluated multiple times).

I didn't check this code thoroughly, however, so take this with caution.

Seems to work for my quick and dirty tests.

Additionally, these macros should start with ZEND_LONG_*, to make clear what is compared against T.

I believe that the macro behaves as expected with any integer expression and type (as large as uintmax_t, so no __uint128_t), signed or unsigned. Do you see any drawbacks with that?

Yea right - I somehow missed that and assumed it for zend_long only.
Maybe adding static assertion to make sure it's used for integer types not wider than intmax_t only?

Only the two-sided check exists, but most callers need one side only: they report the lower bound with their own error first (hash.c, io_poll.c, openssl, dom), or clamp each side differently (round() in math.c).

Is this a functional issue or an optimization issue?

Mostly optimization for debug build. Some cases are checking one bound only (like https://github.com/php/php-src/blob/master/ext/bcmath/bcmath.c#L160) but these could be rewritten as well.

The compiler would no-op the lower-bound check after a manual bound check, so it shouldn't add overhead.

x is evaluated twice in the signed branch (>= MIN && <= MAX), so ZEND_IN_RANGE_OF(func(), T) calls func() twice.

We could probably overcome this:

static inline bool in_range_s(intmax_t v, intmax_t min, uintmax_t max) {
    if (max <= INTMAX_MAX) {
        return v >= min && v <= (intmax_t)max;
    }
    return v >= 0; /* T is a uintmax_t-wide unsigned type */
}

static inline bool in_range_u(uintmax_t v, uintmax_t max) {
    return v <= max;
}

#define ZEND_IN_RANGE_S(x, T) in_range_s((x), ZEND_TYPE_MIN(T), ZEND_TYPE_MAX(T))
#define ZEND_IN_RANGE_U(x, T) in_range_u((x), ZEND_TYPE_MAX(T))

#define ZEND_IN_RANGE_OF(x, T) _Generic((x) + 0, \
    int:                ZEND_IN_RANGE_S(x, T), \
    long:               ZEND_IN_RANGE_S(x, T), \
    long long:          ZEND_IN_RANGE_S(x, T), \
    unsigned:           ZEND_IN_RANGE_U(x, T), \
    unsigned long:      ZEND_IN_RANGE_U(x, T), \
    unsigned long long: ZEND_IN_RANGE_U(x, T))

This should work in C11 but these macros are used in C++ as well (ext/intl) - where _Generic is not supported by g++

@arnaud-lb

Copy link
Copy Markdown
Member

Maybe adding static assertion to make sure it's used for integer types not wider than intmax_t only?

Yes I agree it would be useful

Mostly optimization for debug build

In that case I wouldn't bother, as the difference is really negligible

@marc-mabe

Copy link
Copy Markdown
Contributor Author

@arnaud-lb This PR now adds ZEND_IN_RANGE_OF, ZEND_TYPE_MAX and ZEND_TYPE_MIN for any integer type up to intmax_t. This works for C and C++ with different variants.

  • The first commit adds the new macros
  • The second commit replaces the usages of the old macros with the new once (and fixes the bugs because of that)
  • The third commit deprecates the old macros

Now, what belongs where?
For now I changed the target of this PR onto master - but as this is no longer touching the old macros, can this be targeting PHP-8.4 entirely or how should it be split?

@marc-mabe
marc-mabe force-pushed the fix-zend-long-uint-ovfl branch from 3251f2b to a4a8259 Compare October 23, 2026 17:55

@arnaud-lb arnaud-lb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, what belongs where?
For now I changed the target of this PR onto master - but as this is no longer touching the old macros, can this be targeting PHP-8.4 entirely or how should it be split?

This should target master only. For 8.4 we should commit a minimal fix (preferably before merging this PR).

Looks great otherwise.

Comment thread ext/intl/dateformat/dateformat_parse.cpp Outdated
Comment thread ext/mbstring/php_mbregex.c
@marc-mabe

Copy link
Copy Markdown
Contributor Author

@arnaud-lb
The fixes have been split into separate PRs - This PRs needs to be rebased after they are merged.

@arnaud-lb

Copy link
Copy Markdown
Member

Thank you @marc-mabe. I will give some time for code owners to review the split PRs and then will review/merge.

@marc-mabe marc-mabe changed the title Fix ZEND_LONG_UINT_OVFL() dropping the negative half, and add the long range checks Add ZEND_IN_RANGE_OF() and deprecate the zend_long/size_t vs. int range check macros Oct 28, 2026
Comment thread Zend/zend_range_check.h Outdated
* The unary + applies the integer promotions, as (x) + 0 does for C. */
extern "C++" {
template<typename T, typename X>
constexpr bool _zend_in_range_of(X x) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that s the difference with the C counterpart, that might be not obvious if you do not pay attention. One of the possible case that could be an issue is where you use the expression inside static_assert for C/C++ regardless.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might worth a noticeable comment maybe

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So your point is that static_assert(ZEND_IN_RANGE_OF(...)) works in C++ (as it's a constant expression) but not in C (error: “expression in static assertion is not constant”).

I can also replace the C inline functions with plain constant expressions, but that makes the signed variant more complex to avoid that x gets evaluated twice.

I think this should do the trick

# define _ZEND_IN_RANGE_SIGNED(x, T) ((((uintmax_t)(x) - (uintmax_t)ZEND_TYPE_MIN(T)) & ~(((uintmax_t)ZEND_TYPE_MAX(T) & INTMAX_MAX) - (uintmax_t)ZEND_TYPE_MIN(T))) == 0)

Actually, the unsigned variant would need to change as well to avoid warning comparison is always true due to limited range of data type

# define _ZEND_IN_RANGE_UNSIGNED(x, T) (((uintmax_t)(x) & ~(uintmax_t)ZEND_TYPE_MAX(T)) == 0)

@devnexen @arnaud-lb Should I change the C variant to constant expression or just add a comment to the C variant that this can not be used as constant expression?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it's fine if we can't use it in a static expression for now (I don't see a use-case where we will use ZEND_IN_RANGE_OF() with a constant value).

The only problem is that we might accidentally write code that compiles in C++ but not in C. Maybe just make the C++ version non-constexpr to avoid that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devnexen @arnaud-lb.

Updated (and rebased) the PR:
C and C++ variants now share the same inline helpers _zend_in_range_[signed|unsigned](), and the C++ template is no longer constexpr.

So ZEND_IN_RANGE_OF() now behaves the same in C and C++.

The existing range check macros each cover one pair of types, zend_long or
size_t against int or unsigned int, so a caller narrowing into a DWORD, an
uint32_t or an unsigned long has to pick the closest one and rely on the two
types being of the same width.

ZEND_IN_RANGE_OF(x, T) tells whether the value of the integer expression x
is representable in the integer type T, for any integer x and T not wider
than intmax_t, signed or unsigned. x is evaluated exactly once. In C it
dispatches on the promoted type of x with _Generic; C++ has no _Generic, so
there a function template deduces the type of x instead, which ext/intl
needs. Both call the same inline functions doing the comparison in intmax_t
or uintmax_t, so it behaves the same in C and C++, including not being a
constant expression in either.

Anything that is not an integer type, or is wider than intmax_t, fails to
compile instead of silently truncating.

ZEND_TYPE_MIN(T) and ZEND_TYPE_MAX(T), which it compares against, are
public as well: the limits of any such integer type T as integer constant
expressions, for types without a constant of their own such as time_t.
Move every caller of ZEND_LONG_INT_OVFL(), ZEND_LONG_INT_UDFL(),
ZEND_LONG_EXCEEDS_INT(), ZEND_LONG_UINT_OVFL(), ZEND_SIZE_T_INT_OVFL() and
ZEND_SIZE_T_UINT_OVFL() to ZEND_IN_RANGE_OF(). The old macros stay as they
are for code outside php-src.

Where the value is narrowed into another type than the one the old macro
checked, check that type instead: a DWORD for the codepages in win32, whose
messages now print MAXDWORD, the uint32_t the phar timestamp is stored in, and
an int32_t where intl passes the value on to ICU and where cal_to_jd() limits
the day, as their messages already say.

onig_set_retry_limit_in_match_of_match_param() takes an unsigned long, so
mbstring.regex_retry_limit is checked against that now. On LP64 a limit
beyond the range of an unsigned int is passed on instead of being dropped in
favour of oniguruma's default.

Correct a message whose bounds did not match the check: the number of
colors for imagetruecolortopalette() may be INT_MAX itself.

Callers which deliberately leave the lower bound open keep doing so:
bcmath accepts any negative precision, and in spec mode the DOM character
data methods convert a negative offset or count as an unsigned long.

This changes behaviour where ZEND_LONG_UINT_OVFL() expanded to (0), that is
wherever a zend_long is not wider than an int:
- Phar::buildFromIterator() now rejects a negative getMTime() override with
  a BadMethodCallException, as it does on 64bit.
- sapi_windows_cp_set() and sapi_windows_cp_conv() now reject a negative
  codepage with a ValueError, as they do on 64bit.
- a negative mbstring.regex_stack_limit or mbstring.regex_retry_limit is
  now ignored instead of being passed on as a huge unsigned int.
ZEND_LONG_INT_OVFL(), ZEND_LONG_INT_UDFL(), ZEND_LONG_EXCEEDS_INT(),
ZEND_LONG_UINT_OVFL(), ZEND_SIZE_T_INT_OVFL() and ZEND_SIZE_T_UINT_OVFL()
have no callers left in php-src. They stay for code outside of it, with
their behaviour unchanged, but are deprecated in favour of
ZEND_IN_RANGE_OF().

C has no way to deprecate a macro, so each of them calls the empty function
_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), marked
ZEND_ATTRIBUTE_DEPRECATED, instead. Every use then raises
-Wdeprecated-declarations naming the replacement, and the compiler points at
the macro.

The flags ZEND_LONG_CAN_OVFL_INT, ZEND_LONG_CAN_OVFL_UINT and
ZEND_SIZE_T_CAN_OVFL_UINT only serve these macros and are deprecated as
well. Being only usable in #if, they can do so in a comment only.

zend_portability.h includes zend_range_check.h last now, after
ZEND_ATTRIBUTE_DEPRECATED is defined.
@marc-mabe
marc-mabe force-pushed the fix-zend-long-uint-ovfl branch from 81bdce7 to 2b31859 Compare October 29, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants