Skip to content

fix(render): patch Blend2D so a sheared image at the clip edge does not segfault - #387

Open
wittjeff wants to merge 1 commit into
docling-project:mainfrom
wittjeff:fix/blend2d-analytic-fill-span
Open

wittjeff wants to merge 1 commit into
docling-project:mainfrom
wittjeff:fix/blend2d-analytic-fill-span

Conversation

@wittjeff

@wittjeff wittjeff commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #365.

What

get_image() segfaulted on page 7 of the scanned manual from #365 at exactly scale 3.0. The crash is inside Blend2D's JIT-generated analytic fill, not in docling-parse code, and it is triggered by a combination of our geometry and the clip width:

  • The page is one CCITT bitmap placed with a 0.17 degree skew, so it goes through render_bitmap_affine.
  • At scale 3.0 the canvas is 1279 px wide. 1279 % 256 is 255, which puts the clip's x_end cell on bit 63 of the last 64-bit coverage word of the scanline.
  • The skewed top edge crosses the right clip edge within row 0, so the rasterizer flags every cell of that word.
  • Blend2D's FillAnalyticPart then re-enters its bit-scan loop from the bit-gap path with a stale span length (0 after the constant-mask loop) instead of 64, computes a zero-length span, and its per-pixel do/while loop runs 2^32 times until the destination pointer leaves the heap.

Root cause and trace: blend2d/blend2d#267. Upstream fix: blend2d/blend2d#268.

This PR:

  1. carries that fix as a FetchContent PATCH_COMMAND on the pinned Blend2D commit (cmake/blend2d-fillanalytic-stale-i.patch, applied once by cmake/apply_patch.cmake, which detects an already-applied patch on re-population). Drop both when the pin moves past the upstream fix.
  2. adds tests/test_unit_affine_image_clip.py: a 255.5 x 5 pt page (511 x 10 px at scale 2) with a 16 x 16 image placed on the crashing geometry. It renders in a child process so a regression fails the test with the signal instead of killing the pytest run. It fails with -11 on the current wheels and passes with the patch.

Why it looked like a 7.10.0 regression

Affine image blits exist since v5.9 (#250). 7.9.0 rendered this page only because its int-truncated crop box gave a 1278 x 1794 canvas with the top edge 1.7 px higher, which kept the skewed edge above the canvas. #305 moved the geometry by about 2 px into the failing configuration. Any version can hit this with the right PDF, and clip rectangles can trigger it at any canvas width, so padding the canvas would not be a fix.

Verification

  • Issue repro (page 7 at 3.0): renders 1279 x 1796; all 39 pages at 3.0 render; page-7 scale sweep 2.990..3.010: 0/21 crashes (9/21 before).
  • Standalone Blend2D fuzz across the affected widths (511/1023/1279/2047, 200 sheared blits each): 0 crashes (3-5 each before).
  • Patched JIT output is byte-identical to Blend2D's reference (non-JIT) pipeline on the crashing cases, and all pinned/patched x JIT/reference combinations agree on unaffected widths, so rendering of other content is unchanged.
  • New test passes on the patched build and fails with SIGSEGV on the stock 7.22.1 wheel.

…ot segfault

Blend2D's JIT analytic fill reused a stale span length when the last
coverage word of a scanline was all ones, which happens when an affine
image blit's edge crosses the right clip edge and the clip width is
252..255 mod 256. The zero-length span then ran the per-pixel loop 2^32
times and walked the destination pointer off the heap. Page 7 of the
PDF in docling-project#365 hits exactly that at scale 3.0 (1279 px canvas).

Carry the upstream fix (blend2d/blend2d#268, for blend2d/blend2d#267) as
a FetchContent PATCH_COMMAND on the pinned commit, applied once by
cmake/apply_patch.cmake, and add a regression test that renders a
synthetic page on the crashing geometry in a child process.

Fixes docling-project#365

Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

Thanks @wittjeff, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@lazariv

lazariv commented Oct 26, 2026

Copy link
Copy Markdown

Likely production hit, for prioritisation: docling-serve v1.37.0 (docling-parse 7.22.3) died with SIGSEGV
about 3.5 minutes into converting a PDF with EasyOCR at the default OCR scale 3.0. The faulting frame was in
anonymous executable memory ([0x7e9186aa92ce], no module), which is where Blend2D's AsmJit pipelines live.
The crash log was lost with the container, so this is not confirmed, but it matches the mechanism in #365.
The fix isn't in 7.23.0 yet; a review and release would be much appreciated. Thanks @wittjeff for the analysis.

@lazariv

lazariv commented Oct 26, 2026

Copy link
Copy Markdown

Follow-up: confirmed. Converting the production document page by page, one page crashes with SIGSEGV whenever EasyOCR renders it at the default scale 3.0, and converts at 2.99 and 3.01:

Fatal Python error: Segmentation fault

Current thread (most recent call first):
  File ".../docling_parse/pdf_parser.py", line 1378 in _render_image_at_scale
  File ".../docling_parse/pdf_parser.py", line 1443 in get_image
  File ".../docling/backend/docling_parse_backend.py", line 280 in get_page_image
  File ".../docling/models/stages/ocr/easyocr_model.py", line 328 in __call__

docling-serve v1.38.1 (docling-parse 7.23.0), x86_64. The document is a scanned book with slightly skewed CCITT/JBIG2 page images; two neighbouring pages crash the same way. The page from #365 crashes in the same setup and converts at 2.99. With pdf_backend=pypdfium2 all of them convert. So any stock docling-serve with OCR on can reach this. A merge and release would let us drop the workaround.

@cau-git

cau-git commented Oct 28, 2026

Copy link
Copy Markdown
Member

Thanks for this contribution. This PR is on hold until it has been consolidated as described in #428. Please resubmit it as part of the topic PR listed there, with the evidence the issue asks for.

This branch has not been deployed

No deployments
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.

Segfault in get_image(scale=3.0) on a scanned CCITT page (regression in 7.10.0)

3 participants