carb/extras/StringSafe.h
File members: carb/extras/StringSafe.h
// Copyright (c) 2019-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
// for vsnprintf() on windows
#if !defined(_CRT_SECURE_NO_WARNINGS) && !defined(DOXYGEN_BUILD)
#    define _CRT_SECURE_NO_WARNINGS
#endif
#include "../detail/PushBadMacros.h"
#include "../Defines.h"
#include "ScopeExit.h"
#include <cstdarg>
#include <cstdio>
#include <cstring>
namespace carb
{
namespace extras
{
inline int32_t compareStrings(const char* str1, const char* str2)
{
    return strcmp(str1, str2);
}
inline int32_t compareStringsNoCase(const char* str1, const char* str2)
{
#if CARB_PLATFORM_WINDOWS
    return _stricmp(str1, str2);
#else
    return strcasecmp(str1, str2);
#endif
}
inline bool isMemoryOverlap(const void* ptr1, size_t size1, const void* ptr2, size_t size2)
{
    // We assume flat memory model.
    uintptr_t addr1 = uintptr_t(ptr1);
    uintptr_t addr2 = uintptr_t(ptr2);
    if (addr1 < addr2)
    {
        if (addr2 - addr1 >= size1)
        {
            return false;
        }
    }
    else if (addr1 > addr2)
    {
        if (addr1 - addr2 >= size2)
        {
            return false;
        }
    }
    return true;
}
inline size_t copyStringSafe(char* dstBuf, size_t dstBufSize, const char* srcString)
{
    CARB_ASSERT(dstBuf || dstBufSize == 0);
    CARB_ASSERT(srcString);
    if (dstBufSize > 0)
    {
        // Compute length of the source string to be copied.
        size_t copyLength = strlen(srcString);
        // Check the source and destination are not overlapped.
        CARB_ASSERT(!isMemoryOverlap(dstBuf, dstBufSize, srcString, copyLength + 1));
        if (copyLength >= dstBufSize)
        {
            copyLength = dstBufSize - 1;
            memcpy(dstBuf, srcString, copyLength);
        }
        else if (copyLength > 0)
        {
            memcpy(dstBuf, srcString, copyLength);
        }
        dstBuf[copyLength] = '\0';
        return copyLength;
    }
    return 0;
}
inline size_t copyStringSafe(char* dstBuf, size_t dstBufSize, const char* srcString, size_t maxCharacterCount)
{
    CARB_ASSERT(dstBuf || dstBufSize == 0);
    CARB_ASSERT(srcString || maxCharacterCount == 0);
    // NOTE: We don't use strncpy_s in implementation even if it's available in the system because it places empty
    // string to the destination buffer in case of truncation of source string (see the detailed description at
    // https://en.cppreference.com/w/c/string/byte/strncpy).
    // Instead, we use always our own implementation which is tolerate to any case of truncation.
    if (dstBufSize > 0)
    {
        // Compute length of the source string slice to be copied.
        size_t copyLength = (maxCharacterCount > 0) ? strnlen(srcString, CARB_MIN(dstBufSize - 1, maxCharacterCount)) : 0;
        // Check the source and destination are not overlapped.
        CARB_ASSERT(!isMemoryOverlap(dstBuf, dstBufSize, srcString, copyLength));
        if (copyLength > 0)
        {
            memcpy(dstBuf, srcString, copyLength);
        }
        dstBuf[copyLength] = '\0';
        return copyLength;
    }
    return 0;
}
inline size_t formatStringV(char* dstBuf, size_t dstBufSize, const char* fmtString, va_list argsList)
{
    CARB_ASSERT(dstBuf || dstBufSize == 0);
    CARB_ASSERT(fmtString);
    if (dstBufSize > 0)
    {
        int rc = std::vsnprintf(dstBuf, dstBufSize, fmtString, argsList);
        size_t count = size_t(rc);
        if (rc < 0)
        {
            // We assume no output in a case of I/O error.
            dstBuf[0] = '\0';
            count = 0;
        }
        else if (count >= dstBufSize)
        {
            // ANSI C always adds the null terminator, older MSVCRT versions do not.
            dstBuf[dstBufSize - 1] = '\0';
            count = (dstBufSize - 1);
        }
        return count;
    }
    return 0;
}
inline size_t formatString(char* dstBuf, size_t dstBufSize, const char* fmtString, ...) CARB_PRINTF_FUNCTION(3, 4);
inline size_t formatString(char* dstBuf, size_t dstBufSize, const char* fmtString, ...)
{
    size_t count;
    va_list argsList;
    va_start(argsList, fmtString);
    count = formatStringV(dstBuf, dstBufSize, fmtString, argsList);
    va_end(argsList);
    return count;
}
inline bool isStringPrefix(const char* str, const char* prefix)
{
    for (size_t i = 0; prefix[i] != '\0'; i++)
    {
        if (str[i] != prefix[i])
        {
            return false;
        }
    }
    return true;
}
template <size_t StackSize = 256, class Callable>
void withFormatNV(const char* fmt, va_list ap, Callable&& c) noexcept
{
    char* heap = nullptr;
    char buffer[StackSize];
    va_list ap2;
    va_copy(ap2, ap);
    CARB_SCOPE_EXIT
    {
        va_end(ap2);
        delete[] heap;
    };
    constexpr static char kErrorFormat[] = "<vsnprintf failed>";
    constexpr static char kErrorAlloc[] = "<failed to allocate>";
    // Optimistically try to format
    char* pbuf = buffer;
    int isize = std::vsnprintf(pbuf, StackSize, fmt, ap);
    if (CARB_UNLIKELY(isize < 0))
    {
        c(kErrorFormat, CARB_COUNTOF(kErrorFormat) - 1);
        return;
    }
    auto size = size_t(isize);
    if (size >= StackSize)
    {
        // Need the heap
        pbuf = heap = new (std::nothrow) char[size + 1];
        if (CARB_UNLIKELY(!heap))
        {
            c(kErrorAlloc, CARB_COUNTOF(kErrorAlloc) - 1);
            return;
        }
        isize = std::vsnprintf(pbuf, size + 1, fmt, ap2);
        if (CARB_UNLIKELY(isize < 0))
        {
            c(kErrorFormat, CARB_COUNTOF(kErrorFormat) - 1);
            return;
        }
        size = size_t(isize);
    }
    c(const_cast<const char*>(pbuf), size);
}
template <size_t StackSize = 256, class Callable>
void withFormatV(const char* fmt, va_list ap, Callable&& c)
{
    // Adapt to drop the size
    withFormatNV<StackSize>(fmt, ap, [&](const char* p, size_t) { c(p); });
}
#define CARB_FORMATTED_SIZE(size, fmt, ...)                                                                            \
    do                                                                                                                 \
    {                                                                                                                  \
        va_list CARB_JOIN(ap, __LINE__);                                                                               \
        va_start(CARB_JOIN(ap, __LINE__), fmt);                                                                        \
        CARB_SCOPE_EXIT                                                                                                \
        {                                                                                                              \
            va_end(CARB_JOIN(ap, __LINE__));                                                                           \
        };                                                                                                             \
        ::carb::extras::withFormatV<size>((fmt), CARB_JOIN(ap, __LINE__), __VA_ARGS__);                                \
    } while (0)
#define CARB_FORMATTED(fmt, ...) CARB_FORMATTED_SIZE(256, fmt, __VA_ARGS__)
#define CARB_FORMATTED_N_SIZE(size, fmt, ...)                                                                          \
    do                                                                                                                 \
    {                                                                                                                  \
        va_list CARB_JOIN(ap, __LINE__);                                                                               \
        va_start(CARB_JOIN(ap, __LINE__), fmt);                                                                        \
        CARB_SCOPE_EXIT                                                                                                \
        {                                                                                                              \
            va_end(CARB_JOIN(ap, __LINE__));                                                                           \
        };                                                                                                             \
        ::carb::extras::withFormatNV<size>((fmt), CARB_JOIN(ap, __LINE__), __VA_ARGS__);                               \
    } while (0)
#define CARB_FORMATTED_N(fmt, ...) CARB_FORMATTED_N_SIZE(256, fmt, __VA_ARGS__)
} // namespace extras
} // namespace carb
#include "../detail/PopBadMacros.h"