manifold: #528 causes invalid `UpdateVert` calls

Not sure why, but it seems that when openscad is compiled with the current master after #528, https://gist.github.com/ochafik/70a6b15e982b7ccd5a79ff9afd99dbcf#file-condensed-matter-scad will break and access invalid memory in UpdateVert call:

#1  0x0000000000f9d655 in manifold::Manifold::Impl::UpdateVert(int, int, int) ()
#2  0x0000000000f9d9de in manifold::Manifold::Impl::FormLoop(int, int) ()
#3  0x0000000000f9fa40 in manifold::Manifold::Impl::CollapseEdge(int, std::vector<int, std::allocator<int> >&) ()
#4  0x0000000000fa2676 in manifold::Manifold::Impl::SimplifyTopology() ()
#5  0x0000000000f65008 in manifold::Boolean3::Result(manifold::OpType) const ()
#6  0x0000000000f8cadb in manifold::CsgOpNode::BatchBoolean(manifold::OpType, std::vector<std::shared_ptr<manifold::Manifold::Impl const>, std::allocator<std::shared_ptr<manifold::Manifold::Impl const> > >&) ()
#7  0x0000000000f8dccd in manifold::CsgOpNode::BatchUnion() const ()
#8  0x0000000000f8f812 in manifold::CsgOpNode::ToLeafNode() const ()
#9  0x0000000000edf87c in manifold::Manifold::GetCsgLeafNode() const ()
#10 0x0000000000edfa9b in manifold::Manifold::IsEmpty() const ()
#11 0x00000000008fed03 in GeometryEvaluator::applyToChildren3D(AbstractNode const&, OpenSCADOperator) ()
#12 0x0000000000900422 in GeometryEvaluator::applyToChildren(AbstractNode const&, OpenSCADOperator) ()
#13 0x00000000009004e5 in GeometryEvaluator::visit(State&, AbstractNode const&) ()
#14 0x0000000000739fd6 in NodeVisitor::traverse(AbstractNode const&, State const&) ()
#15 0x0000000000739f3b in NodeVisitor::traverse(AbstractNode const&, State const&) ()
#16 0x0000000000739f3b in NodeVisitor::traverse(AbstractNode const&, State const&) ()
#17 0x0000000000739f3b in NodeVisitor::traverse(AbstractNode const&, State const&) ()
#18 0x00000000008fd56a in GeometryEvaluator::evaluateGeometry(AbstractNode const&, bool) ()
#19 0x00000000005c364c in do_export(CommandLine const&, RenderVariables const&, FileFormat, SourceFile*) ()
#20 0x00000000005c5463 in cmdline(CommandLine const&) ()
#21 0x00000000004ddf29 in main ()

I tried compiling with #525 and it works fine. Maybe we should try to port the example to c++ and debug. We can use address sanitizer to try to debug this kind of issues.

There is still invalid access when parallelization is turned off.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 31 (14 by maintainers)

Most upvoted comments

I think I mentioned this before, but this is another reason I’d like to switch SparseIndices from relying on sort and unique to just using a hash table. It should be faster, simpler, and probably allow us to remove these weird thrust calls entirely.

Alternatively we can use sort, but only dedupe the smaller indices:

diff --git a/src/manifold/src/edge_op.cpp b/src/manifold/src/edge_op.cpp
index 4e3db36..cae7f74 100644
--- a/src/manifold/src/edge_op.cpp
+++ b/src/manifold/src/edge_op.cpp
@@ -162,7 +162,8 @@ void Manifold::Impl::SimplifyTopology() {
   for (int i = 0; i < nbEdges - 1; ++i) {
     if (entries[i].start == entries[i + 1].start &&
         entries[i].end == entries[i + 1].end) {
-      DedupeEdge(entries[i].index);
+      DedupeEdge(std::min(entries[i].index, entries[i + 1].index));
+      entries[i + 1].index = std::max(entries[i].index, entries[i + 1].index);
       numFlagged++;
     }
   }

And I can confirm that 1, 2 and 3 are all independent from each other. Fixing one will not fix another.

I feel like I am starting to use my brain now.

I sometimes got this as well:

Error in file: /home/pca006132/code/manifold/src/polygon/src/polygon.cpp (103): 'std::distance(forward.begin(), end) == n_edges' is false: Half of halfedges should be forward.
polys.push_back({
    {20.1267338, 2.00670671},  //
    {20.12673, 2.00670576},  //
    {19.7702141, 1.63188827},  //
    {19.7702141, 1.63188827},  //
    {19.7702141, 1.63188827},  //
    {19.5752983, 1.81728792},  //
    {19.5623493, 1.81283963},  //
    {19.7630997, 1.54846776},  //
    {19.7630997, 1.54846776},  //
});
polys.push_back({
    {19.7702141, 1.63188827},  //
    {19.898922, 1.92845321},  //
    {19.5752983, 1.81728792},  //
});
polys.push_back({
    {19.7630997, 1.54846776},  //
    {19.7630997, 1.54846776},  //
    {19.7630997, 1.54846776},  //
});
array([
  [20.1267338, 2.00670671],
  [20.12673, 2.00670576],
  [19.7702141, 1.63188827],
  [19.7702141, 1.63188827],
  [19.7702141, 1.63188827],
  [19.5752983, 1.81728792],
  [19.5623493, 1.81283963],
  [19.7630997, 1.54846776],
  [19.7630997, 1.54846776],
])
array([
  [19.7702141, 1.63188827],
  [19.898922, 1.92845321],
  [19.5752983, 1.81728792],
])
array([
  [19.7630997, 1.54846776],
  [19.7630997, 1.54846776],
  [19.7630997, 1.54846776],
])

Indeed, that should work. I was thinking about using pair as the key type but it is not really needed.

OK I got a simpler reproduce, but it is non-deterministic… Boolean.Sweep under the correct condition will trigger the out of bound bug in FormLoop, basically you need to run the test 5 times or more, and add the following patch so the debugger can be triggered (as tbb will mess up with the stack trace otherwise…):

--- a/src/utilities/include/vec.h
+++ b/src/utilities/include/vec.h
@@ -59,12 +59,20 @@ class VecView {
   operator VecView<const T>() const { return {ptr_, size_}; }
 
   inline const T &operator[](int i) const {
-    if (i < 0 || i >= size_) throw std::out_of_range("Vec out of range");
+    if (i < 0 || i >= size_) {
+      printf("i: %d, size: %d\n", i, size_);
+      __builtin_trap();
+      throw std::out_of_range("Vec out of range");
+    }
     return ptr_[i];
   }
 
   inline T &operator[](int i) {
-    if (i < 0 || i >= size_) throw std::out_of_range("Vec out of range");
+    if (i < 0 || i >= size_) {
+      printf("i: %d, size: %d\n", i, size_);
+      __builtin_trap();
+      throw std::out_of_range("Vec out of range");
+    }
     return ptr_[i];
   }

The out of range are always -1 or -3, so I think we should probably add more check to make sure that the halfedge is not already removed…

Also, I implemented multithreaded CollapseEdge, but the performance is not great: the performance is slightly better on my machine with 20 cores (50ms faster on average? over something that took 2400ms), and slower when the number of cores decreases. I double checked to make sure that my partition implementation should be pretty fast, so I guess the problem is just too hard to parallelize (at least for now).