Crash on closing Label::TextEditor on iOS

When testing an iOS build of a standalone plugin, I’m seeing a reproducible crash:

There are two editable labels (dynamically created).
When dismissing the virtual keyboard (with “return”) while editing the second label, I get the following output from asan:

_BSMachError: port 8003; (os/kern) invalid capability (0x14) "Unable to insert COPY_SEND"
=================================================================
==1542==ERROR: AddressSanitizer: heap-use-after-free on address 0x000145112038 at pc 0x000101a30c70 bp 0x00016efb46d0 sp 0x00016efb46c8
READ of size 8 at 0x000145112038 thread T0
    #0 0x101a30c6c in juce::UIViewComponentPeer::updateHiddenTextContent(juce::TextInputTarget*)+0x1c8 (/var/containers/Bundle/Application/C5B2E2CB-129F-429C-B67B-631C1EA13AA6/MyCoolPlugin.app/MyCoolPlugin:arm64+0x100be8c6c)
    #1 0x101a27a44 in juce::UIViewComponentPeer::textViewReplaceCharacters(juce::Range<int>, juce::String const&)+0x560 (/var/containers/Bundle/Application/C5B2E2CB-129F-429C-B67B-631C1EA13AA6/MyCoolPlugin.app/MyCoolPlugin:arm64+0x100bdfa44)
    #2 0x101a27328 in -[JuceUIView textView:shouldChangeTextInRange:replacementText:]+0x370 (/var/containers/Bundle/Application/C5B2E2CB-129F-429C-B67B-631C1EA13AA6/MyCoolPlugin.app/MyCoolPlugin:arm64+0x100bdf328)
    #3 0x18c9dc9f8 in <redacted>+0x9c (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x1ae9f8)
    #4 0x18ccad464 in <redacted>+0x5c (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x47f464)
    #5 0x18d5013e0 in <redacted>+0x9c (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0xcd33e0)
    #6 0x18ccac438 in <redacted>+0x118 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x47e438)
    #7 0x18ccabbe8 in <redacted>+0x40c (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x47dbe8)
    #8 0x18ccab758 in <redacted>+0x38 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x47d758)
    #9 0x18d5013e0 in <redacted>+0x9c (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0xcd33e0)
    #10 0x18ccab65c in <redacted>+0x1e4 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x47d65c)
    #11 0x18c9dc530 in <redacted>+0x2f8 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x1ae530)
    #12 0x18c9dbdf8 in <redacted>+0x6f8 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x1addf8)
    #13 0x18c9da3e0 in <redacted>+0x1f8 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x1ac3e0)
    #14 0x18c9d9288 in <redacted>+0x14c0 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x1ab288)
    #15 0x18ccc5464 in <redacted>+0xe4 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x497464)
    #16 0x18d501fa8 in <redacted>+0xbc (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0xcd3fa8)
    #17 0x18c8675ec in <redacted>+0x17c (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x395ec)
    #18 0x18ccc510c in <redacted>+0x158 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x49710c)
    #19 0x18c87195c in <redacted>+0x9ec (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x4395c)
    #20 0x18c86ce74 in <redacted>+0xc84 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x3ee74)
    #21 0x18c83de78 in <redacted>+0x150 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0xfe78)
    #22 0x18d193308 in <redacted>+0x938 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x965308)
    #23 0x18d195894 in <redacted>+0x1294 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x967894)
    #24 0x18d18e7ac in <redacted>+0x94 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x9607ac)
    #25 0x18322b778 in <redacted>+0x14 (/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:arm64+0xeb778)
    #26 0x18322b6f8 in <redacted>+0x54 (/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:arm64+0xeb6f8)
    #27 0x18322af80 in <redacted>+0xc8 (/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:arm64+0xeaf80)
    #28 0x183228b58 in <redacted>+0x414 (/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:arm64+0xe8b58)
    #29 0x183148c54 in CFRunLoopRunSpecific+0x1b0 (/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:arm64+0x8c54)
    #30 0x184ff4f80 in GSEventRunModal+0x60 (/System/Library/PrivateFrameworks/GraphicsServices.framework/GraphicsServices:arm64+0xaf80)
    #31 0x18c8a15c0 in UIApplicationMain+0xe8 (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x735c0)

This is with JUCE 6.0.8 on a device running iOS 10.

Can you post some code that reproduces this? I’m struggling to get asan to complain here. I’m using an iPad running iOS 14, but I’ve also tried a simulator device running iOS 10 with no luck.

I can maybe see how this could happen if the call to handleKeyPress() in UIViewComponentPeer::textViewReplaceCharacters() causes the text editor to be dismissed.

Can you try your test case with the following modification to that method?

        if (text == "\r" || text == "\n" || text == "\r\n")
            handleKeyPress (KeyPress::returnKey, text[0]);
        else
            target->insertTextAtCaret (text);

        if (findCurrentTextInputTarget() != nullptr)
            updateHiddenTextContent (target);

That sounds about right. Thanks, I’ll try that and report back, if it doesn’t fix it I’ll create a small example project that reproduces the issue.

I can confirm this occurs on iOS 14 in the simulator too.

Sorry for the delay. Finally got a simplified project to demonstrate the issue. The suggested fix did not remedy the crash.

It is not completely consistent, but you can replicate the behaviour by pressing Return a number of times in the iOS Simulator.

    /*******************************************************************************
 The block below describes the properties of this PIP. A PIP is a short snippet
 of code that can be read by the Projucer and used to generate a JUCE project.

 BEGIN_JUCE_PIP_METADATA

  name:             TextEditorCrashIOS
  description:      Basic test to show a crash when moving focus between TextEditor components on iOS

  dependencies:     juce_core, juce_data_structures, juce_events, juce_graphics, juce_gui_basics
  exporters:        XCODE_IPHONE

  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1

  type:             Component
  mainClass:        MainComponent

 END_JUCE_PIP_METADATA

*******************************************************************************/

#pragma once

#include <JuceHeader.h>

//==============================================================================
class MainComponent  : public juce::Component
{
public:
    //==============================================================================
    MainComponent()
    {
        setSize (600, 400);
        
        for (int i = 0; i < 3; i++)
        {
            auto newLabel = std::make_unique<juce::Label>("", "Label");
            addAndMakeVisible(*newLabel);
            newLabel->setEditable(true);
            newLabel->onEditorShow = [&, newLabel = newLabel.get()]
            {
                newLabel->getCurrentTextEditor()->addKeyListener (&labelKeyListener);
            };
            labels.push_back(std::move(newLabel));
        }
    }
    
    ~MainComponent() override
    {
        
    }

    //==============================================================================
    void paint (juce::Graphics& g) override
    {
        g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
    }
    
    void resized() override
    {
        auto r = getLocalBounds();
        r.removeFromTop(30);
        for (auto& label : labels)
        {
            label->setBounds(r.removeFromTop(30));
        }
    }

private:
    //==============================================================================
    std::vector<std::unique_ptr<juce::Label>> labels;

    struct : juce::KeyListener
    {
        bool keyPressed (const juce::KeyPress& key, juce::Component* originatingComponent) override
        {
            if (auto* label = dynamic_cast<juce::Label*>(originatingComponent->getParentComponent()))
            {
                if (key.isKeyCode (juce::KeyPress::returnKey))
                {
                    label->moveKeyboardFocusToSibling (true);
                    return true;
                }
            }

            return false;
        }
    } labelKeyListener;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

Thanks for the example - I was able to reproduce the crash and have pushed a fix here:

1 Like

Thanks @ed95 that fixed it :slight_smile:

I found another similar bug, triggered in our app but not via the example above, when a TextEditor is deleted while being focused. The following addition fixed it for me:

diff --git a/modules/juce_gui_basics/widgets/juce_Label.cpp b/modules/juce_gui_basics/widgets/juce_Label.cpp
index 8459d9e92..b69ccb334 100644
--- a/modules/juce_gui_basics/widgets/juce_Label.cpp
+++ b/modules/juce_gui_basics/widgets/juce_Label.cpp
@@ -276,7 +276,9 @@ void Label::hideEditor (bool discardCurrentEditorContents)
         const bool changed = (! discardCurrentEditorContents)
                                && updateFromTextEditorContents (*outgoingEditor);
         outgoingEditor.reset();
-        repaint();
+
+        if (deletionChecker != nullptr)
+            repaint();

         if (changed)
             textWasEdited();

Thanks, I’ve added that here:

Shouldn’t textWasEdited() not also excluded when the Label was deleted?
Or just

if (!deletionChecker) return;