Bug in DropShadower?

I am getting a crash in DropShadower.
And as far as I can see, it’s because it has a bug. Or rather a shortcoming.

My scenario:
I am creating components dynamically and attach shadows to them. Those components are also deleted dynamically.

The problem:
You cannot detach a component from a DropShadower. So even if my component is deleted, the DropShadower does not realise that.

My steps are the following:

  1. I create a component dynamically and attach a DropShadower to it. That sets the “owner” of the DropShadower to my newly created component. (“owner” is a member variable of DropShadower).
  2. I delete my component.
  3. I close the application. That triggers the destructor of the DropShadower. In the destructor of the DropShadower, it calls
    owner->removeComponentListener (this);
    But “owner” is no longer valid, because I already deleted the component in step 2. And therefore it crashes.

I think the solution would be, that DropShadower also overrides ComponentListener::componentBeingDeleted.
Then it can detect, if its “owner” is being deleted. And in that case it can set the “owner” to nullptr.
NOTE: In this context “owner” is not the same as the parent! A DropShadower can (and should) have a different parent than its “owner”. The “owner” is the component, which the DropShadower follows around.

You can reproduce the problem with the attached JUCE project.
Run the project and press the four buttons in the order they are shown. I.e. first press “Create Component 1 With Shadows”. Then press “Delete Component 1”. Then press “Force Resized” and then press “Create Component 2”.
Once you did that, close the application. And then it should crash.
Note: It does not always crash. It depends on the memory layout. If you are “lucky” the deleted object is not yet fully overwritten and might still be partly executable (and then it would not crash).

I am running JUCE 6.0.7 on OSX Big Sur (Intel).

Attached Project:
DrapShadowCrash.zip (5.3 KB)

Are you creating the DropShadower dynamically as well? I may be off base, but if you had a pointer to it, you could manually delete it after you delete your component.

@alatar Did you find a workaround?

Note that in your example, create the component, delete it, and quit the app is enougth to crash.
I thought to use DropShadower::setOwner (nullptr), but it is not allowed (pointer must be valid).
It seems not possible to detach the DropShadower explicitly.
Have you try to make the DropShadower a member of your component?

class FilledComponent : public juce::Component
{
public:
    FilledComponent() : shadower_ (juce::DropShadow (juce::Colours::black, 40, juce::Point<int>(0,0)))
    {
        shadower_.setOwner (this);
    }
    
    void paint(juce::Graphics &g) override { g.fillAll(juce::Colours::blue); }
    
private:
    juce::DropShadower shadower_;
};

It seems to work fine (note that i never used it before, thus correct me if i’m wrong :smile:).

DrapShadowCrash.zip (7.5 KB)

Thanks for your answers.

Yes, my current workaround is to make sure that I delete the DropShadower before I delete the Component it follows. That seems to work.

It just have to be a bit careful, that I don’t forget that in the future. Like in half a year, when I go revisit that code :smiley:

@stephenk
Yes in my actual app, the DropShadower is also dynamically created. But note that it must be done the other way round you are suggesting. I.e. first delete the DropShadower and afterwards delete the Component it follows.

@nicolasdanet
Ah, interesting idea. Did not try that yet. One thing which might be a bit weird with that idea:
The Component cannot be the parent of the DropShadower. In the sense, that I cannot call addAndMakeVisible(myDropShadower) from inside myComponent. Because otherwise the shadows would be clipped a the Component bounds. But other from that it might work. :slight_smile:

1 Like

Thank you for reporting!

1 Like