Bug 160309 - Crash after ungroup - undo - ungroup in SwUndoDelLayFormat::SwUndoDelLayFormat(SwFrameFormat*)
Summary: Crash after ungroup - undo - ungroup in SwUndoDelLayFormat::SwUndoDelLayForma...
Status: ASSIGNED
Alias: None
Product: LibreOffice
Classification: Unclassified
Component: Writer (show other bugs)
Version:
(earliest affected)
7.4.0.3 release
Hardware: x86-64 (AMD64) All
: high critical
Assignee: David Hashe
URL:
Whiteboard:
Keywords: bibisected, bisected, regression
Depends on:
Blocks: Crash DOCX-Grouped-Shapes
  Show dependency treegraph
 
Reported: 2024-03-22 01:29 UTC by Stéphane Guillou (stragu)
Modified: 2025-06-12 04:49 UTC (History)
5 users (show)

See Also:
Crash report or crash signature: ["SwUndoDelLayFormat::SwUndoDelLayFormat(SwFrameFormat*)","static SwUndoId lcl_GetSwUndoId(const class SwFrameFormat *)"]


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Stéphane Guillou (stragu) 2024-03-22 01:29:29 UTC
Steps:
1. Open attachment 193233 [details]
2. Select group 9 (double-click it in Navigator)
3. Ungroup it
4. Undo
5. Ungroup it again

Result: crash

Reproduced in 7.4.0.3 and recent trunk build:

Version: 24.8.0.0.alpha0+ (X86_64) / LibreOffice Community
Build ID: 53c5d570cab036b23f4969b858a648c8f0c24f93
CPU threads: 8; OS: Linux 6.5; UI render: default; VCL: gtk3
Locale: en-AU (en_AU.UTF-8); UI: en-US
Calc: CL threaded

* Crash report for 7.6 with signature "SwUndoDelLayFormat::SwUndoDelLayFormat(SwFrameFormat*)": https://crashreport.libreoffice.org/stats/crash_details/220fff47-66a6-4bda-9016-46c417caefc3
* Crash report for 24.2, with signature "<name omitted>": https://crashreport.libreoffice.org/stats/crash_details/7b4074be-3256-4480-8dc4-5c340f213c76

Also crashes on Windows 11 with signature "static SwUndoId lcl_GetSwUndoId(const class SwFrameFormat *)": https://crashreport.libreoffice.org/stats/crash_details/269a2e29-083f-4cd7-9a72-939797e40167
Bibisected with linux-64-7.4 repository to first bad build [0d1ffc14abf43c7830cee6369710d8e63e2fd0ef] which points to 44eef5f494825a26594ba3d50ef1f3211ae73b9b which is a cherrypick of:

commit 1d3d2a995239c3c71432006cb795324c56a0412a
author	Attila Bakos (NISZ) 	Mon Jun 20 17:27:53 2022 +0200
committer	László Németh 	Mon Jul 11 14:09:09 2022 +0200
tdf#148687 tdf#149173 tdf#149546 sw: fix crash with textboxes
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136192

Attila seems inactive, let's up increase the priority.
Comment 1 Telesto 2024-03-22 20:08:19 UTC
This report quite similar (or very closely related) to bug 152799, except few additional steps being needed to triggering the crash.
Comment 2 David Hashe 2025-06-06 03:39:24 UTC
Assigning to myself because I have a preliminary fix. I'll try to get a more thorough explanation and a patch up in <24 hours from this comment.

It is indeed very closely related to bug 152799, and my fix should cover both.
Comment 3 David Hashe 2025-06-08 05:06:53 UTC
WIP fix for a single layer of grouping here: https://gerrit.libreoffice.org/c/core/+/186251

I'm still working on fixing it for multiple layers of nested groups. I think that I know how to do that; the code currently assumes that all textboxes are either on the group or one of its immediate children, but in fact textboxes can be on any descendant of the group draw object. SwDoc::UnGroupSelection gets this right but the undo/redo functions currently do not.
Comment 4 David Hashe 2025-06-12 04:46:57 UTC
Here is a simple way to reproduce the issue from a new document:

1. Create a first shape using: Insert > Shape > Basic Shapes > Rectangle
2. Create a second shape using: Insert > Shape > Basic Shapes > Rectangle
3. (Select and right click the first shape) > Add Text Box
4. Optionally, insert some text into the text box.
5. Select both rectangles and then group them using: Format > Group Shapes > Group
6. Immediately ungroup them using: Format > Group Shapes > Ungroup.
7. Undo.
Comment 5 David Hashe 2025-06-12 04:48:55 UTC
The key situation here is undo/redo with groups containing "Text Boxes". In Writer, a Text Box is a bit of arbitrary Writer content that is paired with a Draw object in the Draw pane of the Writer document. They were added to improve compatibility with Microsoft Office formats.

I wish that the fix were simple, but unfortunately this wasn't a single-line fix. Broadly speaking, there were issues with how the undo classes `SwUndoDrawGroup` and `SwUndoDrawUnGroup` managed the ownership and lifetimes of the text boxes, and I just had to iteratively debug/fix the issues until it worked.

This bug report was about ungrouping, but in fact there were very similar issues with grouping that also needed to be fixed.

Concretely, the two undo classes own (i.e are responsible for deleting) `SwFrameFormat`s, which are the part of the text box that holds the Writer content. There are `SwFrameFormat`s for both the individual members of the group and the group itself. Which ones the undo classes own (versus the document owning) depends on the current state of the document:

1. If the document is in a state where the members are currently grouped, then the undo class owns the `SwFrameFormat`s for the individual members, because those `SwFrameFormat`s are not currently attached to the document.
2. If the document is in a state where the members are currently ungrouped, then the undo class owns the `SwFrameFormat` for the group, because that `SwFrameFormat` is not currently attached to the document.

Note that this means that a "done group operation", "undone ungroup operation", and "redone group operations" are all similar to each other (the undo class owns the individual frames), and likewise a "done ungroup operation", "undone group operation", and "redone ungroup operation" are all similar to each other (the undo class owns the group frame).

The value of the boolean `m_bDeleteFormat` tells you which state the undo class is in.

The undo classes do not directly own the `SwTextBoxNode`s. Instead, the `SwFrameFormat`s collectively own the `SwTextBoxNode`s through shared pointers. The `SwTextBoxNode`s also have a link back to every `SwFrameFormat` that owns them. There is an asymmetry in the frames. One `SwFrameFormat` is the parent and the rest are the children.

When I was debugging issues with group/ungroup redo/undo, LibreOffice always told me that I wasn't done yet in one of two ways:

1. SwTextBoxNode::~SwTextBoxNode(): Assertion `false' failed.
2. An issue inside of SwUndoDelLayFormat::SwUndoDelLayFormat

The meaning of (1) is that an `SwTextBoxNode` is surprised that it is being deleted while it still has back links to `SwFrameFormat`s.
The meaning of (2) is that an `SwTextBoxNode` has had `ClearAll()` called on it and is trying to cause its children `SwFrameFormat`s to delete themselves and remove themselves from its member list, but they aren't deleting themselves because they are used elsewhere.

Both of these happen because `SwTextBoxNode` believes that it "owns" the children frames within it. And it does, normally, outside of the undo/redo context. But in the undo/redo context, the undo classes own the frames.

So one guiding principle of the fix is that the `SwFrameFormat`s that the undo classes own (which, again, depends on the state) should never have non-empty `SwTextBoxNode`s, because `SwTextBoxNode`s don't know how to deal with the situation of having children that they aren't responsible for deleting.

There is one more important thing to understand: in a given group, there is exactly one `SwTextBoxNode`, and no matter how many layers of nested groups there are, all of the `SwFrameFormat`s are held as direct children of that one `SwTextBoxNode`. This means that ungrouping the group involves doing a tree traversal (depth-first search) to figure out how to partition the `SwFrameFormat`s among the new `SwTextBoxNode`s that are created for the newly independent children of the former group.

So the other guiding principle of the fix is that the undo classes have to do DFS whenever moving from a state where the members are currently grouped to a state where the members are currently ungrouped. We can't avoid the DFS by having the member frames contain non-empty `SwTextBoxNode`s because, remember, `SwTextBoxNode`s don't know how to have children that they aren't responsible for deleting.
Comment 6 David Hashe 2025-06-12 04:49:47 UTC
Fixes are here:
1. For non-nested groups: https://gerrit.libreoffice.org/c/core/+/186251
2. For nested groups: https://gerrit.libreoffice.org/c/core/+/186393