Potential Memory Bug in Brep.JoinBreps

Hi, I believe I’ve encountered something of a memory bug with Brep.JoinBreps. This function has the following signature:

public static Brep[] JoinBreps(
  IEnumerable<Brep> brepsToJoin,
  Double tolerance,
  Double angleTolerance,
  out List<int[]> indexMap
)

As it takes in an IEnumerable<Brep> it would seem safe to do something like:

Brep[] JoinFaces(BrepFace[] faces) {
  return Brep.JoinBreps(faces
    .Select(face => face.DuplicateFace(true)),
  0.1);
}

However it seems that all the breps given in the brepsToJoin argument must live for the full duration of the call - which is not a guarantee with the IEnumerable<Brep> type if that function isn’t taking ownership of the iterated Breps. I imagine that - if GC runs during this call it could clean up a Brep which this function isn’t keeping alive. I believe this is the case because I occasionally observe the following:

Repeat 2 times:
--------------------------------
   at UnsafeNativeMethods.RHC_RhinoJoinBreps(IntPtr, IntPtr, Double)
--------------------------------
   at Rhino.Geometry.Brep.JoinBreps(System.Collections.Generic.IEnumerable`1<Rhino.Geometry.Brep>, Double)

when I pass in “non-owned” Breps but it never happens when I pass in an owned collection. Does this sound right? Thanks!

Can you tell us the Rhino version?

We fixed several bugs of this class in earlier Service Releases.

Thanks, - Giulio


Giulio Piacentino
for Robert McNeel & Associates
giulio@mcneel.com

Hey Giulio, Rhino Inside is on version 8.0.6-beta and my Rhino UI is:

Rhino 8 SR33 2026-7-7 (Rhino 8, 8.33.26188.13001, Git hash:master @ 482f88a83547c62cde64d78ea8a8690b7349f289)

LMK if I should be updating/downgrading anything

Hi @Jordan_Bonecutter1,

The Breps you pass in need to stay alive for the duration of the call. A deferred LINQ query doesn’t hold onto the objects it has already yielded, so materialize it first:

Brep[] JoinFaces(BrepFace[] faces)
{
  Brep[] duplicates = faces
    .Select(face => face.DuplicateFace(true))
    .ToArray();

  Brep[] joined = Brep.JoinBreps(duplicates, 0.1);

  GC.KeepAlive(duplicates);
  return joined;
}

The ToArray() gives the duplicated Breps an owner that outlives the join.

– Dale

Hey Dale, yes that’s definitely a solution from the calling side which is what I’ve done. Maybe if that should remain a requirement would it make more sense to change the JoinBreps signature to take in a List and have overloads for other owned data structures? It feels like a footgun as is. Or at least mentioning that in the documentation would be helpful. Thanks!

Hi @Jordan_Bonecutter1,

Thanks for the clear report and for the follow-up. You are correct that this is a footgun. I looked at the source. There is a problem on our side.

I made a bug report so that we all can follow up: RH-97586

Until the fix is available, please continue to use ToArray() as Dale showed. Thanks again for finding this.

Thanks,

Giulio


Giulio Piacentino
for Robert McNeel & Associates
giulio@mcneel.com

Awesomeness. Thanks, Giulio!

RH-97586 is fixed in Rhino 8 Service Release 35 Release Candidate