Stave sharing - Part 3#33838
Conversation
Dynamics, text, etc
Because the current m_sharedItem may have been already invalidated, so don't try to use it.
📝 WalkthroughWalkthrough
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsLinked repositories: Your configuration references 1 linked repositories, but your current plan allows 0. Analyzed ``, skipped Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/engraving/rendering/score/stavesharinglayout.cpp`:
- Around line 411-417: The forward-tie compatibility check is comparing the
wrong endpoint for tie consistency. In the condition that checks
`tieFor1->startElement()` against `tieFor2->startElement()`, you are checking
the start of the ties when you should be checking the end. Replace the
`startElement()` calls with `endElement()` calls (or `endNote()` if that is the
appropriate method) to correctly compare whether forward ties have matching
open-vs-closed endpoint configurations.
- Around line 703-708: There is a null dereference bug in the tieFor cloning
logic. When the condition checks if sharedTieFor is null with if
(!sharedTieFor), the code immediately tries to call sharedTieFor->clone() inside
that block, which dereferences a null pointer and causes a crash. The fix is to
clone tieFor (the original tie object) instead of sharedTieFor when sharedTieFor
is null. Change the clone call from sharedTieFor->clone() to tieFor->clone() to
use the correct source object for cloning.
- Around line 375-395: The annotation matching logic does not enforce one-to-one
correspondence between annotations on the previous and next tracks. Currently,
when iterating through annotationsOnPrevTrack and comparing each against
candidates in annotationsOnNextTrack via equal_range, there is no mechanism to
remove or mark matched annotations as consumed. This means a single annotation
from the next track can be matched multiple times, breaking duplicate handling.
To fix this, modify the matching logic to track which annotations from
annotationsOnNextTrack have already been matched (for example, using a set of
matched keys or erasing matched items), and ensure that each annotation is only
matched once. After finding a matching annotation in the inner loop (the loop
iterating through equal_range results), immediately mark or remove it so
subsequent iterations in the outer loop cannot reuse it. This will enforce
strict one-to-one pairing of annotations between the two tracks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7217ee5e-37fa-4426-a4f4-4c3b1b31a392
📒 Files selected for processing (5)
src/engraving/dom/engravingitem.cppsrc/engraving/dom/engravingobject.cppsrc/engraving/rendering/score/stavesharinglayout.cppsrc/engraving/rendering/score/stavesharinglayout.hsrc/engraving/types/types.h
💤 Files with no reviewable changes (1)
- src/engraving/dom/engravingitem.cpp
| if (annotationsOnPrevTrack.size() != annotationsOnNextTrack.size()) { | ||
| return false; | ||
| } | ||
|
|
||
| for (auto [type, item] : annotationsOnPrevTrack) { | ||
| auto range = annotationsOnNextTrack.equal_range(type); | ||
| if (range.first == range.second) { | ||
| // No annotation of this type exists in nextTrack | ||
| return false; | ||
| } | ||
|
|
||
| for (auto i = range.first; i != range.second; ++i) { | ||
| EngravingItem* nextItem = i->second; | ||
| if (muse::contains(TEXTBASE_TYPES, type)) { | ||
| if (toTextBase(item)->xmlText() != toTextBase(nextItem)->xmlText()) { | ||
| return false; | ||
| } | ||
| } | ||
| // TODO: other types will probably need other checks | ||
| } | ||
| } |
There was a problem hiding this comment.
Annotation matching is not one-to-one and can both reject valid pairs and accept invalid multiplicities.
At Line 386, each previous annotation is compared against all same-type candidates on the next track, and matches are never consumed. This breaks duplicate handling (especially text annotations) and can misclassify same-voice compatibility.
Suggested fix
bool StaveSharingLayout::checkAnnotationsForSameVoice(Segment* segment, track_idx_t prevTrack, track_idx_t nextTrack)
{
- std::multimap<ElementType, EngravingItem*> annotationsOnPrevTrack;
- std::multimap<ElementType, EngravingItem*> annotationsOnNextTrack;
+ std::multimap<ElementType, EngravingItem*> annotationsOnPrevTrack;
+ std::multimap<ElementType, EngravingItem*> annotationsOnNextTrack;
@@
- for (auto [type, item] : annotationsOnPrevTrack) {
- auto range = annotationsOnNextTrack.equal_range(type);
- if (range.first == range.second) {
- // No annotation of this type exists in nextTrack
- return false;
- }
-
- for (auto i = range.first; i != range.second; ++i) {
- EngravingItem* nextItem = i->second;
- if (muse::contains(TEXTBASE_TYPES, type)) {
- if (toTextBase(item)->xmlText() != toTextBase(nextItem)->xmlText()) {
- return false;
- }
- }
- // TODO: other types will probably need other checks
- }
- }
+ for (auto it = annotationsOnPrevTrack.begin(); it != annotationsOnPrevTrack.end();) {
+ ElementType type = it->first;
+ auto [pBeg, pEnd] = annotationsOnPrevTrack.equal_range(type);
+ auto [nBeg, nEnd] = annotationsOnNextTrack.equal_range(type);
+ if (std::distance(pBeg, pEnd) != std::distance(nBeg, nEnd)) {
+ return false;
+ }
+ if (muse::contains(TEXTBASE_TYPES, type)) {
+ std::multiset<String> prevTexts;
+ std::multiset<String> nextTexts;
+ for (auto p = pBeg; p != pEnd; ++p) {
+ prevTexts.insert(toTextBase(p->second)->xmlText());
+ }
+ for (auto n = nBeg; n != nEnd; ++n) {
+ nextTexts.insert(toTextBase(n->second)->xmlText());
+ }
+ if (prevTexts != nextTexts) {
+ return false;
+ }
+ }
+ it = pEnd;
+ }
return true;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (annotationsOnPrevTrack.size() != annotationsOnNextTrack.size()) { | |
| return false; | |
| } | |
| for (auto [type, item] : annotationsOnPrevTrack) { | |
| auto range = annotationsOnNextTrack.equal_range(type); | |
| if (range.first == range.second) { | |
| // No annotation of this type exists in nextTrack | |
| return false; | |
| } | |
| for (auto i = range.first; i != range.second; ++i) { | |
| EngravingItem* nextItem = i->second; | |
| if (muse::contains(TEXTBASE_TYPES, type)) { | |
| if (toTextBase(item)->xmlText() != toTextBase(nextItem)->xmlText()) { | |
| return false; | |
| } | |
| } | |
| // TODO: other types will probably need other checks | |
| } | |
| } | |
| if (annotationsOnPrevTrack.size() != annotationsOnNextTrack.size()) { | |
| return false; | |
| } | |
| for (auto it = annotationsOnPrevTrack.begin(); it != annotationsOnPrevTrack.end();) { | |
| ElementType type = it->first; | |
| auto [pBeg, pEnd] = annotationsOnPrevTrack.equal_range(type); | |
| auto [nBeg, nEnd] = annotationsOnNextTrack.equal_range(type); | |
| if (std::distance(pBeg, pEnd) != std::distance(nBeg, nEnd)) { | |
| return false; | |
| } | |
| if (muse::contains(TEXTBASE_TYPES, type)) { | |
| std::multiset<String> prevTexts; | |
| std::multiset<String> nextTexts; | |
| for (auto p = pBeg; p != pEnd; ++p) { | |
| prevTexts.insert(toTextBase(p->second)->xmlText()); | |
| } | |
| for (auto n = nBeg; n != nEnd; ++n) { | |
| nextTexts.insert(toTextBase(n->second)->xmlText()); | |
| } | |
| if (prevTexts != nextTexts) { | |
| return false; | |
| } | |
| } | |
| it = pEnd; | |
| } | |
| return true; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/engraving/rendering/score/stavesharinglayout.cpp` around lines 375 - 395,
The annotation matching logic does not enforce one-to-one correspondence between
annotations on the previous and next tracks. Currently, when iterating through
annotationsOnPrevTrack and comparing each against candidates in
annotationsOnNextTrack via equal_range, there is no mechanism to remove or mark
matched annotations as consumed. This means a single annotation from the next
track can be matched multiple times, breaking duplicate handling. To fix this,
modify the matching logic to track which annotations from annotationsOnNextTrack
have already been matched (for example, using a set of matched keys or erasing
matched items), and ensure that each annotation is only matched once. After
finding a matching annotation in the inner loop (the loop iterating through
equal_range results), immediately mark or remove it so subsequent iterations in
the outer loop cannot reuse it. This will enforce strict one-to-one pairing of
annotations between the two tracks.
| Tie* tieFor = originNote->tieFor(); | ||
| if (tieFor && !tieFor->endNote()) { // If it does have an end note we add it when we process the end note | ||
| Tie* sharedTieFor = sharedNote->tieFor(); | ||
| if (!sharedTieFor) { | ||
| sharedTieFor = toTie(sharedTieFor->clone()); | ||
| sharedTieFor->setNoteSpan(sharedNote, nullptr); |
There was a problem hiding this comment.
Null dereference when creating sharedTieFor.
At Line 707, sharedTieFor is null inside the if (!sharedTieFor) block, but is immediately dereferenced for cloning. This is a deterministic crash path.
Suggested fix
Tie* sharedTieFor = sharedNote->tieFor();
if (!sharedTieFor) {
- sharedTieFor = toTie(sharedTieFor->clone());
+ sharedTieFor = toTie(tieFor->clone());
sharedTieFor->setNoteSpan(sharedNote, nullptr);
score->undoAddElement(sharedTieFor);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Tie* tieFor = originNote->tieFor(); | |
| if (tieFor && !tieFor->endNote()) { // If it does have an end note we add it when we process the end note | |
| Tie* sharedTieFor = sharedNote->tieFor(); | |
| if (!sharedTieFor) { | |
| sharedTieFor = toTie(sharedTieFor->clone()); | |
| sharedTieFor->setNoteSpan(sharedNote, nullptr); | |
| Tie* tieFor = originNote->tieFor(); | |
| if (tieFor && !tieFor->endNote()) { // If it does have an end note we add it when we process the end note | |
| Tie* sharedTieFor = sharedNote->tieFor(); | |
| if (!sharedTieFor) { | |
| sharedTieFor = toTie(tieFor->clone()); | |
| sharedTieFor->setNoteSpan(sharedNote, nullptr); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/engraving/rendering/score/stavesharinglayout.cpp` around lines 703 - 708,
There is a null dereference bug in the tieFor cloning logic. When the condition
checks if sharedTieFor is null with if (!sharedTieFor), the code immediately
tries to call sharedTieFor->clone() inside that block, which dereferences a null
pointer and causes a crash. The fix is to clone tieFor (the original tie object)
instead of sharedTieFor when sharedTieFor is null. Change the clone call from
sharedTieFor->clone() to tieFor->clone() to use the correct source object for
cloning.
miiizen
left a comment
There was a problem hiding this comment.
Spotted a few things, but this is looking so good! 😍
| EngravingItem* sharedItem = seg->findAnnotation(originItem->type(), sharedTrack, sharedTrack); | ||
| if (sharedItem && sharedItem->isTextBase()) { | ||
| if (toTextBase(sharedItem)->xmlText() != toTextBase(originItem)->xmlText()) { | ||
| sharedItem = nullptr; // Not the one we are looking for |
| std::vector<EngravingItem*> annotations = seg->annotations(); // Copy because we may remove elements | ||
| for (EngravingItem* item : annotations) { | ||
| if (item->track() >= startTrack && item->track() < endTrack) { | ||
| if (item->originItems().empty()) { |
There was a problem hiding this comment.
This could be in the condition above
| if (tieFor && !tieFor->endNote()) { // If it does have an end note we add it when we process the end note | ||
| Tie* sharedTieFor = sharedNote->tieFor(); | ||
| if (!sharedTieFor) { | ||
| sharedTieFor = toTie(sharedTieFor->clone()); |
There was a problem hiding this comment.
sharedTieFor = toTie(tieFor->clone());




No description provided.