Skip to content

refactor: C23 modernization wave 2#133

Merged
CodeLieutenant merged 4 commits into
v1.3.xfrom
refactor/c23-modernization-wave-2
May 23, 2026
Merged

refactor: C23 modernization wave 2#133
CodeLieutenant merged 4 commits into
v1.3.xfrom
refactor/c23-modernization-wave-2

Conversation

@CodeLieutenant
Copy link
Copy Markdown
Member

Summary

Continues the C++ → C23 migration: adopts the language features the C23 standard added for safety (nullptr, auto, constexpr, [[attributes]], = {}), hardens the public API with [[gnu::nonnull]] / [[gnu::pure]] / [[gnu::const]] / [[nodiscard]], and pushes more correctness into the gen_stub layer so the generated arginfo files stop being a magic-number duplicator.

Highlights

Correctness (these fix latent bugs)

  • @not-serializable on 29 native-pointer-holding classes (Cluster/Session/Statement/Future/Rows/TimestampGen/SSL/RetryPolicy/Database meta). Without this, default serialize($cluster) → unserialize(...) produces an object with no native handle; the next method call dereferences uninitialized memory. Now PHP refuses the operation explicitly.
  • typeof in SAFE_STR / SAFE_ZEND_STRING — the prior ((a) ? a : "") evaluated a twice; any side-effecting argument was UB.
  • ulong portability — removed the project-local typedef unsigned long ulong; and migrated 8 call sites to zend_ulong (the PHP-canonical type). ulong isn't POSIX and needs _BSD_SOURCE on glibc.

Stub-driven codegen

  • @cvalue for all enum-backed constants in Cassandra.stub.phpCONSISTENCY_*, VERIFY_*, BATCH_*, LOG_*, VERSION. The generated arginfo emits ZVAL_LONG(&val, CASS_CONSISTENCY_ANY) instead of ZVAL_LONG(&val, 0x0); values stay synchronized with cassandra.h automatically.
  • @not-serializable wired into 29 stubs (see above).

Attribute hardening

  • [[nodiscard, gnu::nonnull(...)]] on public *_instantiate / *_initialize APIs (DateTime, RetryPolicy, SSLOptions, plus utility headers).
  • [[gnu::const]] exception_class(CassError) — pure mapping, CSE-able.
  • [[gnu::pure]] / [[gnu::const]] on the FNV cache-key helpers.
  • PHP_SCYLLADB_CLEANUP(fn) macro wrapping __attribute__((cleanup(...))). Adopted at 10 sites in bind_argument_by_index / bind_argument_by_name — removes the CassError rc; ...; free(data); CHECK_RESULT(rc); ladder and keeps exception safety.

Language modernization

  • nullptr instead of NULL across hand-written sources (~370 sites).
  • auto (C23) where the type is spelled twice: auto self = PHP_SCYLLADB_GET_X(zv); (~370 sites across 49 files).
  • [[maybe_unused]] on parameter declarations instead of (void)param; suppressions.
  • = {} (C23) replacing = {0} at the 9 sites that had it.
  • static constexpr for true compile-time constants where the value isn't consumed by the preprocessor (FNV table, default consistency).

Dispatcher refactor

  • bind_argument_by_index / bind_argument_by_name: switch (Z_TYPE_P(value)) replaces the if-chain over primitives. One read, one jump table. Object branch unchanged.

Build / lint policy

  • .clang-tidy enables modernize-use-nullptr, modernize-use-bool-literals, modernize-macro-to-enum, plus a handful of readability-* checks now that the codebase is C23-ready.
  • cmake/TargetOptimizations.cmake: C standard bumped to c_std_23; C++ stays at C++20 for residual .cpp files until ported.
  • Dropped dead #ifdef __cplusplus / extern "C" guards in headers we own (no C++ TUs exist in src/). PHP-idiomatic BEGIN_EXTERN_C / END_EXTERN_C kept where the convention applies.

What's deliberately NOT in this PR

  • No mass zero-init of bare zval x; locals — they're immediately written by ZVAL_NULL / array_init / etc., which don't read prior state. Mass = {} would emit dead xors.
  • No new *_arginfo.h files committed — they're gitignored and regenerated by tools/gen_stub/gen_arginfo.sh at build time.
  • No @frameless-function / @compile-time-eval — apply only to global functions, of which we have none.

Test plan

Build verification was not run locally; the toolchain needs libuv + cpp-driver + a compiled PHP source tree + a running ScyllaDB. CI will exercise the full matrix. Highest-attention items:

  • cmake --preset DebugPHP8.4NTS && cmake --build out/DebugPHP8.4NTS — compiles clean across the matrix.
  • ./scripts/run-scylladb.sh && composer install && php ./vendor/bin/pest — the test suite covers the bind_argument_* paths that got the dispatcher refactor and the cleanup-attribute adoption.
  • Spot-check: var_dump(unserialize(serialize($cluster))) should now throw Serialization of 'Cassandra\DefaultCluster' is not allowed instead of producing a half-constructed object.
  • [[gnu::nonnull]] annotations may surface new compiler warnings at internal call sites where a nullptr flows in unconditionally — those are real bugs worth reviewing.

🤖 Generated with Claude Code

Continues the C++ → C23 migration with a sweep of language features,
attribute hardening, gen_stub annotations, and lint policy.

Language features
- nullptr replaces NULL throughout hand-written code (auto-generated
  *_arginfo.h files stay on NULL until upstream gen_stub catches up).
- `auto` (C23) for ~370 type-redundant locals where the macro/function
  already encodes the type: `php_scylladb_X *self = PHP_SCYLLADB_GET_X(...)`
  → `auto self = PHP_SCYLLADB_GET_X(...)`.
- `[[maybe_unused]]` replaces `(void)param;` suppressions.
- `= {}` (C23 empty initializer) replaces `= {0}`.
- `static constexpr` for true compile-time constants (FNV table, default
  consistency) where the value isn't consumed by the preprocessor.
- `typeof` + statement expression in SAFE_STR / SAFE_ZEND_STRING to avoid
  double-evaluation of side-effecting arguments.

Attribute hardening
- [[nodiscard, gnu::nonnull(...)]] on public *_instantiate / *_initialize
  APIs and other helpers with required pointer args.
- [[gnu::const]] / [[gnu::pure]] on the FNV cache-key helpers and on
  exception_class() — they're pure mappings, this lets the optimizer
  CSE repeated calls.
- PHP_SCYLLADB_CLEANUP(fn) wrapper over __attribute__((cleanup(...))) so
  RAII-style scope cleanup is one line. Adopted at 10 sites in
  DefaultSession.c's bind_argument paths (varint/decimal/collection/
  map/collection/tuple/user-type intermediaries): removes manual rc-then-
  free ladders, preserves exception safety.
- PHP_SCYLLADB_LIKELY / PHP_SCYLLADB_UNLIKELY branch-prediction macros.

Stub-level codegen improvements
- @cvalue annotations on Cassandra.stub.php's 24 enum-backed constants
  (CONSISTENCY_*, VERIFY_*, BATCH_*, LOG_*, VERSION). The generated
  arginfo now emits ZVAL_LONG(&val, CASS_CONSISTENCY_ANY) instead of a
  magic 0x0, so values stay in lockstep with the linked driver.
- @not-serializable on all 29 concrete classes that hold a raw Cass*
  pointer. Closes a UAF: default zend serialize would dehydrate via
  get_properties (no native handle), then unserialize would create a
  fresh instance whose pointer is uninitialized; the next method call
  was a crash. Now PHP throws "Serialization not allowed" explicitly.

Bind dispatcher
- bind_argument_by_index / bind_argument_by_name: replaced the primitive
  if-chain with a switch on Z_TYPE_P. Single read, compiler emits a jump
  table. Object branch unchanged (instanceof can't be switched).

Build / lint
- .clang-tidy: enable modernize-use-nullptr / -use-bool-literals /
  -macro-to-enum plus a few targeted readability checks.
- cmake/TargetOptimizations.cmake: bump C standard to c_std_23, keep
  C++20 for residual .cpp files until they're ported.
- Dropped dead extern "C" guards in headers we now own; PHP-style
  BEGIN_EXTERN_C / END_EXTERN_C kept where the convention applies.

Portability fix
- Removed `typedef unsigned long ulong;` from php_scylladb.h. `ulong` is
  not POSIX (needs _BSD_SOURCE on glibc) and zend_hash_* already takes
  zend_ulong; all 8 call sites migrated.

Hash iteration
- Tuple.c / UserTypeValue.c: switched to the key-only foreach variants
  (ZEND_HASH_FOREACH_NUM_KEY / _STR_KEY) where the value pointer was
  unused. Drops two (void)current; suppressions and dead locals.

Build verification
- Not run locally — toolchain needs libuv + cpp-driver + a compiled PHP
  source tree + a running ScyllaDB. The cleanup-attribute paths and
  gen_stub-emitted code are the highest-attention items; please run
  cmake --build out/DebugPHP8.4NTS && composer install && pest before
  merging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CodeLieutenant CodeLieutenant self-assigned this May 23, 2026
CodeLieutenant and others added 3 commits May 23, 2026 13:27
Three CI breakages, three localized fixes:

1. clang-tidy chokes on `constexpr` despite -std=c23 — its frontend
   doesn't pick up the C23 flag from the compilation database in the
   way clang(1) does. Revert the FNV constants and DEFAULT_CONSISTENCY
   to `#define`. The gain was cosmetic; revisit once the lint job's
   std flag is sorted.

2. readability-else-after-return fires as warnings-as-error on
   pre-existing `} else if (...)` after `return` chains in TypeFactory
   and elsewhere. Disable the check — fixing the call sites is a
   separate cleanup, not in scope for this PR.

3. Cassandra_descriptor.c was missing <cassandra.h> + <version.h>, so
   the new @cvalue references (CASS_CONSISTENCY_*, PHP_SCYLLADB_VERSION)
   were undeclared in that TU. Switch the descriptor template to
   include "php_scylladb.h" — the umbrella already brings every
   identifier any @cvalue might reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SAFE_STR / SAFE_ZEND_STRING used `({ typeof(a) _v = (a); _v ? _v : ""; })`
to dodge double-evaluation, but statement expressions are a GNU extension
and trip -Wpedantic on the CI compiler.

Switch to `static inline` helpers behind the same macro names:

  [[gnu::const]] static inline const char *php_scylladb_safe_str(const char *);
  [[gnu::pure]]  static inline const char *php_scylladb_safe_zend_string(const zend_string *);

Function-call evaluation evaluates the argument exactly once, so the
original safety property is preserved without leaving ISO C. Call-site
syntax `SAFE_STR(x)` / `SAFE_ZEND_STRING(x)` is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
actions/checkout@v4 rewrites submodule URLs to use its scoped
GITHUB_TOKEN as HTTPS Basic auth. For a public submodule in a
different org (arsenm/sanitizers-cmake), the token can't satisfy the
auth challenge and the clone falls through to a terminal prompt that
isn't available in CI:

    fatal: could not read Username for 'https://github.com':
           terminal prompts disabled

The submodule is 15 files (six small Find*.cmake modules + a wrapper
script + tests). The upstream hasn't seen activity in years. Vendoring
removes the cross-org auth dance entirely and makes the build work
without `--recursive`.

Changes:
- Drop .gitmodules entirely (sanitizers-cmake was the only entry).
- Detach the gitlink and add the files as tracked content under
  third-party/sanitizers-cmake/.
- Simplify CMakeLists.txt's sanitizer block — no more
  `git submodule update --init` fallback, just an existence check
  with a clear error if the vendored tree is missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CodeLieutenant CodeLieutenant merged commit 2256c68 into v1.3.x May 23, 2026
70 of 80 checks passed
@CodeLieutenant CodeLieutenant deleted the refactor/c23-modernization-wave-2 branch May 23, 2026 13:19
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.

1 participant