Skip to content

fix: avoid header pollution from Mersenne Twister helper macros#70

Closed
dkkoma wants to merge 1 commit intocolopl:mainfrom
dkkoma:fix/mt-macro-header-pollution
Closed

fix: avoid header pollution from Mersenne Twister helper macros#70
dkkoma wants to merge 1 commit intocolopl:mainfrom
dkkoma:fix/mt-macro-header-pollution

Conversation

@dkkoma
Copy link
Copy Markdown

@dkkoma dkkoma commented Apr 20, 2026

Summary

ext/php_colopl_bc_php70.h defined unprefixed Mersenne Twister macros
(N, M, hiBit, loBit, loBits, mixBits, twist) in a public
header. They leak into every TU that includes php_colopl_bc.h, and
when colopl_bc is statically linked into PHP alongside e.g. libsodium,
the bare N collides 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> to ext/sodium/php_libsodium.h. That pulled
libsodium's full API — including scrypt's uint64_t N, uint32_t r, ...
declaration — into internal_functions_cli.c during static builds.

Error example

/usr/src/php/ext/colopl_bc/php_colopl_bc_php70.h:116:42:
  error: expected ')' before numeric constant
  116 | #define N                               (624)
/usr/include/sodium/crypto_pwhash_scryptsalsa208sha256.h:106:55:
  error: expected ';', ',' or ')' before 'uint32_t'
  106 | uint64_t N, uint32_t r, uint32_t p,

Reproduced on PHP 8.5.5 with --enable-colopl_bc --with-sodium (static
build). Shared builds (--enable-colopl_bc=shared) and PHP <= 8.3 are
unaffected.

Fix

  • Expose only the state-vector length as COLOPL_BC_MT_N in the header
    (required by the module-globals struct).
  • Move the remaining MT helpers to ext/colopl_bc_php70.c as
    implementation details.
  • Inside the .c file, keep upstream short names (N, M, twist, ...)
    via #define N COLOPL_BC_MT_N, matching php-src's historical
    ext/standard/mt_rand.c style.

Test plan

  • make test passes (37 passed / 0 failed; 9 skipped are ZTS-only)
  • tests/php70/mt_rand_compatibility.phpt — bit-exact MT output
    unchanged
  • PHP 8.5.5 static build with --with-sodium reproduces the failure
    on main and succeeds on this branch (verified in a clean
    php:8.5-cli-trixie Docker container; resulting binary loads both
    colopl_bc and sodium)

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 20, 2026 00:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 N MT state-size macro usage with a prefixed COLOPL_BC_MT_N constant for the module globals state vector.
  • Move MT helper macros (M, hiBit, loBit, mixBits, twist, etc.) out of the public header and into ext/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.

Comment thread ext/colopl_bc_php70.c
Comment on lines +145 to +153
#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))

Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
#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);
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

@zeriyoshi zeriyoshi Apr 20, 2026

Choose a reason for hiding this comment

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

@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() ?

@zeriyoshi zeriyoshi mentioned this pull request Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants