Skip to content

Verify pointers during functable init#1983

Merged
Dead2 merged 1 commit intodevelopfrom
functable-verify
Oct 21, 2025
Merged

Verify pointers during functable init#1983
Dead2 merged 1 commit intodevelopfrom
functable-verify

Conversation

@Dead2
Copy link
Copy Markdown
Member

@Dead2 Dead2 commented Oct 10, 2025

During init, make sure none of the functable entries are nullpointers.
This will cause zlib-ng to abort during init if it is run on a cpu that is missing both optimized and generic fallback functions, instead of running fine until such a missing function is called and potentially being missed during testing.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds runtime verification to functable initialization: init_functable now returns int, force_init and many stubs return int, assignments use FUNCTABLE_VERIFY_ASSIGN with a memory barrier, and FUNCTABLE_INIT checks force_init return; also unconditionally includes <stdio.h> in zbuild.h.

Changes

Cohort / File(s) Change summary
Function table implementation
functable.c
Converted init_functable, force_init_empty, and force_init_stub from void to int; added FUNCTABLE_VERIFY_ASSIGN and FUNCTABLE_INIT_ABORT; replaced direct function-pointer assignments with verified assignments; added a memory barrier after assignments; updated many stub functions to the new int-returning initialization/verification flow.
Function table interface / macro
functable.h
Changed struct functable_s member force_init from void (*)(void) to int (*)(void); updated FUNCTABLE_INIT to check functable.force_init() return and return Z_VERSION_ERROR when non-zero.
Build header include
zbuild.h
Added unconditional #include <stdio.h> at top and removed conditional stdio.h inclusion from within ZLIB_DEBUG blocks.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Stub as stub (e.g. crc32_stub)
    participant Functable as init_functable
    participant Verify as FUNCTABLE_VERIFY_ASSIGN
    participant Barrier as MemoryBarrier

    Stub->>Functable: call FUNCTABLE_INIT (calls force_init)
    Functable->>Functable: set safe default `ft.force_init = force_init_empty`
    Functable->>Verify: verify & assign function pointers (per-pointer)
    alt any verify fails
        Verify-->>Stub: return non-zero (propagate error)
    else all verify succeed
        Verify->>Barrier: enforce memory barrier
        Barrier-->>Stub: return Z_OK (init succeeded)
    end
Loading

Possibly related PRs

Suggested labels

Architecture

Suggested reviewers

  • nmoinvaz

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly states the main change of adding pointer verification during functable initialization, directly reflecting the core functionality introduced in this PR without unnecessary detail.
Description Check ✅ Passed The description directly relates to the changeset by explaining the runtime verification of functable entries and the resulting abort behavior on missing implementations, providing clear context for the update.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch functable-verify

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d70298 and e2c1c1d.

📒 Files selected for processing (3)
  • functable.c (2 hunks)
  • functable.h (2 hunks)
  • zbuild.h (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • zbuild.h
🧰 Additional context used
🧬 Code graph analysis (1)
functable.c (1)
adler32.c (1)
  • adler32 (26-28)
🔇 Additional comments (10)
functable.h (2)

27-27: LGTM: Function pointer signature updated for error propagation.

Changing force_init to return int enables the initialization verification workflow to propagate errors through deflateInit/inflateInit as described in the PR objectives.


48-48: No action required — inflateInit2 and deflateInit2 already return Z_VERSION_ERROR on FUNCTABLE_INIT failure.

functable.c (8)

41-50: LGTM: Verification macro enables graceful error handling.

The macro correctly checks for null pointers and returns 1 to propagate errors through init_functable()force_init_stub()FUNCTABLE_INITdeflateInit/inflateInit, allowing them to return Z_VERSION_ERROR instead of proceeding with a null pointer.

Note: The generic error message "Functable failed initialization!" was intentionally kept concise to avoid code bloat from stringifying each function name, as discussed in previous review comments.

Based on past review comments.


52-60: LGTM: Abort macro provides fail-fast for stubs without error propagation.

This macro is necessary for stub functions that have no return path to propagate errors (e.g., void-returning functions like crc32_fold_stub). Aborting with a clear message is preferable to a segmentation fault, as discussed in the PR comments.

Based on PR objectives and comments.


62-65: LGTM: Empty stub correctly signals successful initialization.

Returning 0 (success) is appropriate since this function is only called after initialization completes successfully.


70-370: LGTM: Initialization flow with verification is correctly implemented.

The function properly:

  1. Assigns force_init_empty as the success marker (line 75)
  2. Populates generic fallbacks based on build configuration (lines 77-108)
  3. Applies architecture-specific optimizations (lines 110-348)
  4. Verifies all function pointers before publishing them (lines 352-364)
  5. Issues a memory barrier for weak-order CPUs (line 367)
  6. Returns Z_OK on success (line 369)

The verification at lines 352-364 will catch any missing implementations, whether due to missing generic fallbacks or unsupported CPU features, ensuring early detection during initialization as intended by the PR.


351-351: Confirm: force_init intentionally skips verification.

Line 351 uses FUNCTABLE_ASSIGN instead of FUNCTABLE_VERIFY_ASSIGN because force_init is always set to &force_init_empty at line 75, guaranteeing it's never null. This is correct.


373-375: LGTM: Stub correctly propagates initialization result.

Returning the result of init_functable() enables the FUNCTABLE_INIT macro (functable.h line 48) to detect failures and return Z_VERSION_ERROR from deflateInit/inflateInit.


377-440: LGTM: Stubs use abort-on-failure for functions without error returns.

All stub functions correctly use FUNCTABLE_INIT_ABORT to terminate the process if initialization fails. This prevents:

  • Infinite recursion (the original critical issue)
  • Segmentation faults from calling null pointers
  • Silent data corruption from uninitialized functions

For void-returning functions (e.g., crc32_fold_stub, inflate_fast_stub, slide_hash_stub), aborting is the only viable error handling strategy since they cannot propagate errors through return values.

Based on PR objectives and past review comments.


352-364: Verify fallback coverage and build flags
All FUNCTABLE_VERIFY_ASSIGN calls in lines 352–364 are included in the generic WITH_ALL_FALLBACKS fallback block. Ensure your build configurations either define WITH_ALL_FALLBACKS by default or enable the appropriate architecture-specific flags (WITH_OPTIM, X86_/ARM_/…) so no function pointers remain uninitialized.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9ef3eb and a7e61d4.

📒 Files selected for processing (1)
  • functable.c (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
functable.c (2)
adler32.c (1)
  • adler32 (26-28)
functable.h (1)
  • adler (26-41)
🔇 Additional comments (1)
functable.c (1)

58-58: LGTM! Early initialization prevents null pointer.

Setting ft.force_init early ensures this function pointer is never null, even before architecture-specific optimizations are applied.

@Dead2 Dead2 force-pushed the functable-verify branch 2 times, most recently from d0917d6 to 73b2f01 Compare October 10, 2025 11:53
@codecov
Copy link
Copy Markdown

codecov bot commented Oct 10, 2025

Codecov Report

❌ Patch coverage is 62.50000% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.03%. Comparing base (db3f23f) to head (e2c1c1d).

Files with missing lines Patch % Lines
functable.c 62.50% 11 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           develop    #1983       +/-   ##
============================================
+ Coverage    39.33%   82.03%   +42.69%     
============================================
  Files           73      161       +88     
  Lines         7911    13748     +5837     
  Branches      1280     3149     +1869     
============================================
+ Hits          3112    11278     +8166     
+ Misses        4555     1439     -3116     
- Partials       244     1031      +787     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Dead2
Copy link
Copy Markdown
Member Author

Dead2 commented Oct 11, 2025

I figured calling abort() would not be very nice to the application linking zlib-ng.
Implemented improved failure handling for deflateinit and inflateinit.

Suggestions for how to handle other exported functions directly? Currently I guess they'll likely segfault, same as they would without this PR.
Edit: Or they get infinite recursion like coderabbitai mentions. Its "Solution 2" might be ok for those calling functions directly..?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
functable.c (1)

339-402: Inconsistent error handling across stubs.

All these stub functions call init_functable() but ignore its return value, then proceed to call the function pointer. This differs from force_init_stub (lines 335-337) which returns the initialization result.

If initialization fails, these stubs will cause infinite recursion because they don't check whether init_functable() succeeded before calling functable.<function>(...), which would still point to the stub.

This is part of the critical issue flagged in the FUNCTABLE_VERIFY_ASSIGN macro review above.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7e61d4 and 2ecf564.

📒 Files selected for processing (3)
  • functable.c (2 hunks)
  • functable.h (2 hunks)
  • zbuild.h (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • zbuild.h
🧰 Additional context used
🧬 Code graph analysis (1)
functable.c (2)
adler32.c (1)
  • adler32 (26-28)
crc32.c (1)
  • crc32 (39-41)
🔇 Additional comments (5)
functable.h (2)

27-27: LGTM!

The signature change from void to int enables proper error propagation from the initialization sequence.


48-48: LGTM!

The macro correctly checks the return value of force_init() and propagates initialization failures as Z_VERSION_ERROR.

functable.c (3)

49-51: LGTM!

The signature change and return value are correct for the post-initialization no-op state.


53-332: Initialization logic is correct, but depends on recursion fix.

The init sequence properly assigns generic fallbacks first (lines 61-73), then applies architecture-specific overrides, then verifies and atomically updates the global functable. The early assignment of force_init at line 58 ensures it's never null when checked at line 313.

However, the effectiveness of this implementation depends on resolving the infinite recursion issue flagged in the FUNCTABLE_VERIFY_ASSIGN macro comment above.


335-337: LGTM!

This stub correctly propagates the initialization result, enabling the FUNCTABLE_INIT macro to detect and report initialization failures.

@mtl1979
Copy link
Copy Markdown
Collaborator

mtl1979 commented Oct 12, 2025

I figured calling abort() would not be very nice to the application linking zlib-ng. Implemented improved failure handling for deflateinit and inflateinit.

Suggestions for how to handle other exported functions directly? Currently I guess they'll likely segfault, same as they would without this PR. Edit: Or they get infinite recursion like coderabbitai mentions. Its "Solution 2" might be ok for those calling functions directly..?

Aborting with error message is still better than "Segmentation Fault" without any clue what went wrong... Solution 2 would most likely result in hidden data corruption as not all functions return status code.

@Dead2
Copy link
Copy Markdown
Member Author

Dead2 commented Oct 13, 2025

Aborting with error message is still better than "Segmentation Fault" without any clue what went wrong...

Yeah, I agree.

Solution 2 would most likely result in hidden data corruption as not all functions return status code.

No, solution 2 is doing printf + abort for all the direct-calls, while deflateinit/inflateinit would still return Z_VERSION_ERROR (I figure that is the error message most similar, in this case indicating that the zlib version tring to run is not compiled to be compatible with the current cpu).
Inflateinit/deflateinit would never complete, and zlib-ng would never even get to a state of accepting data, so no data loss there.
Potential data loss for the direct calls that hit the abort() though, but as we agree, that is effectively no worse than a segfault anyways, and I don't see any other proper way to do it.

Again, this is an error condition that should never occur with a default build, only potentially possible with custom build configs where generic functions are left out as well as the needed optimized functions. Currently should not be possible with CMake/Configure without using custom defines or code changes. Even with #1984, this is not possible since it makes sure to require WITH_SSE2 and WITH_OPTIM before allowing the removal of the specific generic fallbacks that SSE2 can replace.

But I feel that it is better to have a defined failure mode IF this ever happens.

I'll work on a solution for the stub functions. I also think several of the stub functions should be unreachable (not directly callable and always after inflateinit/deflateinit), not sure whether we should keep those as functional unreachable stubs or replace them with a common one that just calls abort (not something for this PR in any case).

Edit: Implemented changes and squashed the commits, also improved commit message.

@mtl1979
Copy link
Copy Markdown
Collaborator

mtl1979 commented Oct 13, 2025

I'll work on a solution for the stub functions. I also think several of the stub functions should be unreachable (not directly callable and always after inflateinit/deflateinit), not sure whether we should keep those as functional unreachable stubs or replace them with a common one that just calls abort (not something for this PR in any case).

A lot of changes have been made with the stub functions since I initially designed the logic... I haven't been happy with all the changes and have already said so earlier... It might be best to do proper redesign/rewrite and not just commit random patches that cause more dead code. In the current state, some of the stub functions can be called, but others are essentially no-op in normal code. Coverity scan will get very low coverage due to stub calls being mutually exclusive.

…init,

make sure none of the function pointers are nullpointers.

Up until now, zlib-ng and the application would have segfaulted either at the start
of processing, or at some point later depending on when a nullpointer call would happen
in the processing. In any case most likely after accepting data from the application.

Now, the deflateinit/inflateinit functions will error with Z_VERSION_ERROR, and
gzopen will return Z_STREAM_ERROR before actually processing any data.

Direct calls to functions like adler32 or crc32 will however print an error message
and call abort(), as these functions have no actual way of reporting errors.

Note: This should never happen with default builds of zlib-ng, only if it is run on
a cpu that is missing both the matching optimized and the generic fallback functions.
This can currently only happen if zlib-ng is compiled using custom cflags or by
editing the code.
@Dead2 Dead2 requested a review from nmoinvaz October 15, 2025 11:55
@Dead2 Dead2 merged commit fa09b05 into develop Oct 21, 2025
304 checks passed
@Dead2 Dead2 mentioned this pull request Nov 5, 2025
@Dead2 Dead2 deleted the functable-verify branch November 29, 2025 22:48
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.

3 participants