Access violation when allocating memory


#1

I am calling

Foo::doSomething(const String& text) {..}

with

foo.doSomething(juce::String(number));

dozens of times without issues. But about the 50th time I am calling it, it crashes. So right before the crash, my code calls juce's

ArrayAllocationBase::setAllocatedSize (const int numElements)

Which calls malloc in my usecase with a "size" of 32.

Which calls

_malloc_base

and which fails to add memory. So we're hitting the "return nullptr" at the end of this code:

// This function implements the logic of malloc().  It is called directly by the
// malloc() function in the Release CRT and is called by the debug heap in the
// Debug CRT.
extern "C" _CRTRESTRICT void* __cdecl _malloc_base(size_t const size)
{
    // Ensure that the requested size is not too large:
    _VALIDATE_RETURN_NOEXC(_HEAP_MAXREQ >= size, ENOMEM, nullptr);

    // Ensure we request an allocation of at least one byte:
    size_t const actual_size = size == 0 ? 1 : size;

    for (;;)
    {
        void* const block = HeapAlloc(__acrt_heap, 0, actual_size);
        if (block)
            return block;

        // Otherwise, see if we need to call the new handler, and if so call it.
        // If the new handler fails, just return nullptr:
        if (_query_new_mode() == 0 || !_callnewh(actual_size))
        {
            errno = ENOMEM;
            return nullptr;
        }

        // The new handler was successful; try to allocate again...
    }
}

 

Which results in a crash - "at 0x000CD2E7 in MidiShaperTests.ext: 0xC0000005: Access violation when writing at position 0xFFFFFFF8".
    
At the time of the crash my application uses about 26 MB of memory.

 

This only happens with the release version when built with Visual Studio Community 2015 (it does not crash with Visual Studio Community 2013), and only with highest optimization (/Ox). It does not happen with the 64bit version, and it also does not happen with the Debug version or when running on Mac. But of course I would doubt that the optimization does something bad, more likely this setup uncovers a serious bug.

Having tested with valgrind on Mac I got no errors besides this - most likely unrelated - one at http://www.juce.com/forum/topic/valgrind-reports-error-getdisplayfromscreen

Also adding some code to check for memory overwrites (as described at https://msdn.microsoft.com/en-us/library/52bz2688.aspx) does not raise an error. Also trying with the Debug heap in Visual or with XCode did not report any issues.

When I change some code at unrelated places, the error sometimes does not occur any more, or happens at different places (but in my case only when juce's String objects are involved.. no idea if this is by by chance, though).

 

Did anyone ever experience the same issue, or has a good idea what it could be or how to isolate the source of the issue?

 


#2

You're doing something that's corrupting heap memory somewhere. I'm afraid these are very hard bugs to track down, as the place where you're messing up may be nowhere near the place where you see the crash, and it's hard to give useful advice about how to tackle it.

Passing a non-const String& parameter is a bad sign BTW. You should make sure you properly understand good use of constness, as that'll help avoid the kind of silly bugs that you're getting here.


#3

Thanks Jules. Regarding const-ness: Uh, sorry, it's actually "const String&" in the code, I was just forgetting the "const" when writing the posting (I've just corrected it in the posting above now).


#4

:)

Good luck hunting your memory error!


#5

My bet is on out-of-bounds array access or uninitilized member variables, but of course it's hard to tell from a distance.

As Jules mentioned, those things can be hard to track down. But here are some suggestions for trying to round down the culprit:

  • enable jasserts in release builds via the introjucer
  • enable C++ runtime checks in Release builds
  • double check, that you initialize all member variables that are raw pointers, make sure you don't double delete. null them after you free them, check (assert) them before accessing them.
  • add jasserts for boundary checks before accessing/modifying objects in arrays
  • add asserts/jasserts to all places dealing with pointers/arrays, make sure they're enabled in release builds
  • make sure, that your application and all dependencies link against the same runtime (Debug vs. Release, static vs. dynamic, single-threaded vs multi-threaded, VS version)
  • make a full rebuild
  • try to figure out which address is corrupted, use memory breakpoints to find where it is modified.
  • if all this does not shed any light on the issue, I'd start to comment out code to narrow it down

Hope some of this helps,
-- Benjamin


#6

Thank you Benjamin for this long massive list of ideas! Sadly, I've already tried most of them with no luck. But the 2nd last - can you tell me more on that?

 

enable jasserts in release builds via the introjucer

I just tried this, and I did not get any assertion. But interestingly, the crash disappears then. But I doubt that Jules has some code with side effects in a "jassert" statement, more likely this is due to the Heisenbug nature of the bug - it randomly disappears or pops up somewhere else with any unrelated code changes.

 

enable C++ runtime checks in Release builds

Yes. This does not inform me of any issue in my case.
 

double check, that you initialize all member variables that are raw pointers, make sure you don't double delete. null them after you free them, check (assert) them before accessing them.

All good. I even removed all delete statements once for testing.

 

add jasserts for boundary checks before accessing/modifying objects in arrays

Absolutely. I replaced all my arrays with std::array, so that should be good.

 

add asserts/jasserts to all places dealing with pointers/arrays, make sure they're enabled in release builds

I'll do that.

 

make sure, that your application and all dependencies link against the same runtime (Debug vs. Release, static vs. dynamic, single-threaded vs multi-threaded, VS version)

No dependencies besides Juce.

 

make a full rebuild

Yes.

 

try to figure out which address is corrupted, use memory breakpoints to find where it is modified.

This got me very curious. Do you some more hints for me on this point?

 

if all this does not shed any light on the issue, I'd start to comment out code to narrow it down

I did. Until I removed about 50% of my code, the program kept crashing (although it crashed at different lines, in the same long UI constructor). Now when I remove a random single line the crash is very likely to disappear.


#7

Ha - Heisenbug. Good one!

Please be aware, that operator [] of the stl containers might not do bounds checking. use .at() if you want boundary checks, or use the _ITERATOR_DEBUG_LEVEL/_GLIBCXX_DEBUG preprocessor defines.

Since you use std::array is the crash still hapening because _malloc_base returns nullptr?

What is the size parameter ending up in _malloc_base? If that's screwed up, it should be easy to walk up the stack and see which member variable is corrupted and cause the misscalculation. Then you can set a memory breakpoint and see exactly when the memory is corrupted. The size parameter being screwed up might explaint, why _malloc_base returns null and it does not happen on 64bit.

_malloc_base calls HeapAlloc, which might fail because of a heap corruption. So it would be helpful, if we could see what HeapAlloc is doing and at which point it decides to return nullptr. Maybe http://www.informit.com/articles/article.aspx?p=1081496&seqNum=2 can be of help.

Other than that it's hard to tell from a distance

-- Benjamin


#8

One gotcha you might be encountering is that code inside jassert is not ran in release. It should ​only be used for debugging code. This might explain why your error disappears when you enable assertions.

Hope this helps!


#9

You mean stuff like

int* x;
assert(x = new int); // <- don't do that!
*x = 100; // <- randomly explodes when asserts are disabled.

 


#10

Yes, exactly like that :)


#11

Thank you Benjamin, Jules and Joshua Gerrard for your answers.

I've removed code to isolate the issue. Sometimes the crash disappeared after I removed some code, but I had it popping up again when I added some more UI elements.

So the code I have now is only for testing, leaks memory badly, but - and that's the interesting part: The code is now only 200 lines, it's absolutely straightforward, but still crashes.

 

Here is the full thing to reproduce the crash (when built with Visual Studio Community 2015 on Windows 10, 32bit Release, static runtime, Optimisation set to "Maximise speed" and with Whole Program Optimisation set to "Enable link-time code generation when possible" in IntroJucer. No additional other flags. And also when using the latest version of JUCE from today.

Jules and Joshua Gerrard, can you have a look? I know it's a lot to ask for, but I would be very glad if you can. Also, because it is so straightforward, I wonder if it uncovers an incompatibility with these Visual Studio settings and JUCE.

 

Note that the code has a random looking combination of new menus and menu entries.. the problem is, removing single lines sometimes stops the crash, so I kept it this way.

 

CgMain.cpp:

#include "CgComponent.h"

int main(int argc, char* argv[])
{
    std::cout << "START\n";

    // Do a couple of loops, because the first one(s) might not crash yet.
    for(int i = 0; i < 32; ++i)
    {
        std::cout << i << "  ";
        CgComponent component;
    }

    std::cout << "END\n";

    return 0;
};

 

CgMenu.h:

#ifndef __CGMENU_H__
#define __CGMENU_H__

#include "JuceHeader.h"

class CgMenu: public juce::ComboBox
{
public:
    CgMenu() { };

    virtual ~CgMenu() { };

public:
    void appendItem(const juce::String& newItemText)
    {
        addItem(newItemText, getNumItems() + 1);
    };
};

#endif

 

CgComponent.h:

#ifndef __CGCOMPONENT_H__
#define __CGCOMPONENT_H__

#include "CgMenu.h"

class CgComponent: public juce::Component
{
public:
    CgComponent()
    {
        {
            CgMenu* menu = addMenu();
            menu->appendItem("In From Host (VST Only)");

            menu = addMenu();
            menu->appendItem("All");
            for(int i = 1; i <= 16; ++i)
                menu->appendItem(juce::String(i));

            menu = addMenu();
            menu->appendItem("All Notes");
            for(int i = 0; i < 9; ++i)
                menu->appendItem("Octave " + juce::String(i));

            for(int i = 0; i < 160; ++i)
            {
                menu = addMenu();
                menu->appendItem("C");
                menu->appendItem("C#");
                menu->appendItem("D");
                menu->appendItem("D#");
                menu->appendItem("E");
                menu->appendItem("F");
                menu->appendItem("F#");
                menu->appendItem("G");
                menu->appendItem("G#");
                menu->appendItem("A");
                menu->appendItem("A#");
                menu->appendItem("B");
            }
        }

        {
            CgMenu* menu = addMenu();
            menu->appendItem("Out To Host (VST Only)");

            menu = addMenu();
            for(int i = 0; i < 16; ++i)
                menu->appendItem(juce::String(i));
        }

        for(int i = 0; i < 16; ++i)
        {
            CgMenu* menu = addMenu();
            menu->appendItem(juce::String(i));
        }

        {
            CgMenu* const menu = addMenu();
            menu->appendItem("Foo");
            menu->appendItem("Foo");
            menu->appendItem("Foo");
            menu->appendItem("Foo");
            menu->appendItem("Foo");
            menu->appendItem("Foo");
            menu->appendItem("Foo");
            menu->appendItem("Foo");

            for(int i = 0; i < 1000; ++i)
                menu->appendItem(juce::String(i));
        }

        {
            CgMenu* menu = addMenu();
            menu->appendItem("Off");
            for(int j = 0; j < 128; ++j)
                menu->appendItem(juce::String(j));
        }

        for(int i = 0; i < 4; ++i)
        {
            CgMenu* menu = addMenu();
            menu->appendItem("Beat Synced");
            menu->appendItem("Beat Retrig.");
            menu->appendItem("Beat 1-Shot");
            menu->appendItem("Hertz Synced");
            menu->appendItem("Hertz Retrig.");
            menu->appendItem("Hertz 1-Shot");
            menu->appendItem("Pitch -> Rate");

            menu = addMenu();
            menu->appendItem("1/128");
            menu->appendItem("1/64");
            menu->appendItem("1/32");
            menu->appendItem("1/16");
            menu->appendItem("1/8");
            menu->appendItem("1/8d (3/16)");
            menu->appendItem("1/4");
            menu->appendItem("1/4d (3/8)");
            menu->appendItem("1/2");
            menu->appendItem("3/4");
            menu->appendItem("1 Bar (4/4)");
            menu->appendItem("1.5 Bars (6/4)");
            menu->appendItem("2 Bars (8/4)");
            menu->appendItem("3 Bars (12/4)");
            menu->appendItem("4 Bars");
            menu->appendItem("8 Bars");
            menu->appendItem("16 Bars");
            menu->appendItem("32 Bars");

            menu = addMenu();
            menu->appendItem("LFO Off");
            for(int j = 0; j < 10; ++j)
                menu->appendItem("Wave " + juce::String(j + 1));
            menu->appendItem("Noise");

            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Blo " + juce::String(i) + " -- " + juce::String(j));
        }

        for(int i = 0; i < 4; ++i)
        {
            CgMenu* menu = addMenu();
            menu->appendItem("EG Off");
            menu->appendItem("EG On");

            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Bla " + juce::String(i) + " -- " + juce::String(j));

            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Bla " + juce::String(i) + " -- " + juce::String(j));

            menu = addMenu();
            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Gnar gnar" + juce::String(j));

            menu = addMenu();
            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Blub blub" + juce::String(j));

            menu = addMenu();
            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Mars" + juce::String(j));

            menu = addMenu();
            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Mond" + juce::String(j));

            for(int j = 0; j < 1000; ++j)
            {
                menu = addMenu();
                menu->appendItem("Sterne" + juce::String(j));
                menu->appendItem("Sterne" + juce::String(j));
            }
        }

        for(int i = 0; i < 4; ++i)
        {
            CgMenu* menu = addMenu();
            menu->appendItem("EG Off");
            menu->appendItem("EG On");

            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Bla " + juce::String(i) + " -- " + juce::String(j));

            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Bla " + juce::String(i) + " -- " + juce::String(j));

            menu = addMenu();
            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Gnar gnar" + juce::String(j));

            menu = addMenu();
            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Blub blub" + juce::String(j));

            menu = addMenu();
            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Mars" + juce::String(j));

            menu = addMenu();
            for(int j = 0; j < 1000; ++j)
                menu->appendItem("Mond" + juce::String(j));

            for(int j = 0; j < 1000; ++j)
            {
                menu = addMenu();
                menu->appendItem("Sterne" + juce::String(j));
                menu->appendItem("Sterne" + juce::String(j));
            }
        }
    };

    virtual ~CgComponent() { };

private:
    CgMenu* addMenu(const juce::String& text = "")
    {
        return new CgMenu();
    };
};

#endif

 

Juce's AppConfig.h:

/*

    IMPORTANT! This file is auto-generated each time you save your
    project - if you alter its contents, your changes may be overwritten!

    There's a section below where you can add your own custom code safely, and the
    Introjucer will preserve the contents of that block, but the best way to change
    any of these definitions is by using the Introjucer's project settings.

    Any commented-out settings will assume their default values.

*/

#ifndef __JUCE_APPCONFIG_FM0ZLV__
#define __JUCE_APPCONFIG_FM0ZLV__

//==============================================================================
// [BEGIN_USER_CODE_SECTION]

// (You can add your own code in this section, and the Introjucer will not overwrite it)

// [END_USER_CODE_SECTION]

//==============================================================================
#define JUCE_MODULE_AVAILABLE_juce_core                 1
#define JUCE_MODULE_AVAILABLE_juce_data_structures      1
#define JUCE_MODULE_AVAILABLE_juce_events               1
#define JUCE_MODULE_AVAILABLE_juce_graphics             1
#define JUCE_MODULE_AVAILABLE_juce_gui_basics           1

//==============================================================================
#ifndef    JUCE_STANDALONE_APPLICATION
 #define   JUCE_STANDALONE_APPLICATION 1
#endif

//==============================================================================
// juce_core flags:

#ifndef    JUCE_FORCE_DEBUG
 //#define JUCE_FORCE_DEBUG
#endif

#ifndef    JUCE_LOG_ASSERTIONS
 //#define JUCE_LOG_ASSERTIONS
#endif

#ifndef    JUCE_CHECK_MEMORY_LEAKS
 //#define JUCE_CHECK_MEMORY_LEAKS
#endif

#ifndef    JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
 //#define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
#endif

#ifndef    JUCE_INCLUDE_ZLIB_CODE
 //#define JUCE_INCLUDE_ZLIB_CODE
#endif

#ifndef    JUCE_USE_CURL
 //#define JUCE_USE_CURL
#endif

//==============================================================================
// juce_graphics flags:

#ifndef    JUCE_USE_COREIMAGE_LOADER
 //#define JUCE_USE_COREIMAGE_LOADER
#endif

#ifndef    JUCE_USE_DIRECTWRITE
 //#define JUCE_USE_DIRECTWRITE
#endif

//==============================================================================
// juce_gui_basics flags:

#ifndef    JUCE_ENABLE_REPAINT_DEBUGGING
 //#define JUCE_ENABLE_REPAINT_DEBUGGING
#endif

#ifndef    JUCE_USE_XSHM
 //#define JUCE_USE_XSHM
#endif

#ifndef    JUCE_USE_XRENDER
 //#define JUCE_USE_XRENDER
#endif

#ifndef    JUCE_USE_XCURSOR
 //#define JUCE_USE_XCURSOR
#endif

#endif  // __JUCE_APPCONFIG_FM0ZLV__

 

 

The only thing which might be "special" is this (in CgComponent.h):

CgMenu* addMenu(const juce::String& text = "")
{
    return new CgMenu();
};

That should not raise any issues, should it?

 


Thank you!


#12

It can be further simplified:


#include "JuceHeader.h"
void addMenu(const juce::String& text = ""){}
void appendItem(const juce::String& t) {}
void crash_test()
{
    {
        addMenu();
        appendItem("");
        addMenu();
        appendItem("");
        for (int i = 1; i <= 16; ++i)
            appendItem(juce::String(i));
        addMenu();
        appendItem("");
        for (int i = 0; i < 9; ++i)
            appendItem("" + juce::String(i));
        for (int i = 0; i < 160; ++i)
        {
            addMenu();
            appendItem("");
            appendItem("");
            appendItem("");
            appendItem("");
            appendItem("");
            appendItem("");
            appendItem("");
            appendItem("");
            appendItem("");
            appendItem("");
            appendItem("");
            appendItem("");
        }
    }
    {
        addMenu();
        appendItem("");
        addMenu();
        for (int i = 0; i < 16; ++i)
            appendItem(juce::String(i));
    }
    for (int i = 0; i < 16; ++i)
    {
        addMenu();
        appendItem(juce::String(i));
    }
    {
        addMenu();
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        for (int i = 0; i < 1000; ++i)
            appendItem(juce::String(i));
    }
    {
        addMenu();
        appendItem("");
        for (int j = 0; j < 128; ++j)
            appendItem(juce::String(j));
    }
    for (int i = 0; i < 4; ++i)
    {
        addMenu();
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        addMenu();
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        appendItem("");
        addMenu();
        appendItem("");
        for (int j = 0; j < 10; ++j)
            appendItem("" + juce::String(j + 1));
        appendItem("");
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(i) + " -- " + juce::String(j));
    }
    for (int i = 0; i < 0; ++i)
    {
        addMenu();
        appendItem("");
        appendItem("");
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(i) + " -- " + juce::String(j));
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(i) + " -- " + juce::String(j));
        addMenu();
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(j));
        addMenu();
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(j));
        addMenu();
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(j));
        addMenu();
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(j));
        for (int j = 0; j < 1000; ++j)
        {
            addMenu();
            appendItem("" + juce::String(j));
            appendItem("" + juce::String(j));
        }
    }
    for (int i = 0; i < 0; ++i)
    {
        addMenu();
        appendItem("");
        appendItem("");
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(i) + " -- " + juce::String(j));
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(i) + " -- " + juce::String(j));
        addMenu();
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(j));
        addMenu();
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(j));
        addMenu();
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(j));
        addMenu();
        for (int j = 0; j < 1000; ++j)
            appendItem("" + juce::String(j));
        for (int j = 0; j < 1000; ++j)
        {
            addMenu();
            appendItem("" + juce::String(j));
            appendItem("" + juce::String(j));
        }
    }
}
int main(int argc, char* argv[])
{
    crash_test();
    return 0;
};

The only Juce class used is String. Maybe it's a bug in the microsoft compiler (woudln't be the first time). Maybe it's related to how memory for StringHolder is handled. From my observations, it looks like the memory right before a StringHolder is somehow zeroed. Unfortunately adding a memory-breakpoint changes the behaviour. (Heisenbug :)

Nevertheless. I don't think it's your fault.

Ben

 


#13

Can't see anything wrong with it.. You have some loops that will never be executed - obviously you get the same result when you remove them? And what about if you just merge together adjacent loops into a smaller lump of code that does the same number of iterations? Does the number of iterations make a difference?


#14

Yep, that's part of the oddity. removing the loops that loop zero times makes a difference. Also removing the default initialisation from 
addMenu makes a difference. removing this unused parameter also makes a difference. o_O


#15

If removing the dummy loops makes a difference then something is very, very fucked-up in your compiler!

I suggest you burn your computer and start again with a new one.


#16

Well, I have already tried shielding my computer with tin-foil against neutrino radiation, but the problem persists :)

But seriously: because it happens on my and Jakobs (and probably your) computer it's probably save to assume, that it's in fact a bug in the microsoft compiler.

Someone should file a Bug with them.

Ben


#17

Sorry that I only re-join the discussion now.

Jules, is the call to "appendItem" really a dummy-loop?

void appendItem(const juce::String& t) {}

void crash_test()
{
    ..
    appendItem("");
    ..
};

It creates a String object. We'd hope that the compiler looks at all code that is called by this and that the compiler sees that this has no side-effects and can be removed.

Maybe the compiler creates the temporary String object. But even if it doesn't, we might still get a slightly different executable, with memory laid out a tad differently.

 

Anyway, the code still crashes with the new Visual Studio 2015.1 update. Jules or your team, I would be very thankful if you still could have a look at it and see if the crash uncovers an issue with JUCE.

Benjamin did a great job of simplifying my code. You just need to set up a Visual 2015 project with Introjucer: 32bit build on 64bit Windows 10, with the juce_core module, "Maximise speed", static runtime, "Enable link-time code generation when possible". And with Benjamin's code of post #12.


#18

I very much doubt that this is anything that's our fault, but if someone can send us a minimal project that we can just run, and which will definitely go wrong, then I'll have a quick look.


#19

Perfect. I've uploaded it here: http://www.cableguys.com/downloads/2015-12-StringCrash.zip

To reproduce the crash, open the Visual 2015 project from project\vstudio\midi_shaper_tests\MidiShaperTests.sln and run the code as 32bit Release version on Windows 10 64bit (might crash on other Windows-es too, but I have not tested this).

Note that the slightest change to the test code might make the crash disappear (remove for example the last 20 lines or so..).

And: Have a good start for 2016!


#20

I tried this project and it does NOT crash on Windows7 64bit using VS2015 Enterprise.