Skip to content

Ppmd7/Ppmd8: Fix SIGABRT in OutputBuffer_Grow - #131

Open
leon-anavi wants to merge 2 commits into
miurahr:mainfrom
leon-anavi:leon/fix-sigabrt
Open

Ppmd7/Ppmd8: Fix SIGABRT in OutputBuffer_Grow#131
leon-anavi wants to merge 2 commits into
miurahr:mainfrom
leon-anavi:leon/fix-sigabrt

Conversation

@leon-anavi

Copy link
Copy Markdown

The decode loop could abort in OutputBuffer_Grow() when a resumed decode thread wrote past the current call's max_length. Stop early once that limit is reached instead of trying to grow the buffer further.

Fixes test_ppmd7_decode_chunks and the equivalent Ppmd8 case, which previously aborted deterministically partway through decoding testdata2.ppmd in small chunks.

This work was sponsored by GOVCERT.LU.

I've noticed this issue while integrating the tests in the Yocto/OpenEmbedded layer meta-openembedded/meta-python. Without this fix test test_ppmd7_decode_chunks was crashing on Raspberry Pi 5:

PASS: tests/test_ppmd7.py:test_ppmd7_encode_decode[16777216]
PASS: tests/test_ppmd7.py:test_ppmd7_encode_decode[1048576]
Fatal Python error: Aborted
Current thread 0x00007ffecfe08020 [python3] (most recent call first):
  File "/usr/lib/python3-pyppmd/ptest/tests/test_ppmd7.py", line 157 in test_ppmd7_decode_chunks
  File "/usr/lib/python3.14/site-packages/_pytest/python.py", line 167 in pytest_pyfunc_call
  File "/usr/lib/python3.14/site-packages/pluggy/_callers.py", line 121 in _multicall
  File "/usr/lib/python3.14/site-packages/pluggy/_manager.py", line 120 in _hookexec
  File "/usr/lib/python3.14/site-packages/pluggy/_hooks.py", line 512 in __call__
  File "/usr/lib/python3.14/site-packages/_pytest/python.py", line 1707 in runtest
  File "/usr/lib/python3.14/site-packages/_pytest/runner.py", line 184 in pytest_runtest_call
  File "/usr/lib/python3.14/site-packages/pluggy/_callers.py", line 121 in _multicall
  File "/usr/lib/python3.14/site-packages/pluggy/_manager.py", line 120 in _hookexec
  File "/usr/lib/python3.14/site-packages/pluggy/_hooks.py", line 512 in __call__
  File "/usr/lib/python3.14/site-packages/_pytest/runner.py", line 250 in <lambda>
  File "/usr/lib/python3.14/site-packages/_pytest/runner.py", line 361 in from_call
  File "/usr/lib/python3.14/site-packages/_pytest/runner.py", line 249 in call_and_report
  File "/usr/lib/python3.14/site-packages/_pytest/runner.py", line 139 in runtestprotocol
  File "/usr/lib/python3.14/site-packages/_pytest/runner.py", line 118 in pytest_runtest_protocol
  File "/usr/lib/python3.14/site-packages/pluggy/_callers.py", line 121 in _multicall
  File "/usr/lib/python3.14/site-packages/pluggy/_manager.py", line 120 in _hookexec
  File "/usr/lib/python3.14/site-packages/pluggy/_hooks.py", line 512 in __call__
  File "/usr/lib/python3.14/site-packages/_pytest/main.py", line 408 in pytest_runtestloop
  File "/usr/lib/python3.14/site-packages/pluggy/_callers.py", line 121 in _multicall
  File "/usr/lib/python3.14/site-packages/pluggy/_manager.py", line 120 in _hookexec
  File "/usr/lib/python3.14/site-packages/pluggy/_hooks.py", line 512 in __call__
  File "/usr/lib/python3.14/site-packages/_pytest/main.py", line 384 in _main
  File "/usr/lib/python3.14/site-packages/_pytest/main.py", line 330 in wrap_session
  File "/usr/lib/python3.14/site-packages/_pytest/main.py", line 377 in pytest_cmdline_main
  File "/usr/lib/python3.14/site-packages/pluggy/_callers.py", line 121 in _multicall
  File "/usr/lib/python3.14/site-packages/pluggy/_manager.py", line 120 in _hookexec
  File "/usr/lib/python3.14/site-packages/pluggy/_hooks.py", line 512 in __call__
  File "/usr/lib/python3.14/site-packages/_pytest/config/__init__.py", line 229 in _main
  File "/usr/lib/python3.14/site-packages/_pytest/config/__init__.py", line 253 in _console_main
  File "/bin/pytest", line 8 in <module>

Current thread's C stack trace (most recent call first):
  Binary file "/usr/lib/libpython3.14.so.1.0", at _Py_DumpStack+0x4c [0x7ffed00bb91c]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0x2c0224 [0x7ffed00d0224]
  Binary file "linux-vdso.so.1", at __kernel_rt_sigreturn+0x0 [0x7ffed0480840]
  Binary file "/usr/lib/libc.so.6", at +0x8a228 [0x7ffecfcca228]
  Binary file "/usr/lib/libc.so.6", at gsignal+0x1c [0x7ffecfc7717c]
  Binary file "/usr/lib/libc.so.6", at abort+0x30 [0x7ffecfc62720]
  Binary file "/usr/lib/libc.so.6", at +0x7d7f4 [0x7ffecfcbd7f4]
  Binary file "/usr/lib/libc.so.6", at __assert_fail+0xb4 [0x7ffecfc709b4]
  Binary file "/usr/lib/python3.14/site-packages/pyppmd/c/_ppmd.cpython-314-aarch64-linux-gnu.so", at +0x4580 [0x7ffecc504580]
  Binary file "/usr/lib/python3.14/site-packages/pyppmd/c/_ppmd.cpython-314-aarch64-linux-gnu.so", at +0x4760 [0x7ffecc504760]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0xf8b9c [0x7ffecff08b9c]
  Binary file "/usr/lib/libpython3.14.so.1.0", at PyObject_Vectorcall+0x58 [0x7ffecfef9d9c]
  Binary file "/usr/lib/libpython3.14.so.1.0", at _PyEval_EvalFrameDefault+0x2f28 [0x7ffed002bf98]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0x2274f0 [0x7ffed00374f0]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0xebbe8 [0x7ffecfefbbe8]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0xebde4 [0x7ffecfefbde4]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0x17c960 [0x7ffecff8c960]
  Binary file "/usr/lib/libpython3.14.so.1.0", at _PyObject_MakeTpCall+0xac [0x7ffecfef9af4]
  Binary file "/usr/lib/libpython3.14.so.1.0", at _PyEval_EvalFrameDefault+0x27c0 [0x7ffed002b830]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0x2274f0 [0x7ffed00374f0]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0xebbe8 [0x7ffecfefbbe8]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0xebde4 [0x7ffecfefbde4]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0x17c960 [0x7ffecff8c960]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0xec084 [0x7ffecfefc084]
  Binary file "/usr/lib/libpython3.14.so.1.0", at _PyEval_EvalFrameDefault+0x3774 [0x7ffed002c7e4]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0x2274f0 [0x7ffed00374f0]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0xebbe8 [0x7ffecfefbbe8]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0xebde4 [0x7ffecfefbde4]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0x17c960 [0x7ffecff8c960]
  Binary file "/usr/lib/libpython3.14.so.1.0", at _PyObject_MakeTpCall+0xac [0x7ffecfef9af4]
  Binary file "/usr/lib/libpython3.14.so.1.0", at _PyEval_EvalFrameDefault+0x27c0 [0x7ffed002b830]
  Binary file "/usr/lib/libpython3.14.so.1.0", at +0x2274f0 [0x7ffed00374f0]
  <truncated rest of calls>
Extension modules: pyppmd.c._ppmd (total: 1)
/usr/lib/python3-pyppmd/ptest/run-ptest: line 3:  1463 Aborted                    (core dumped) pytest --automake

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@leon-anavi, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 087c9689-98c7-4b93-9f76-b0f77a5ccbff

📥 Commits

Reviewing files that changed from the base of the PR and between 8cc28ad and f9c6a05.

📒 Files selected for processing (1)
  • src/ext/_ppmdmodule.c

Walkthrough

Added a check in both Ppmd7Decoder and Ppmd8Decoder decode loops to stop output when blocksOutputBuffer reaches its configured max_length, breaking out of the loop instead of calling OutputBuffer_Grow.

Changes

Decoder Output Limit Guard

Layer / File(s) Summary
Add max_length break in decode loops
src/ext/_ppmdmodule.c
Both Ppmd7Decoder and Ppmd8Decoder decode loops check blocksOutputBuffer max_length/allocated when out->pos equals out->size, breaking out of the loop instead of growing the buffer once the limit is reached.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Poem

A rabbit hops through buffers deep,
When max_length's met, it dares not leap.
No more growing, no more strain,
Just a quiet break to hold the rein. 🐇✂️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: preventing SIGABRT in Ppmd7/Ppmd8 OutputBuffer_Grow.
Description check ✅ Passed The description directly matches the code change and explains the crash fix and affected tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/ext/_ppmdmodule.c (1)

534-539: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using OutputBuffer_ReachedMaxLength helper for consistency.

Both guards inline the allocated == max_length check, but the codebase already provides OutputBuffer_ReachedMaxLength(buffer, ob) in blockoutput.h which performs the same check (with an assert that ob->pos == ob->size, already guaranteed by the out->pos == out->size guard above). Using the helper would reduce duplication and keep the check aligned with the upstream contract.

♻️ Optional refactor for Ppmd7 (lines 536-538)
-            if (self->blocksOutputBuffer->max_length >= 0 &&
-                self->blocksOutputBuffer->allocated >= self->blocksOutputBuffer->max_length) {
+            if (self->blocksOutputBuffer->max_length >= 0 &&
+                OutputBuffer_ReachedMaxLength(self->blocksOutputBuffer, out)) {
                 break;
♻️ Optional refactor for Ppmd8 (lines 1286-1288)
-            if (self->blocksOutputBuffer->max_length >= 0 &&
-                self->blocksOutputBuffer->allocated >= self->blocksOutputBuffer->max_length) {
+            if (self->blocksOutputBuffer->max_length >= 0 &&
+                OutputBuffer_ReachedMaxLength(self->blocksOutputBuffer, out)) {
                 break;

Also applies to: 1285-1289

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ext/_ppmdmodule.c` around lines 534 - 539, Replace the inline max-length
guard in the Ppmd7/Ppmd8 block output paths with the existing
OutputBuffer_ReachedMaxLength helper from blockoutput.h for consistency. Update
the relevant checks around the output buffer growth logic in the Ppmd7 and Ppmd8
code paths to call the helper instead of duplicating the
allocated-versus-max_length comparison, keeping the surrounding pos/size guard
intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ext/_ppmdmodule.c`:
- Around line 1285-1289: Ppmd8Decoder_decode is missing the same needs_input
update that Ppmd7Decoder_decode already applies when output stops because
max_length is reached and all input has been consumed. Update the
Ppmd8Decoder_decode path that breaks on the max_length guard so that, after the
loop, it sets needs_input to True when in->pos == in->size and result > 0
without EOF, matching the Ppmd7Decoder_decode logic and preventing stale
needs_input from carrying over to the next call.

---

Nitpick comments:
In `@src/ext/_ppmdmodule.c`:
- Around line 534-539: Replace the inline max-length guard in the Ppmd7/Ppmd8
block output paths with the existing OutputBuffer_ReachedMaxLength helper from
blockoutput.h for consistency. Update the relevant checks around the output
buffer growth logic in the Ppmd7 and Ppmd8 code paths to call the helper instead
of duplicating the allocated-versus-max_length comparison, keeping the
surrounding pos/size guard intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c205c2b-42dd-4b1c-b364-39d47287b311

📥 Commits

Reviewing files that changed from the base of the PR and between 52da9d1 and 8cc28ad.

📒 Files selected for processing (1)
  • src/ext/_ppmdmodule.c

Comment thread src/ext/_ppmdmodule.c
The decode loop could abort in OutputBuffer_Grow() when a resumed
decode thread wrote past the current call's max_length. Stop early
once that limit is reached instead of trying to grow the buffer
further.

Also fix Ppmd8Decoder_decode because it never updated needs_input
when input was fully consumed, unlike Ppmd7.

Fixes test_ppmd7_decode_chunks and the equivalent Ppmd8 case,
which previously aborted deterministically partway through decoding
testdata2.ppmd in small chunks.

This work was sponsored by GOVCERT.LU.

Signed-off-by: Leon Anavi <leon.anavi@konsulko.com>
@leon-anavi

Copy link
Copy Markdown
Author

For the record, with this patch on top of version 1.3.1 all tests pass on Raspberry Pi 5:

root@raspberrypi5:~# ptest-runner python3-pyppmd
START: ptest-runner
2026-07-09T12:59
BEGIN: /usr/lib/python3-pyppmd/ptest
PASS: tests/test_benchmark.py:test_benchmark_text_compress[PPMd H-7-6-16777216]
PASS: tests/test_benchmark.py:test_benchmark_text_compress[PPMd I-8-8-8388608]
PASS: tests/test_benchmark.py:test_benchmark_text_decompress[PPMd H-7-6-16777216]
PASS: tests/test_benchmark.py:test_benchmark_text_decompress[PPMd I-8-8-8388608]
PASS: tests/test_fuzzer.py:test_ppmd7_fuzzer
PASS: tests/test_fuzzer.py:test_ppmd8_fuzzer
PASS: tests/test_highlevel_api.py:test_compress_str
PASS: tests/test_highlevel_api.py:test_compress
PASS: tests/test_highlevel_api.py:test_decompress_str
PASS: tests/test_highlevel_api.py:test_decompress
PASS: tests/test_ppmd7.py:test_ppmd7_encoder
PASS: tests/test_ppmd7.py:test_ppmd7_encoder2
PASS: tests/test_ppmd7.py:test_ppmd7_decoder
PASS: tests/test_ppmd7.py:test_ppmd7_decoder2
PASS: tests/test_ppmd7.py:test_ppmd7_encode_decode[16777216]
PASS: tests/test_ppmd7.py:test_ppmd7_encode_decode[1048576]
PASS: tests/test_ppmd7.py:test_ppmd7_decode_chunks
PASS: tests/test_ppmd8.py:test_ppmd8_encoder1
PASS: tests/test_ppmd8.py:test_ppmd8_encoder2
PASS: tests/test_ppmd8.py:test_ppmd8_decoder1
PASS: tests/test_ppmd8.py:test_ppmd8_decoder2
PASS: tests/test_ppmd8.py:test_ppmd8_encode_decode[8388608-0]
PASS: tests/test_ppmd8.py:test_ppmd8_encode_decode[8388608-1]
PASS: tests/test_ppmd8.py:test_ppmd8_encode_decode[1048576-0]
PASS: tests/test_ppmd8.py:test_ppmd8_encode_decode[1048576-1]
PASS: tests/test_ppmd8.py:test_ppmd8_encode_decode_shortage
PASS: tests/test_ppmd8.py:test_ppmdcompress
PASS: tests/test_ppmd8.py:test_ppmddecompress
============================================================================
Testsuite summary
# TOTAL: 28
# PASS: 28
# SKIP: 0
# XFAIL: 0
# FAIL: 0
# XPASS: 0
# ERROR: 0
DURATION: 15
END: /usr/lib/python3-pyppmd/ptest
2026-07-09T13:00
STOP: ptest-runner
TOTAL: 1 FAIL: 0

@miurahr
miurahr self-requested a review July 16, 2026 21:49
@miurahr miurahr added the bug Something isn't working label Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants