What is the cause of this crash on vsnprintf()?

Manda Rajo 141 Reputation points
2023-07-06T10:13:39.8733333+00:00

The following code crashes on vsnprintf, but when I replace MALLOC/FREE with malloc/free then there is no crash, so I'm thinking that there is a problem in the functions MALLOC()/FREE().

My question is "What is the cause of this crash on vsnprintf()?".

  • You cannot reproduce the crash because it doesn't crash on most compilers, it only crashes with Clang (Android Studio) and on a specific device like Pixel_3a_API_33_x86_64 (emulator), and it doesn't crash on other device.
  • I use smart pointers 95% of case in my whole project, but I only use MALLOC/FREE in rare cases like this to avoid memory leak, or in a case where Vulkan needs a custom memory management (like malloc()/free()) for example.
string string_sprintf(const char *format, ...) {
    // https://docs.microsoft.com/en-us/answers/questions/813614/how-to-printf-to-stdstring-by-using-of-a-function.html
    va_list args;
    va_start(args, format);
        // Referenced from https://stackoverflow.com/questions/436367/best-way-to-safely-printf-to-a-string
        size_t sz = vsnprintf(nullptr, 0, format, args);
        size_t bufsize = sz + 1;
        char *buf = (char *)MALLOC(bufsize); // If I replace it with malloc() then there is no crash.
        EMC_VERIFY(buf != nullptr);
        vsnprintf(buf, bufsize, format, args); // <-- The crash is on this line.
        //buf[bufsize - 1] = '\0'; // This line is not necessary, check the official documentation of vsnprintf for proof.
    va_end(args);
    string str = buf;
    SAFE_FREE(buf);
    return str;
}

MALLOC() and FREE()

// In a header file:
#define MALLOC(size) ((__file = __FILE__, __line = __LINE__, 0) ? 0 : _MALLOC(size))
#define REALLOC(block, newSize) _REALLOC(block, newSize)
#define FREE(block) _FREE(block)
// In a cpp file:
typedef unsigned long long BlockId; // The reason it's 64-bit is a memory block can be freed and reallocated multiple times, which means that there can be a lot of ids.
BlockId g_blockId = 0;
BlockId newBlockId() {
    return g_blockId++;
}

struct Block {
    const char          *file;
    int                  line;
    const char          *scope;
    char                *hint;
    size_t               size;
    BlockId              id; // That id is used for comparison because it will never be changed but the block pointer can be changed.
    void                *block;
};

std::vector<Block *> g_blocks;

void *_MALLOC(size_t size) {
    Block *b = (Block *)malloc(sizeof(*b));
    b->file  = __file ; __file  = nullptr;
    b->line  = __line ; __line  = 0;
    b->scope = __scope; __scope = nullptr;
    b->hint  = ""; //allocateMemoryHint(__hint.c_str());
    b->size = size;
    b->id = newBlockId();
    b->block = malloc(size);
    g_blocks.push_back(b);
    return b->block;
}

void *_REALLOC(void *block, size_t newSize) {
    ...
}

void _FREE(void *block) {
    if (block == nullptr) {
        return; // 'free' can free a nullptr.
    }
    auto &vec = g_blocks;
    for (int i = 0; i < int(vec.size()); i++) {
        Block *b = vec[i];
        if (b->block == block) {
            vec.erase(vec.begin() + i);
            //freeMemoryHint(b->hint); b->hint = nullptr;
            free(b->block); b->block = nullptr;
            free(b); b = nullptr;
            return;
        }
    }
    EMC_ERROR("A memory block could not be found when trying to free.\n...");
}
Developer technologies | C++
{count} votes

3 answers

Sort by: Most helpful
  1. Manda Rajo 141 Reputation points
    2023-07-06T14:07:37.1566667+00:00

    How I use the function string_sprintf()?

    If I modify the code on how I use that function then there is no crash, so it makes me conclude that maybe the crash has nothing to do with MALLOC()/FREE(). My new questions are:

    • What is causing the crash on vsnprintf(buf, bufsize, format, args); in my first code?
    • What would you do if you have a problem like this? Do you believe that the crash maybe caused by Android Studio bugs, and if so, then would you leave like how I slightly change the code then the crash seems to be gone without being sure if it's gone for real (there is no logic) which means that there maybe another hidden crash somewhere. "A bug like that is one of the most difficult bugs to detect because its cause is unknown".
    // Event Type
    ...
    enum EventType {
        ...
    };
    ...
    inline string EventType_postfix(EventType x) { // postfix vs suffix: https://wikidiff.com/postfix/suffix
        ... (same as EventValue_postfix())
    }
    
    // Event Value
    enum EventValue {
        EventValue_None = 0,
        
        EventValue_Down,
        EventValue_Up,
        EventValue_Click,
        EventValue_DoubleClick,
        EventValue_Move,
        EventValue_Cancel,
    };
    inline string EventValue_postfix(EventValue x) { // postfix vs suffix: https://wikidiff.com/postfix/suffix
    #define MACRO_ITEM(x) case EventValue_##x: return #x;
        switch (x) {
            MACRO_ITEM(None)
            
            MACRO_ITEM(Down)
            MACRO_ITEM(Up)
            MACRO_ITEM(Click)
            MACRO_ITEM(DoubleClick)
            MACRO_ITEM(Move)
            MACRO_ITEM(Cancel)
        default:
            return "???";
        }
    #undef MACRO_ITEM
    }
    
    void _Window::_event_pointer(const WindowPointer &pointer, bool wantCaptureMouse) {
        #if 1
            // FIXME: There's a crash only with Pixel_3a_API_33_x86_64 (Android Studio 2021.3.1 Patch 1)
            //        and on emc-engine 4.217.
            //        Maybe the cause of the crash is on MALLOC()/FREE(), or maybe it's somewhere else in the
            //        project, that's why the other code for trying to find the cause of the crash.
            string s = string_sprintf(
                    "{id:%d, pos:(%.1f,%.1f), type:%s, value:%s}\n",
                    pointer.id,
                    pointer.pos.x,
                    pointer.pos.y,
                    EventType_postfix(pointer.type).c_str(),
                    EventValue_postfix(pointer.value).c_str());
        #else // This code doesn't crash, but it looks exactly the same as above.
            string s1 = EventType_postfix(pointer.type);
            string s2 = EventValue_postfix(pointer.value);
            string s = string_sprintf(
                    "{id:%d, pos:(%.1f,%.1f), type:%s, value:%s}\n",
                    pointer.id,
                    pointer.pos.x,
                    pointer.pos.y,
                    s1.c_str(),
                    s2.c_str());
        #endif
        g_debug_pointerEventsLog.push_back(s);
        if (g_debug_pointerEventsLog.size() > 5) {
            g_debug_pointerEventsLog.erase(g_debug_pointerEventsLog.begin());
        }
    
        ...
    }
    

  2. Manda Rajo 141 Reputation points
    2023-07-06T14:40:09.9366667+00:00

    (Edit: I want to deleted this post)

    0 comments No comments

  3. Manda Rajo 141 Reputation points
    2023-07-08T19:14:13.3166667+00:00

    An AI's answer is sometimes inaccurate, but what I did was generate multiple answers with a common idea. I feel that the AI is right in this instance. Does anyone trust this AI solution? It doesn't appear to crash.

    string string_sprintf(const char *format, ...) {
        // https://docs.microsoft.com/en-us/answers/questions/813614/how-to-printf-to-stdstring-by-using-of-a-function.html
        // Referenced from https://stackoverflow.com/questions/436367/best-way-to-safely-printf-to-a-string
        #if 0
            #error This code crashes.
            // FIXME: There's a crash only with Pixel_3a_API_33_x86_64 (Android Studio 2021.3.1 Patch 1)
            // and on emc-engine 4.217/4.229.
            // My thread: https://learn.microsoft.com/en-us/answers/questions/1325634/what-is-the-cause-of-this-crash-on-vsnprintf()
    
            va_list args;
            va_start(args, format);
    
            size_t sz = vsnprintf(nullptr, 0, format, args);
    
            size_t bufSize = sz + 1;
            char *buf = (char *)MALLOC(bufSize);
            DEBUG_VERIFY(buf != nullptr);
            buf[bufSize - 1] = '\0'; // Check the link why this line.
    
            vsnprintf(buf, bufSize, format, args);
            va_end(args);
        #else
            // ChatGPT (1):
            // Incorrect use of va_list: When using va_list multiple times, it is necessary to make
            // a copy of it before each use because va_list is typically implemented as a pointer.
            // In your code, you are using vsnprintf twice with the same va_list args argument, which
            // could lead to undefined behavior. To fix this, you can create a copy of the va_list
            // using va_copy before the second usage.
            //
            // ChatGPT (2):
            // The crash on the line vsnprintf(buf, bufSize, format, args); in your code may be caused
            // by a problem with the va_list and how it is used. The va_list type is used to iterate
            // through a variable number of arguments in variadic functions like string_sprintf that
            // use the ellipsis (...) syntax. However, once you call vsnprintf once with the va_list
            // arguments, you cannot reuse it without resetting it.
            //
            // https://en.cppreference.com/w/cpp/utility/variadic/va_copy
    
            va_list args1;
            va_start(args1, format);
            va_list args2;
            va_copy(args2, args1);
            
            size_t sz = vsnprintf(nullptr, 0, format, args1);
            va_end(args1);
    
            size_t bufSize = sz + 1;
            char *buf = (char *)MALLOC(bufSize);
            DEBUG_VERIFY(buf != nullptr);
            buf[bufSize - 1] = '\0'; // Check the link why this line.
    
            vsnprintf(buf, bufSize, format, args2);
            va_end(args2);
        #endif
        string str = buf;
        SAFE_FREE(buf);
        return str;
    }
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.