Paint triggered from destructor

I happened to witness a very strange crash caused by destructed list box attempting to paint itself. I don’t understand how is this possible and perhaps i’m just interpreting the stack trace wrong.

At the bottom we have ~Label and ~Component which calls ~AccessibilityHandler. It looks like the AccessibilityHandler destructor is somehow triggering a paint operation. The Sidebar::paintListBoxItem is my code and it references the viewport of the parent list box, which has been destroyed at this point and crashes in getVerticalScrollBar.

Do you have any idea what is causing the paint operation? is it intentional? and how to avoid it? It looks like a bad idea to paint a component being destroyed.

The problem is hard to reproduce and so far has occured only when using the app through Windows remote desktop connection.

you could try to put a MessageManagerLock inside the destructor so that it’s impossible for anything to paint during its scope

well, I wouldn’t do that (first I don’t think it helps, and there are good reasons to avoid MML at all)
Looks more something which should be fixed inside juce.
Looks like AccessibilityHandler runs the event-loop.

would you be so kind and explain why you want to avoid MML at all costs? it has helped me sometimes in the past, but if there’s an issue with it i’d like to know.

i should probably state that i don’t think my idea is the best solution of all time. I’d more see it as a quickfix to make things run now until the underlying problem is solved, or just to find out if that reveils another dynamic of this problem

Thanks for the suggestion @Mrugalla !

Looking at RowAccessibilityHandler::getCurrentState doesn’t give any clue to what triggers the paint. Nothing in that (const) method seems to alter any state. This is really weird.

I am trying to reproduce your stacktrace, so just to be sure can you confirm that you can reproduce this issue with the develop branch? There’s one entry in your trace where I see an offset of one line.

My first guess was that it could be a simple juce::Label returned from and destructed in ListBoxModel::refreshComponentForRow(). However that doesn’t seem to be possible as Component::~Component() clears the parent at juce_Component.cpp:516 before ~AccessibilityHandler would run.

So with an unmodified Label and a non-overridden createAccessibilityHandler() it doesn’t seem possible to reach the ListBox::RowComponent

Can you say more about the row component you are using? Are you maybe returning a custom AccessibilityHandler?

I was able to reproduce this from the latest develop-branch. It only occurs when the app is shutdown and all components are destroyed.

I’m using a custom row component which contains labels. Originally i had a custom label, but i’m able to reproduce this with using only stock juce::Label. I have to set setEditable (false, true) on the label in order to reproduce. Also some amount of scrolling is required - probably to generate some create/destroy calls for the row components. I haven’t intentionally changed anything regarding accessibility handlers.

Here’s a stack trace from develop using stock label

	<app name>.exe!std::unique_ptr<juce::ScrollBar,std::default_delete<juce::ScrollBar>>::operator*() Line 3243	C++
 	<app name>.exe!juce::Viewport::getVerticalScrollBar() Line 254	C++
 	<app name>.exe!juce::ListBox::getVerticalScrollBar() Line 1003	C++
 	<app name>.exe!Sidebar::scrollbarWidthCompensation() Line 35	C++
 	<app name>.exe!Sidebar::paintListBoxItem(int rowNumber, juce::Graphics & g, int width, int height, bool rowIsSelected) Line 47	C++
 	<app name>.exe!juce::ListBox::RowComponent::paint(juce::Graphics & g) Line 64	C++
 	<app name>.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 1986	C++
 	<app name>.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 2087	C++
 	<app name>.exe!juce::Component::paintWithinParentContext(juce::Graphics & g) Line 1971	C++
 	<app name>.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 2028	C++
 	<app name>.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 2087	C++
 	<app name>.exe!juce::Component::paintWithinParentContext(juce::Graphics & g) Line 1971	C++
 	<app name>.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 2028	C++
 	<app name>.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 2087	C++
 	<app name>.exe!juce::Component::paintWithinParentContext(juce::Graphics & g) Line 1971	C++
 	<app name>.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 2028	C++
 	<app name>.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 2087	C++
 	<app name>.exe!juce::Component::paintWithinParentContext(juce::Graphics & g) Line 1971	C++
 	<app name>.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 2028	C++
 	<app name>.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 2087	C++
 	<app name>.exe!juce::Component::paintWithinParentContext(juce::Graphics & g) Line 1971	C++
 	<app name>.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 2028	C++
 	<app name>.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 2087	C++
 	<app name>.exe!juce::Component::paintWithinParentContext(juce::Graphics & g) Line 1971	C++
 	<app name>.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 2028	C++
 	<app name>.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 2087	C++
 	<app name>.exe!juce::Component::paintWithinParentContext(juce::Graphics & g) Line 1971	C++
 	<app name>.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 2028	C++
 	<app name>.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 2087	C++
 	<app name>.exe!juce::Component::paintWithinParentContext(juce::Graphics & g) Line 1971	C++
 	<app name>.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 2028	C++
 	<app name>.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 2087	C++
 	<app name>.exe!juce::ComponentPeer::handlePaint(juce::LowLevelGraphicsContext & contextToPaintTo) Line 132	C++
 	<app name>.exe!juce::HWNDComponentPeer::performPaint(HDC__ * dc, HRGN__ * rgn, int regionType, tagPAINTSTRUCT & paintStruct) Line 2663	C++
 	<app name>.exe!juce::HWNDComponentPeer::handlePaintMessage() Line 2557	C++
 	<app name>.exe!juce::HWNDComponentPeer::peerWindowProc(HWND__ * h, unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 3687	C++
 	<app name>.exe!juce::HWNDComponentPeer::windowProc(HWND__ * h, unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 3627	C++
 	[External Code]	
 	<app name>.exe!juce::ListBox::RowComponent::RowAccessibilityHandler::getCurrentState() Line 217	C++
 	<app name>.exe!juce::AccessibilityHandler::isIgnored() Line 89	C++
 	<app name>.exe!juce::getUnignoredAncestor(juce::AccessibilityHandler * handler) Line 163	C++
 	<app name>.exe!juce::AccessibilityHandler::getParent() Line 200	C++
 	<app name>.exe!juce::notifyAccessibilityEventInternal(const juce::AccessibilityHandler & handler, juce::InternalAccessibilityEvent eventType) Line 161	C++
 	<app name>.exe!juce::AccessibilityHandler::AccessibilityHandler(juce::Component & comp, juce::AccessibilityRole accessibilityRole, juce::AccessibilityActions accessibilityActions, juce::AccessibilityHandler::Interfaces interfacesIn) Line 66	C++
 	<app name>.exe!juce::LabelAccessibilityHandler::LabelAccessibilityHandler(juce::Label & labelToWrap) Line 531	C++
 	[External Code]	
 	<app name>.exe!juce::Label::createAccessibilityHandler() Line 586	C++
 	<app name>.exe!juce::Component::getAccessibilityHandler() Line 3220	C++
 	<app name>.exe!juce::Component::internalKeyboardFocusLoss(juce::Component::FocusChangeType cause) Line 2734	C++
 	<app name>.exe!juce::ComponentPeer::handleFocusLoss() Line 363	C++
 	<app name>.exe!juce::HWNDComponentPeer::peerWindowProc(HWND__ * h, unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 3826	C++
 	<app name>.exe!juce::HWNDComponentPeer::windowProc(HWND__ * h, unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 3627	C++
 	[External Code]	
 	<app name>.exe!juce::ListBox::RowComponent::RowAccessibilityHandler::getCurrentState() Line 217	C++
 	<app name>.exe!juce::AccessibilityHandler::isIgnored() Line 89	C++
 	<app name>.exe!juce::getUnignoredAncestor(juce::AccessibilityHandler * handler) Line 163	C++
 	<app name>.exe!juce::AccessibilityHandler::getParent() Line 200	C++
 	<app name>.exe!juce::notifyAccessibilityEventInternal(const juce::AccessibilityHandler & handler, juce::InternalAccessibilityEvent eventType) Line 161	C++
 	<app name>.exe!juce::AccessibilityHandler::AccessibilityHandler(juce::Component & comp, juce::AccessibilityRole accessibilityRole, juce::AccessibilityActions accessibilityActions, juce::AccessibilityHandler::Interfaces interfacesIn) Line 66	C++
 	<app name>.exe!juce::LabelAccessibilityHandler::LabelAccessibilityHandler(juce::Label & labelToWrap) Line 531	C++
 	[External Code]	
 	<app name>.exe!juce::Label::createAccessibilityHandler() Line 586	C++
 	<app name>.exe!juce::Component::getAccessibilityHandler() Line 3220	C++
 	<app name>.exe!juce::Component::internalKeyboardFocusGain(juce::Component::FocusChangeType cause, const juce::WeakReference<juce::Component,juce::ReferenceCountedObject> & safePointer) Line 2717	C++
 	<app name>.exe!juce::Component::takeKeyboardFocus(juce::Component::FocusChangeType cause) Line 2874	C++
 	<app name>.exe!juce::Component::grabKeyboardFocusInternal(juce::Component::FocusChangeType cause, bool canTryParent) Line 2887	C++
 	<app name>.exe!juce::Component::grabKeyboardFocusInternal(juce::Component::FocusChangeType cause, bool canTryParent) Line 2897	C++
 	<app name>.exe!juce::Component::grabKeyboardFocusInternal(juce::Component::FocusChangeType cause, bool canTryParent) Line 2906	C++
 	<app name>.exe!juce::Component::grabKeyboardFocusInternal(juce::Component::FocusChangeType cause, bool canTryParent) Line 2906	C++
 	<app name>.exe!juce::Component::grabKeyboardFocus() Line 2920	C++
 	<app name>.exe!juce::Component::removeChildComponent(int index, bool sendParentEvents, bool sendChildEvents) Line 1581	C++
 	<app name>.exe!juce::Component::~Component() Line 516	C++
 	<app name>.exe!juce::Label::~Label() Line 49	C++
 	[External Code]	
 	<app name>.exe!juce::ContainerDeletePolicy<juce::ListBox::RowComponent>::destroy(juce::ListBox::RowComponent * object) Line 54	C++
 	<app name>.exe!juce::OwnedArray<juce::ListBox::RowComponent,juce::DummyCriticalSection>::deleteAllObjects() Line 864	C++
 	<app name>.exe!juce::OwnedArray<juce::ListBox::RowComponent,juce::DummyCriticalSection>::~OwnedArray<juce::ListBox::RowComponent,juce::DummyCriticalSection>() Line 65	C++
 	[External Code]	
 	<app name>.exe!juce::ListBox::~ListBox() Line 538	C++
 	[External Code]	
 	<app name>.exe!Sidebar::~Sidebar() Line 18	C++
 	[External Code]	
 	<app name>.exe!ProjectEditorView::~ProjectEditorView() Line 28	C++
 	<app name>.exe!MainComponent::~MainComponent() Line 159	C++
 	[External Code]	
 	<app name>.exe!juce::Component::SafePointer<juce::Component>::deleteAndZero() Line 2314	C++
 	<app name>.exe!juce::ResizableWindow::clearContentComponent() Line 108	C++
 	<app name>.exe!juce::ResizableWindow::~ResizableWindow() Line 58	C++
 	<app name>.exe!juce::DocumentWindow::~DocumentWindow() Line 79	C++
 	[External Code]	
 	<app name>.exe!<app name>Application::shutdown() Line 37	C++
 	<app name>.exe!juce::JUCEApplicationBase::shutdownApp() Line 324	C++
 	<app name>.exe!juce::JUCEApplicationBase::main() Line 266	C++
 	<app name>.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) Line 111	C++
 	[External Code]	

Are any of the leak detectors ever firing on shutdown, or are you using a custom implementation of refreshComponentForRow? If so, please could you inspect it for potential leaks, or paste the code here so that I can see if it looks reasonable?

I just found a similar issue in the InAppPurchasesDemo which was caused by a faulty implementation of refreshComponentForRow. The repro steps were similar (needed a bit of scrolling on the list view in order to cause the leak), as were the stack traces I was seeing. I was generally only seeing leak detector reports, rather than crashes, although I did see a crash once. I suspect that if a row component is leaked, it may end up trying to participate in some accessibility-related calls, which might in turn cause crahses.

No leak detectors finring.

Sure, the correct way of implementing refreshComponentForRow is still a bit unclear to me. Please let me know if you spot any possible weaknesses in there. Here’s the implementation

juce::Component* SectionPanel::refreshComponentForRow (int rowNumber, bool isSelected, juce::Component* existingComponentToUpdate) {
	if (rowNumber >= 0 && rowNumber < getNumRows ()) {
		auto item = dynamic_cast<Item*> (existingComponentToUpdate);
		if (item == nullptr) {
			if (existingComponentToUpdate != nullptr) {
				delete existingComponentToUpdate;
			}
			std::unique_ptr<Item> i (new Item (rowNumber, &listBox, [this] { return scrollbarWidthCompensation (); }));
			i->label.setFont (juce::Font (Theme::Fonts::SubtitleFontSize));
			i->label.setColour (juce::TextEditor::ColourIds::highlightColourId, Theme::Colours::dark);
			i->label.setMinimumHorizontalScale (1.f);
			i->label.addListener (this);
			item = i.release ();
		}
		item->label.row = rowNumber;
		item->label.setText (getTextForRow (rowNumber, isSelected), juce::NotificationType::dontSendNotification);
		item->label.setColour (juce::Label::ColourIds::textColourId, isSelected ? Theme::Colours::light : listBox.getLookAndFeel ().findColour (juce::ListBox::ColourIds::textColourId));
		item->containsInfo = getInfoForRow (rowNumber, isSelected) != juce::String ();
		return item;
	}
	else {
		if (existingComponentToUpdate != nullptr) {
			delete existingComponentToUpdate;
		}
		return nullptr;
	}
}

The Item class

struct Item : public juce::Component {

	const int NumberTextWidth = 5 * Theme::Layout::Unit;
	const int InfoTextWidth = 12 * Theme::Layout::Unit;

	struct Label : public juce::Label {

		Label (int row, juce::ListBox* listBox) : row (row), listBox (listBox) {
			setColour (juce::Label::ColourIds::textWhenEditingColourId, Theme::Colours::light);
			setColour (juce::Label::ColourIds::textWhenEditingColourId, Theme::Colours::light);
			setColour (juce::Label::ColourIds::backgroundWhenEditingColourId, Theme::Colours::dark);
			setEditable (false, true);
		}
		
		void mouseDown (const juce::MouseEvent& event) override {
			if (listBox->isEnabled ()) {
				listBox->selectRowsBasedOnModifierKeys (row, event.mods, false);
			}
			juce::Label::mouseDown (event);
		}
		
		void mouseUp (const juce::MouseEvent& event) override {
			if (listBox->isEnabled ()) {
				listBox->getModel ()->listBoxItemClicked (row, event);
			}
			juce::Label::mouseUp (event);
		}
		
		void resized () override {
			juce::Label::resized ();
			updateTooltip ();
		}
		
		void textWasChanged () override {
			juce::Label::textWasChanged ();
			updateTooltip ();
		}
		
		void updateTooltip () {
			setTooltip (getFont().getStringWidthFloat (getText ()) > getWidth ()
			            ? getText ()
			            : juce::String ());
		}
		
		int row;
		juce::ListBox* listBox;
	};
	
	Item (int row, juce::ListBox* listBox, std::function<int()> scrollbarWidthCompensation) : label (row, listBox), scrollbarWidthCompensation (scrollbarWidthCompensation) {
		setInterceptsMouseClicks (false, true);
		addAndMakeVisible (&label);
	}
	
	void resized () override {
		label.setBounds (getLocalBounds ().withTrimmedLeft (RowMarginWidth + NumberTextWidth)
		                                  .withTrimmedTop (10)
		                                  .withTrimmedBottom (10)
		                                  .withTrimmedRight (InfoTextWidth + Theme::Layout::MarginSize - scrollbarWidthCompensation ()));
	}
	
	Label label;
	std::function<int()> scrollbarWidthCompensation;
	bool containsInfo = false;
};

Can you make anything out of the fact that i can repro this only over Windows remote desktop connection? Never seen it on local device, win or mac.

I think that implementation looks like it should work. The problem must be elsewhere, but unfortunately I can’t think where that might be at the moment. I’m also not sure why running over remote desktop would have an effect. I’ll keep an eye out for similar problems and update this thread if I think of anything else to try.

1 Like

For the sake of completeness, the InAppPurchasesDemo fix is here:

Update: The problem is no longer limited to instances running on remote desktop. I have it right here on my laptop, 100% reproducible.

I have the Sidebar class inheriting ListBoxModel. The actual ListBox is a member of this class. Could this cause some issue, e.g. with the order of destruction, i’m unable to see?

I can avoid the crash by

  1. clearing the model contents, so that there are no rows to render, or by
  2. setting the model property of the list box to nullptr

in the destructor.

Are you able to share a minimal example that exhibits this behaviour? We have ListBox members of ListBoxModels in some JUCE components and examples, which all appear to be working as expected. It feels like there might be something else going on.