From c7239ce8e523a7af0947f8c8654bbd51badfc145 Mon Sep 17 00:00:00 2001 From: Joe Rivera Date: Wed, 21 Oct 2026 13:47:35 -0500 Subject: [PATCH] fix(LBIE): keep getVertGrad on the grid (intermittent crash in compute_qef) LbieMesherTest.HexaVerticesStayInsideAVolumeTheSurfaceLeaves hit an access violation on windows-2022 Release (transfix/libcvc#758, run 39637628700) in its r = 1.5 sphere, after "finished collapse" and before "finished compute qef". Octree::interpRect3Dpts_x/y/z take a second gradient sample at the grid point after the one an edge's crossing falls in. When the edge ends on the volume's far face exactly at the isovalue (ival == 1), that point is one plane off the grid, and getVertGrad()/getValue() read past orig_vol: up to about 1 KB for a z edge. The r = 1.5 sphere, sampled at 17^3 in [-1,1]^3, passes exactly through 24 grid points on the box's faces (8^2 + 4^2 + 8^2 = 12^2 in eighths); r = 1.2 passes through none. AddressSanitizer flags the read on every run: heap-buffer-overflow READ of size 4, 12 bytes after the 19652-byte orig_vol Octree::getValue octree.cpp:3598 Octree::getVertGrad octree.cpp:3661 Octree::interpRect3Dpts_z octree.cpp:3501 Octree::cell_comp octree.cpp:606 Octree::compute_qef octree.cpp:760 getVertGrad() now clamps its point onto the grid. The off-grid sample's weight is 0, so meshes are unchanged (344 vertices / 4374 quads and 64 / 162 for the test's spheres, as before). New LbieMesherTest.MeshesASurfaceThroughGridPointsOnTheFarFaces meshes that sphere as TETRA, HEXA, DOUBLE and TETRA2; without the fix ASan flags each of them (compute_qef or compute_qef_interval). --- src/cvc/geometry/cvc-mesher/LBIE/octree.cpp | 7 ++++++ src/cvc/tests/lbie_mesher_test.cpp | 26 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/cvc/geometry/cvc-mesher/LBIE/octree.cpp b/src/cvc/geometry/cvc-mesher/LBIE/octree.cpp index 0704fd16b..5679ae638 100644 --- a/src/cvc/geometry/cvc-mesher/LBIE/octree.cpp +++ b/src/cvc/geometry/cvc-mesher/LBIE/octree.cpp @@ -3607,6 +3607,13 @@ float Octree::getValue(int i, int j, int k) //------------------------------------------------------------------- void Octree::getVertGrad(int i, int j, int k, float g[3]) { + // interpRect3Dpts_x/y/z also sample the grid point after the one an edge's crossing falls in. + // When the edge ends on the volume's far face exactly at the isovalue, that point is one plane + // off the grid and getValue() read past orig_vol (its weight is 0, so the face's gradient is + // the same answer). + i = std::min(std::max(i, 0), dim[0]-1); + j = std::min(std::max(j, 0), dim[1]-1); + k = std::min(std::max(k, 0), dim[2]-1); if(flag_normal == 1) { // central difference int lres=dim[0]; diff --git a/src/cvc/tests/lbie_mesher_test.cpp b/src/cvc/tests/lbie_mesher_test.cpp index 1929122ef..90fe4703a 100644 --- a/src/cvc/tests/lbie_mesher_test.cpp +++ b/src/cvc/tests/lbie_mesher_test.cpp @@ -407,6 +407,32 @@ TEST_F(LbieMesherTest, HexaVerticesStayInsideAVolumeTheSurfaceLeaves) { } } +// Sampled at 17^3 in its [-1,1]^3 box, a sphere of radius 1.5 passes exactly through grid points +// on the box's faces (8^2 + 4^2 + 8^2 = 12^2 in eighths). Where an edge ends on the far face at the +// isovalue, Octree::interpRect3Dpts_x/y/z asked getVertGrad() for a point one plane off the grid, +// and getValue() read up to about 1 KB past orig_vol. That was the access violation in compute_qef +// that crashed HexaVerticesStayInsideAVolumeTheSurfaceLeaves now and then on Windows; +// AddressSanitizer flags it on every run, for every mesh type. getVertGrad() now stays on the grid. +// SINGLE extracts no surface from this sphere. QUAD is left out: polygonize_quad has its own +// out-of-bounds read wherever a surface leaves the volume (r = 1.2 too), not fixed here. +TEST_F(LbieMesherTest, MeshesASurfaceThroughGridPointsOnTheFarFaces) { + volume v = make_sphere_volume(ctx, 17, 1.5); + for (LBIE::geoframe::GEOTYPE mt : {LBIE::geoframe::TETRA, LBIE::geoframe::HEXA, + LBIE::geoframe::DOUBLE, LBIE::geoframe::TETRA2}) { + const bool interval = mt == LBIE::geoframe::DOUBLE || mt == LBIE::geoframe::TETRA2; + LBIE::geoframe g = run_do_mesh(v, 0.0f, interval ? -0.15f : 0.0f, kDefaultErr, kDefaultErrIn, + mt, LBIE::Mesher::NO_IMPROVE, 0, interval); + EXPECT_GT(g.numverts, 0) << "mesh type " << mt; + expect_geoframe_valid(g); + int bad_normal = 0; + for (size_t i = 0; i < g.normals.size(); ++i) + for (int j = 0; j < 3; ++j) + if (!std::isfinite(g.normals[i][j])) + ++bad_normal; + EXPECT_EQ(bad_normal, 0) << "mesh type " << mt << ": non-finite normals"; + } +} + TEST_F(LbieMesherTest, HexaDoMeshGeoFlow) { volume v = make_sphere_volume(ctx, 17, 0.5); LBIE::geoframe g = run_do_mesh(v, 0.0f, 0.0f, kDefaultErr, kDefaultErrIn, LBIE::geoframe::HEXA,