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"