Fix SIGSEGV in AssembleHalfedges on broken face topology#1652
Fix SIGSEGV in AssembleHalfedges on broken face topology#1652DatanoiseTV wants to merge 1 commit into
Conversation
AssembleHalfedges uses a multimap lookup to follow halfedge chains. When the topology is broken (endVert has no matching startVert among remaining edges), the find() returns end(). The existing DEBUG_ASSERT only fires in debug builds — in release builds, dereferencing end() is UB, producing a garbage edge index that causes an out-of-bounds read on the halfedge array, leading to SIGSEGV. This is reproducible with complex boolean operations (BatchBoolean via Face2Tri) where degenerate faces can be produced. Fix: - Replace DEBUG_ASSERT-only check with a hard check that breaks out of the loop gracefully, draining remaining edges - Add bounds check on thisEdge before dereferencing - Protect the quad path: if AssembleHalfedges returns fewer than 4 edges, fall through to general triangulation instead of crashing - Protect the triangle path: skip degenerate triangles where edges don't form a valid loop
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1652 +/- ##
==========================================
+ Coverage 92.59% 95.27% +2.67%
==========================================
Files 33 34 +1
Lines 6048 7804 +1756
==========================================
+ Hits 5600 7435 +1835
+ Misses 448 369 -79 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Can you give a test case that triggers the bad topology? I think we want to fix that instead of just avoiding segfault. |
|
Fair enough — without a standalone reproducer this is hard to act on. I only hit it through OpenSCAD's GUI render path and couldn't strip it down to a manifold-level test case. The headless path with the same geometry didn't crash reliably. Happy to close this. If someone does hit the same crash (SIGSEGV in AssembleHalfedges, release build, always at the |
|
Tried to build a standalone reproducer — ran BatchBoolean with 200 random overlapping spheres at varying $fn, 100 different random seeds. Couldn't trigger it. The crash only happened through OpenSCAD's GUI render path (CGALWorker thread), so it might be related to how OpenSCAD constructs the input meshes before passing them to manifold, or some specific near-degenerate geometry that the random tests don't generate. Since I can't provide a test case, probably best to close this. The defensive check is still useful as a safety net in release builds (the existing DEBUG_ASSERT is a no-op in release), but I understand wanting to fix the root cause instead. |
|
A reproducer with openscad is still good, at least we can look into what happened. |
|
Makes sense — will put together a .scad + build invocation that reproduces it and post back here. |
|
@DatanoiseTV any luck, or should we close this as no repro? |
Unfortunatenly I am currently occupied with other projects. Will look in 2 weeks. |
|
@DatanoiseTV any update? Also, we updated the code a bit, so maybe you can check again to see if there is any segfault. |
Currently busy with other projects, but it is on my todo list. |
Summary
Fixes a crash (
SIGSEGV/EXC_BAD_ACCESS) inAssembleHalfedges(face_op.cpp) that occurs duringFace2Triin release builds when boolean operations produce degenerate faces with broken halfedge topology.vert_edge.find()returnsend()when a face's halfedge chain is broken (endVert has no matching startVert). The only guard wasDEBUG_ASSERT, which is a no-op in release builds. Dereferencingend()is UB — it produces a garbage edge index causing an out-of-bounds read on the halfedge array.BatchBoolean→Boolean3::Result→Face2Tri) with high-polygon meshes under TBB parallelism. Reproducible on macOS with Apple Silicon (M5 Pro) but the bug is platform-independent.SIGSEGVin(anonymous namespace)::AssembleHalfedgesat various offsets (+108, +184, +224), always accessing an address just past the end of aMALLOC_LARGEregion.Changes
Three layers of defense in
face_op.cpp:AssembleHalfedges: Added bounds check onthisEdgebefore dereferencing, and replaced theDEBUG_ASSERT-only check with a hard check that breaks out of the loop gracefully (drains remaining edges)AssembleHalfedgesreturned at least 4 edges before indexingquad[0..3]; fall through to general triangulation if notThe fix is conservative — it prevents the crash and skips degenerate faces rather than producing corrupt geometry. The underlying cause (boolean operations producing faces with broken topology) may warrant a separate investigation.
Test plan