diff --git a/CLAUDE.md b/CLAUDE.md index ee19dc9..18d1731 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,14 @@ OpenSCAD AST and produces Manifold CSG geometry. This port builds on two depende - `openscad_cpp_parser` (git submodule, `external/openscad_cpp_parser`) for lexing/parsing/AST/scope - Manifold (https://github.com/elalish/manifold) v3.5.2, pulled in via CMake `FetchContent` (pinned - tag, not vendored) — wired in as of Phase 2 + tag, not vendored) — wired in as of Phase 2. **Patched at fetch time** by + `cmake/patch_manifold.cmake`: it deletes the `combined.RemoveDegenerates()` call in + `CsgLeafNode::Compose`, which on some inputs leaves the face data inconsistent so the sort after + it reads past an array (heap-buffer-overflow under ASan, reached by + `MinkowskiDifference.ShrinksANonConvexBodyOnEverySide`). Bisected with a Manifold-only repro to + upstream #1789 (969b1417), which fixes it by deleting the same call; unreleased as of 3.5.4. + The script fails the configure if the source around the call has changed, so **drop the patch + when bumping past #1789** rather than letting it rot - Boost.Polygon (`boostorg/polygon` + `boostorg/config`, fetched individually — not the full Boost superproject) for `roof()`'s Voronoi-diagram construction — wired in as of Phase 6, see below - `nothings/stb` (`stb_image.h`; header-only, no build step) for `surface()`'s PNG loader — wired diff --git a/CMakeLists.txt b/CMakeLists.txt index 36b7661..39509eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,6 +137,9 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/elalish/manifold.git GIT_TAG v3.5.2 GIT_SHALLOW TRUE + # Backports the one line of upstream #1789 that fixes a heap overflow in + # CsgLeafNode::Compose -- see cmake/patch_manifold.cmake. + PATCH_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch_manifold.cmake ) FetchContent_MakeAvailable(manifold) diff --git a/cmake/patch_manifold.cmake b/cmake/patch_manifold.cmake new file mode 100644 index 0000000..6c8c036 --- /dev/null +++ b/cmake/patch_manifold.cmake @@ -0,0 +1,31 @@ +# Patches the fetched Manifold v3.5.2 source; run as its FetchContent +# PATCH_COMMAND, in the source directory. +# +# CsgLeafNode::Compose calls RemoveDegenerates() on the combined mesh and +# then sorts it, and on some inputs the cleanup leaves the face data +# inconsistent: SortGeometry -> GatherFaces then reads past the end of an +# array (a heap-buffer-overflow under AddressSanitizer). Our +# MinkowskiDifference.ShrinksANonConvexBodyOnEverySide test reaches it; +# reproduced with Manifold alone, and bisected upstream to elalish/manifold +# #1789 ("Improved decimation", 969b1417), which fixes it by deleting this +# same call -- the only part of that commit the fix needs. Not in any +# release yet; drop this patch when Manifold is bumped past it. +# +# Idempotent: a re-run on already-patched source changes nothing. Anything +# else -- the call or its surroundings changed -- is an error, so a version +# bump can never silently skip the patch. +set(file src/csg_tree.cpp) +file(READ ${file} src) +set(call " // required to remove parts that are smaller than the tolerance\n combined.RemoveDegenerates();\n") +string(FIND "${src}" "${call}" at) +if(at EQUAL -1) + string(FIND "${src}" "combined.RemoveDegenerates();" any) + if(NOT any EQUAL -1) + message(FATAL_ERROR "patch_manifold.cmake: ${file} changed around RemoveDegenerates(); update the patch") + endif() + message(STATUS "patch_manifold.cmake: already applied") + return() +endif() +string(REPLACE "${call}" "" src "${src}") +file(WRITE ${file} "${src}") +message(STATUS "patch_manifold.cmake: removed RemoveDegenerates() from CsgLeafNode::Compose")