fix: avoid header pollution from Mersenne Twister helper macros#70
fix: avoid header pollution from Mersenne Twister helper macros#70dkkoma wants to merge 1 commit intocolopl:mainfrom
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR prevents global macro name collisions by removing unprefixed Mersenne Twister helper macros from a public header and keeping MT implementation helpers private to the PHP 7.0 compatibility C file, fixing static-build conflicts (notably with libsodium on PHP 8.4+).
Changes:
- Replace public
NMT state-size macro usage with a prefixedCOLOPL_BC_MT_Nconstant for the module globals state vector. - Move MT helper macros (
M,hiBit,loBit,mixBits,twist, etc.) out of the public header and intoext/colopl_bc_php70.c.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| ext/php_colopl_bc_php70.h | Removes unprefixed MT helper macros; exports only COLOPL_BC_MT_N. |
| ext/php_colopl_bc.h | Updates module globals MT state array size to use COLOPL_BC_MT_N. |
| ext/colopl_bc_php70.c | Reintroduces MT helper macros as implementation-local definitions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #define N COLOPL_BC_MT_N /* length of state vector */ | ||
| #define M (397) /* a period parameter */ | ||
| #define hiBit(u) ((u) & 0x80000000U) /* mask all but highest bit of u */ | ||
| #define loBit(u) ((u) & 0x00000001U) /* mask all but lowest bit of u */ | ||
| #define loBits(u) ((u) & 0x7FFFFFFFU) /* mask the highest bit of u */ | ||
| #define mixBits(u, v) (hiBit(u)|loBits(v)) /* move hi bit of u to hi bit of v */ | ||
|
|
||
| #define twist(m,u,v) (m ^ (mixBits(u,v)>>1) ^ ((uint32_t)(-(uint32_t)(loBit(u))) & 0x9908b0dfU)) | ||
|
|
There was a problem hiding this comment.
These helper macros (N, M, hiBit/loBit/loBits/mixBits, twist) are now TU-local, but they remain defined for the rest of this .c file. To avoid accidental macro expansion/collisions in the later sections of the file (array.c/string.c) and to keep the scope tight, undefine them after the MT implementation (e.g., right before the /* array.c */ section).
| #define N COLOPL_BC_MT_N /* length of state vector */ | |
| #define M (397) /* a period parameter */ | |
| #define hiBit(u) ((u) & 0x80000000U) /* mask all but highest bit of u */ | |
| #define loBit(u) ((u) & 0x00000001U) /* mask all but lowest bit of u */ | |
| #define loBits(u) ((u) & 0x7FFFFFFFU) /* mask the highest bit of u */ | |
| #define mixBits(u, v) (hiBit(u)|loBits(v)) /* move hi bit of u to hi bit of v */ | |
| #define twist(m,u,v) (m ^ (mixBits(u,v)>>1) ^ ((uint32_t)(-(uint32_t)(loBit(u))) & 0x9908b0dfU)) | |
| enum { | |
| N = COLOPL_BC_MT_N, /* length of state vector */ | |
| M = 397 /* a period parameter */ | |
| }; | |
| static inline uint32_t hiBit(uint32_t u) /* mask all but highest bit of u */ | |
| { | |
| return u & 0x80000000U; | |
| } | |
| static inline uint32_t loBit(uint32_t u) /* mask all but lowest bit of u */ | |
| { | |
| return u & 0x00000001U; | |
| } | |
| static inline uint32_t loBits(uint32_t u) /* mask the highest bit of u */ | |
| { | |
| return u & 0x7FFFFFFFU; | |
| } | |
| static inline uint32_t mixBits(uint32_t u, uint32_t v) /* move hi bit of u to hi bit of v */ | |
| { | |
| return hiBit(u) | loBits(v); | |
| } | |
| static inline uint32_t twist(uint32_t m, uint32_t u, uint32_t v) | |
| { | |
| return m ^ (mixBits(u, v) >> 1) ^ ((uint32_t)(-(uint32_t)(loBit(u))) & 0x9908b0dfU); | |
| } |
There was a problem hiding this comment.
@dkkoma
Thanks! However, the twist macro is used here, so it cannot be removed. It gets called when MT19937 needs to reload, but since this only happens under very limited conditions, it appears to be slipping through the tests.
https://github.com/colopl/php-colopl_bc/blob/main/ext/colopl_bc_php70.c#L165
Since there's no real benefit to using a macro in the current C implementation, could you redefine it as an inline function with a different name, such as static inline colopl_bc_twist() ?
Summary
ext/php_colopl_bc_php70.hdefined unprefixed Mersenne Twister macros(
N,M,hiBit,loBit,loBits,mixBits,twist) in a publicheader. They leak into every TU that includes
php_colopl_bc.h, andwhen
colopl_bcis statically linked into PHP alongside e.g. libsodium,the bare
Ncollides with other extensions' declarations.The header-level collision has been a latent issue since the extension
was first imported, but it only became visible on PHP 8.4+ after
php/php-src#14515
(aae237aad5) added
#include <sodium.h>toext/sodium/php_libsodium.h. That pulledlibsodium's full API — including scrypt's
uint64_t N, uint32_t r, ...declaration — into
internal_functions_cli.cduring static builds.Error example
Reproduced on PHP 8.5.5 with
--enable-colopl_bc --with-sodium(staticbuild). Shared builds (
--enable-colopl_bc=shared) and PHP <= 8.3 areunaffected.
Fix
COLOPL_BC_MT_Nin the header(required by the module-globals struct).
ext/colopl_bc_php70.casimplementation details.
N,M,twist, ...)via
#define N COLOPL_BC_MT_N, matching php-src's historicalext/standard/mt_rand.cstyle.Test plan
make testpasses (37 passed / 0 failed; 9 skipped are ZTS-only)tests/php70/mt_rand_compatibility.phpt— bit-exact MT outputunchanged
--with-sodiumreproduces the failureon
mainand succeeds on this branch (verified in a cleanphp:8.5-cli-trixieDocker container; resulting binary loads bothcolopl_bcandsodium)🤖 Generated with Claude Code