From 97ac94eb44fb641ce93fee4ef0ddff7dd2e8245e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 17 Sep 2026 21:00:36 +0800 Subject: [PATCH] Common: adds more gcc constrants --- src/common/FFlist.h | 33 ++-- src/common/FFstrbuf.h | 294 +++++++++++++++------------- src/common/base64.h | 9 +- src/common/debug.h | 2 +- src/common/duration.h | 2 +- src/common/frequency.h | 4 +- src/common/impl/FFstrbuf.c | 21 +- src/common/impl/base64.c | 3 +- src/common/impl/library.c | 4 +- src/common/impl/networking_common.c | 9 +- src/common/impl/option.c | 2 - src/common/io.h | 15 +- src/common/jsonconfig.h | 19 +- src/common/library.h | 11 +- src/common/mallocHelper.h | 3 +- src/common/networking.h | 17 +- src/common/option.h | 26 ++- src/common/parsing.h | 9 +- src/common/path.h | 6 +- src/common/percent.h | 7 +- src/common/printing.h | 11 +- src/common/properties.h | 30 +-- src/common/size.h | 2 +- src/common/smbios.h | 14 +- src/common/strutil.h | 38 +++- src/common/sysctl.h | 9 +- src/common/temps.h | 5 +- src/common/thread.h | 19 +- src/common/wcwidth.h | 5 +- 29 files changed, 344 insertions(+), 285 deletions(-) diff --git a/src/common/FFlist.h b/src/common/FFlist.h index 630b0734b..be98668bb 100644 --- a/src/common/FFlist.h +++ b/src/common/FFlist.h @@ -15,17 +15,18 @@ typedef struct FFlist { } FFlist; // Removes the first element, and copy its value to `*result` -bool ffListShift(FFlist* list, uint32_t elementSize, void* __restrict result); +// `result` is only written when the list is not empty, so it may be null for an empty list +[[gnu::nonnull(1), nodiscard]] bool ffListShift(FFlist* list, uint32_t elementSize, void* __restrict result); // Removes the last element, and copy its value to `*result` -bool ffListPop(FFlist* list, uint32_t elementSize, void* __restrict result); +[[gnu::nonnull(1), nodiscard]] bool ffListPop(FFlist* list, uint32_t elementSize, void* __restrict result); -static inline void ffListInit(FFlist* list) { +[[gnu::nonnull(1)]] static inline void ffListInit(FFlist* list) { list->capacity = 0; list->length = 0; list->data = nullptr; } -static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capacity) { +[[gnu::nonnull(1)]] static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capacity) { ffListInit(list); list->capacity = capacity; list->data = __builtin_expect(capacity == 0, 0) ? nullptr : (uint8_t*) malloc((size_t) capacity * elementSize); @@ -43,12 +44,13 @@ static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capa return result; } -[[nodiscard]] static inline void* ffListGet(const FFlist* list, uint32_t elementSize, uint32_t index) { +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline void* ffListGet(const FFlist* list, uint32_t elementSize, uint32_t index) { assert(list->capacity > index); return list->data + (index * elementSize); } -[[nodiscard]] static inline uint32_t ffListFirstIndexComp(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) { +// Not `pure`: `compFunc` is caller-supplied and may have side effects +[[gnu::nonnull(1, 3, 4), nodiscard]] static inline uint32_t ffListFirstIndexComp(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) { for (uint32_t i = 0; i < list->length; i++) { if (compFunc(ffListGet(list, elementSize, i), compElement)) { return i; @@ -58,16 +60,17 @@ static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capa return list->length; } -[[nodiscard]] static inline bool ffListContains(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) { +// Not `pure`, same reason as ffListFirstIndexComp +[[gnu::nonnull(1, 3, 4), nodiscard]] static inline bool ffListContains(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) { return ffListFirstIndexComp(list, elementSize, compElement, compFunc) != list->length; } -static inline void ffListSort(FFlist* list, uint32_t elementSize, int (*compar)(const void*, const void*)) { +[[gnu::nonnull(1, 3)]] static inline void ffListSort(FFlist* list, uint32_t elementSize, int (*compar)(const void*, const void*)) { qsort(list->data, list->length, elementSize, compar); } // Move the contents of `src` into `list`, and left `src` empty -static inline void ffListInitMove(FFlist* list, FFlist* src) { +[[gnu::nonnull(1)]] static inline void ffListInitMove(FFlist* list, FFlist* src) { if (src) { list->capacity = src->capacity; list->length = src->length; @@ -78,7 +81,7 @@ static inline void ffListInitMove(FFlist* list, FFlist* src) { } } -static inline void ffListDestroy(FFlist* list) { +[[gnu::nonnull(1)]] static inline void ffListDestroy(FFlist* list) { if (!list->data) { return; } @@ -89,11 +92,11 @@ static inline void ffListDestroy(FFlist* list) { list->data = nullptr; } -static inline void ffListClear(FFlist* list) { +[[gnu::nonnull(1)]] static inline void ffListClear(FFlist* list) { list->length = 0; } -static inline void ffListReserve(FFlist* list, uint32_t elementSize, uint32_t newCapacity) { +[[gnu::nonnull(1)]] static inline void ffListReserve(FFlist* list, uint32_t elementSize, uint32_t newCapacity) { if (__builtin_expect(newCapacity <= list->capacity, false)) { return; } @@ -102,7 +105,7 @@ static inline void ffListReserve(FFlist* list, uint32_t elementSize, uint32_t ne list->capacity = newCapacity; } -static inline void* ffListAdd(FFlist* list, uint32_t elementSize) { +[[gnu::nonnull(1)]] static inline void* ffListAdd(FFlist* list, uint32_t elementSize) { if (__builtin_expect(list->length == list->capacity, false)) { ffListReserve(list, elementSize, list->capacity == 0 ? FF_LIST_DEFAULT_ALLOC : list->capacity * 2); } @@ -111,13 +114,13 @@ static inline void* ffListAdd(FFlist* list, uint32_t elementSize) { return ffListGet(list, elementSize, list->length - 1); } -static inline void ffListRemoveAt(FFlist* list, uint32_t elementSize, uint32_t index) { +[[gnu::nonnull(1)]] static inline void ffListRemoveAt(FFlist* list, uint32_t elementSize, uint32_t index) { assert(list->length > index); memmove(list->data + (index * elementSize), list->data + ((index + 1) * elementSize), (size_t) (list->length - index - 1) * elementSize); --list->length; } -static inline void ffListInsertAt(FFlist* list, uint32_t elementSize, uint32_t index, const void* element) { +[[gnu::nonnull(1, 4)]] static inline void ffListInsertAt(FFlist* list, uint32_t elementSize, uint32_t index, const void* element) { assert(list->length >= index); ffListAdd(list, elementSize); memmove(list->data + ((index + 1) * elementSize), list->data + (index * elementSize), (size_t) (list->length - index - 1) * elementSize); diff --git a/src/common/FFstrbuf.h b/src/common/FFstrbuf.h index 0f4f405a6..879508a07 100644 --- a/src/common/FFstrbuf.h +++ b/src/common/FFstrbuf.h @@ -24,6 +24,29 @@ __stdcall char* StrStrIA(const char* lpFirst, const char* lpSrch); #define FASTFETCH_STRBUF_DEFAULT_ALLOC 32 +// --------------------------------------------------------------------------------------------- +// Contract attributes used throughout this header: +// +// `gnu::pure` marks a function that reads memory (through its arguments and through globals) but +// writes nothing observable, so the compiler may cache and reorder calls to it. It is applied to +// the comparison / search accessors, and deliberately NOT to: +// * the `To*` converters -- `strtod` / `strtoull` / `strtoll` write `errno`; +// * `ffStrbufEndsWithFn` -- the caller-supplied `fn` may have side effects; +// * `ffStrbufWriteTo` / `ffStrbufPutTo` -- they write to a `FILE*`. +// +// `gnu::nonnull(N)` is added wherever the function dereferences argument N unconditionally. The +// two cannot be combined with a null check: clang reports `-Wtautological-pointer-compare` / +// `-Wpointer-bool-conversion` (both in `-Wall`) when a `nonnull` parameter is compared against +// null, so a redundant `assert(p != nullptr)` is dropped in favour of the attribute. Asserts that +// state something `nonnull` cannot express -- pointer aliasing (`value != strbuf`), index ranges +// (`start <= strbuf->length`), or a non-pointer invariant -- are kept. +// +// `nodiscard` is NOT applied to the "was the buffer modified" boolean returns +// (`ffStrbufSubstr*`, `ffStrbufRemoveSubstr`, `ffStrbufEnsureEndsWithC`, ...). Ignoring that +// result is a normal, intended use of those functions -- `ffStrbufSubstrBefore` alone has 153 call +// sites and ~147 of them discard the result -- so `nodiscard` would only produce noise. +// --------------------------------------------------------------------------------------------- + // static string (allocated == 0), chars points to a string literal // dynamic string (allocated > 0), chars points to a heap allocated buffer typedef struct FFstrbuf { @@ -32,67 +55,70 @@ typedef struct FFstrbuf { char* chars; } FFstrbuf; -static inline void ffStrbufInit(FFstrbuf* strbuf); -void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate); -void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments); -void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr); -[[gnu::format(printf, 2, 3)]] void ffStrbufInitF(FFstrbuf* strbuf, const char* format, ...); -[[gnu::format(printf, 1, 2)]] [[nodiscard]] FFstrbuf ffStrbufCreateF(const char* format, ...); +[[gnu::nonnull(1)]] static inline void ffStrbufInit(FFstrbuf* strbuf); +[[gnu::nonnull(1)]] void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate); +[[gnu::nonnull(1, 2), gnu::format(printf, 2, 0)]] void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments); +[[gnu::nonnull(1, 3)]] void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr); +[[gnu::format(printf, 2, 3), gnu::nonnull(1, 2)]] void ffStrbufInitF(FFstrbuf* strbuf, const char* format, ...); +[[gnu::format(printf, 1, 2), gnu::nonnull(1), nodiscard]] FFstrbuf ffStrbufCreateF(const char* format, ...); -void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free); -void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free); +[[gnu::nonnull(1)]] void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free); +[[gnu::nonnull(1)]] void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free); -static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value); -void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transformFunc)(int)); -[[gnu::format(printf, 2, 3)]] void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...); -void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments); -const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until); +[[gnu::nonnull(1)]] static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value); +[[gnu::nonnull(1, 3)]] void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transformFunc)(int)); +[[gnu::format(printf, 2, 3), gnu::nonnull(1, 2)]] void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...); +[[gnu::nonnull(1, 2), gnu::format(printf, 2, 0)]] void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments); +// Returns the pointer to the terminator, or nullptr if `value` was nullptr; callers are free to +// ignore it, so not `nodiscard`. +[[gnu::nonnull(1)]] const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until); -void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value); -void ffStrbufPrependC(FFstrbuf* strbuf, char c); +[[gnu::nonnull(1)]] void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value); +[[gnu::nonnull(1)]] void ffStrbufPrependC(FFstrbuf* strbuf, char c); -void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c); +[[gnu::nonnull(1)]] void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c); // Clear the content of strbuf and set new value // NOTE: Unlike ffStrbufAppend*, ffStrbufSet* functions may NOT reserve extra space -void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value); -void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value); -[[gnu::format(printf, 2, 3)]] void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...); +[[gnu::nonnull(1, 2)]] void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value); +[[gnu::nonnull(1, 3)]] void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value); +[[gnu::format(printf, 2, 3), gnu::nonnull(1, 2)]] void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...); -void ffStrbufTrimLeft(FFstrbuf* strbuf, char c); -void ffStrbufTrimRight(FFstrbuf* strbuf, char c); -void ffStrbufTrimLeftSpace(FFstrbuf* strbuf); -void ffStrbufTrimRightSpace(FFstrbuf* strbuf); +[[gnu::nonnull(1)]] void ffStrbufTrimLeft(FFstrbuf* strbuf, char c); +[[gnu::nonnull(1)]] void ffStrbufTrimRight(FFstrbuf* strbuf, char c); +[[gnu::nonnull(1)]] void ffStrbufTrimLeftSpace(FFstrbuf* strbuf); +[[gnu::nonnull(1)]] void ffStrbufTrimRightSpace(FFstrbuf* strbuf); -bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex); -void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str); -void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]); +[[gnu::nonnull(1)]] bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex); +[[gnu::nonnull(1, 2)]] void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str); +// `strings` is only dereferenced when `numStrings > 0`, so it is intentionally not `nonnull(3)` +[[gnu::nonnull(1)]] void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]); -void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace); +[[gnu::nonnull(1)]] void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace); // Returns true if the strbuf is modified -bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index); -bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index); // Not including the index -bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c); -bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str); -bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c); -bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end); +[[gnu::nonnull(1)]] bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index); +[[gnu::nonnull(1)]] bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index); // Not including the index +[[gnu::nonnull(1)]] bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c); +[[gnu::nonnull(1, 2)]] bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str); +[[gnu::nonnull(1)]] bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c); +[[gnu::nonnull(1)]] bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end); -[[nodiscard]] uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c); +[[gnu::nonnull(1), gnu::pure, nodiscard]] uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c); -bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end); +[[gnu::nonnull(1, 2)]] bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end); -bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c); +[[gnu::nonnull(1)]] bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c); -void ffStrbufUpperCase(FFstrbuf* strbuf); -void ffStrbufLowerCase(FFstrbuf* strbuf); +[[gnu::nonnull(1)]] void ffStrbufUpperCase(FFstrbuf* strbuf); +[[gnu::nonnull(1)]] void ffStrbufLowerCase(FFstrbuf* strbuf); // Function alters the buffer to extract lines or delimited segments (replaces the delimiter with '\0') // so that buffer MUST be heap allocated (NOT a static string) // `lineptr` must be `nullptr` and `n` MUST be `0` for the first call // Caller MUST NOT free `*lineptr` -bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer); -void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer); +[[gnu::nonnull(1, 2, 4)]] bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer); +[[gnu::nonnull(1, 2, 4)]] void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer); /** * @brief Read a line from a FFstrbuf. @@ -110,29 +136,31 @@ void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf * * @return true if a line has been read, false if the end of the buffer has been reached. */ -static inline bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) { +[[gnu::nonnull(1, 2, 3), nodiscard]] static inline bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) { return ffStrbufGetdelim(lineptr, n, '\n', buffer); } /** * @brief Restore the end of a line that was modified by ffStrbufGetline. * @warning This function should be called before breaking an ffStrbufGetline loop if `buffer` will be used later. */ -static inline void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer) { +[[gnu::nonnull(1, 2, 3)]] static inline void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer) { ffStrbufGetdelimRestore(lineptr, n, '\n', buffer); } -bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf); -bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); -bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); -bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); -bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); +[[gnu::nonnull(1)]] bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf); +// `comp` is only dereferenced when `compLength > 0` in the first pair, and is never dereferenced +// when `strbuf` is empty in the second pair, so neither takes `nonnull(3)` +[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); +[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); +[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); +[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); -int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint); +[[gnu::nonnull(1)]] int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint); -void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value); -void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value); +[[gnu::nonnull(1)]] void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value); +[[gnu::nonnull(1)]] void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value); // Appends a double value to the string buffer with the specified precision (0~15). // if `precision < 0`, let yyjson decide the precision -void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros); +[[gnu::nonnull(1)]] void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros); [[nodiscard]] static inline FFstrbuf ffStrbufCreateA(uint32_t allocate) { FFstrbuf strbuf; @@ -140,7 +168,7 @@ void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool return strbuf; } -static inline void ffStrbufInitCopy(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict src) { +[[gnu::nonnull(1, 2)]] static inline void ffStrbufInitCopy(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict src) { if (src->allocated == 0) { // static string *strbuf = *src; } else { @@ -149,14 +177,14 @@ static inline void ffStrbufInitCopy(FFstrbuf* __restrict strbuf, const FFstrbuf* } } -[[nodiscard]] static inline FFstrbuf ffStrbufCreateCopy(const FFstrbuf* src) { +[[gnu::nonnull(1), nodiscard]] static inline FFstrbuf ffStrbufCreateCopy(const FFstrbuf* src) { FFstrbuf strbuf; ffStrbufInitCopy(&strbuf, src); return strbuf; } // Move the content of `src` into `strbuf`, and left `src` empty -static inline void ffStrbufInitMove(FFstrbuf* strbuf, FFstrbuf* src) { +[[gnu::nonnull(1)]] static inline void ffStrbufInitMove(FFstrbuf* strbuf, FFstrbuf* src) { if (src) { *strbuf = *src; ffStrbufInit(src); @@ -171,12 +199,12 @@ static inline void ffStrbufInitMove(FFstrbuf* strbuf, FFstrbuf* src) { return strbuf; } -static inline void ffStrbufInitMoveS(FFstrbuf* strbuf, char* heapStr) { +[[gnu::nonnull(1, 2)]] static inline void ffStrbufInitMoveS(FFstrbuf* strbuf, char* heapStr) { ffStrbufInitMoveNS(strbuf, (uint32_t) strlen(heapStr), heapStr); } // Despite the name, this function resets strbuf to the initial/unallocated state -static inline void ffStrbufDestroy(FFstrbuf* strbuf) { +[[gnu::nonnull(1)]] static inline void ffStrbufDestroy(FFstrbuf* strbuf) { if (strbuf->allocated > 0) { free(strbuf->chars); } @@ -184,8 +212,7 @@ static inline void ffStrbufDestroy(FFstrbuf* strbuf) { ffStrbufInit(strbuf); } -[[nodiscard]] static inline uint32_t ffStrbufGetFree(const FFstrbuf* strbuf) { - assert(strbuf != nullptr); +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufGetFree(const FFstrbuf* strbuf) { if (strbuf->allocated == 0) { return 0; } @@ -193,7 +220,7 @@ static inline void ffStrbufDestroy(FFstrbuf* strbuf) { return strbuf->allocated - strbuf->length - 1; // - 1 for the null byte } -static inline void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free) { +[[gnu::nonnull(1)]] static inline void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free) { if (__builtin_expect(free == 0, false)) { if (__builtin_expect(!(strbuf->allocated == 0 && strbuf->length > 0), true)) { return; @@ -208,8 +235,7 @@ static inline void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free) { } -static inline void ffStrbufClear(FFstrbuf* strbuf) { - assert(strbuf != nullptr); +[[gnu::nonnull(1)]] static inline void ffStrbufClear(FFstrbuf* strbuf) { extern char* CHAR_NULL_PTR; if (strbuf->allocated == 0) { @@ -221,13 +247,13 @@ static inline void ffStrbufClear(FFstrbuf* strbuf) { strbuf->length = 0; } -static inline void ffStrbufAppendC(FFstrbuf* strbuf, char c) { +[[gnu::nonnull(1)]] static inline void ffStrbufAppendC(FFstrbuf* strbuf, char c) { ffStrbufEnsureFree(strbuf, 1); strbuf->chars[strbuf->length++] = c; strbuf->chars[strbuf->length] = '\0'; } -static inline void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) { +[[gnu::nonnull(1)]] static inline void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) { if (__builtin_expect(num == 0, false)) { return; } @@ -238,7 +264,7 @@ static inline void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) { strbuf->chars[strbuf->length] = '\0'; } -static inline void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value) { +[[gnu::nonnull(1)]] static inline void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value) { if (__builtin_expect(value == nullptr || length == 0, false)) { return; } @@ -249,7 +275,7 @@ static inline void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const cha strbuf->chars[strbuf->length] = '\0'; } -static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value) { +[[gnu::nonnull(1)]] static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value) { assert(value != strbuf); if (value == nullptr) { return; @@ -257,13 +283,12 @@ static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* _ ffStrbufAppendNS(strbuf, value->length, value->chars); } -static inline void ffStrbufRecalculateLength(FFstrbuf* strbuf) { +[[gnu::nonnull(1)]] static inline void ffStrbufRecalculateLength(FFstrbuf* strbuf) { strbuf->length = (uint32_t) strlen(strbuf->chars); } -static inline void ffStrbufSetS(FFstrbuf* strbuf, const char* value) { - assert(strbuf != nullptr); - +// `value` may be null (clears the buffer); `strbuf` may not +[[gnu::nonnull(1)]] static inline void ffStrbufSetS(FFstrbuf* strbuf, const char* value) { if (value == nullptr) { ffStrbufClear(strbuf); } else { @@ -271,9 +296,7 @@ static inline void ffStrbufSetS(FFstrbuf* strbuf, const char* value) { } } -static inline bool ffStrbufSetJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) { - assert(strbuf != nullptr); - +[[gnu::nonnull(1)]] static inline bool ffStrbufSetJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) { if (yyjson_is_str(jsonVal)) { ffStrbufSetNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal)); return true; @@ -283,13 +306,15 @@ static inline bool ffStrbufSetJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) { return false; } -static inline void ffStrbufAppendS(FFstrbuf* strbuf, const char* value) { +[[gnu::nonnull(1)]] static inline void ffStrbufAppendS(FFstrbuf* strbuf, const char* value) { if (value == nullptr) { return; } ffStrbufAppendNS(strbuf, (uint32_t) strlen(value), value); } +// Returns whether `jsonVal` was a string. Callers routinely pre-check with `yyjson_is_str`, so the +// result is not `nodiscard`. static inline bool ffStrbufAppendJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) { if (yyjson_is_str(jsonVal)) { ffStrbufAppendNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal)); @@ -298,7 +323,7 @@ static inline bool ffStrbufAppendJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) return false; } -static inline void ffStrbufInit(FFstrbuf* strbuf) { +[[gnu::nonnull(1)]] static inline void ffStrbufInit(FFstrbuf* strbuf) { extern char* CHAR_NULL_PTR; strbuf->allocated = strbuf->length = 0; strbuf->chars = CHAR_NULL_PTR; @@ -327,7 +352,7 @@ static inline void ffStrbufInitStatic(FFstrbuf* strbuf, const char* str) { return strbuf; } -static inline void ffStrbufSetStatic(FFstrbuf* strbuf, const char* value) { +[[gnu::nonnull(1)]] static inline void ffStrbufSetStatic(FFstrbuf* strbuf, const char* value) { if (strbuf->allocated > 0) { free(strbuf->chars); } @@ -339,7 +364,7 @@ static inline void ffStrbufSetStatic(FFstrbuf* strbuf, const char* value) { } } -static inline void ffStrbufInitNS(FFstrbuf* strbuf, uint32_t length, const char* str) { +[[gnu::nonnull(1)]] static inline void ffStrbufInitNS(FFstrbuf* strbuf, uint32_t length, const char* str) { ffStrbufInit(strbuf); ffStrbufAppendNS(strbuf, length, str); } @@ -350,12 +375,13 @@ static inline void ffStrbufInitNS(FFstrbuf* strbuf, uint32_t length, const char* return strbuf; } +// Returns whether `jsonVal` was a string; not `nodiscard`, same reason as ffStrbufAppendJsonVal static inline bool ffStrbufInitJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) { ffStrbufInit(strbuf); return ffStrbufAppendJsonVal(strbuf, jsonVal); } -static inline void ffStrbufInitS(FFstrbuf* strbuf, const char* str) { +[[gnu::nonnull(1)]] static inline void ffStrbufInitS(FFstrbuf* strbuf, const char* str) { ffStrbufInit(strbuf); ffStrbufAppendS(strbuf, str); } @@ -366,107 +392,107 @@ static inline void ffStrbufInitS(FFstrbuf* strbuf, const char* str) { return strbuf; } -static inline void ffStrbufPrepend(FFstrbuf* strbuf, FFstrbuf* value) { +[[gnu::nonnull(1)]] static inline void ffStrbufPrepend(FFstrbuf* strbuf, FFstrbuf* value) { if (value == nullptr) { return; } ffStrbufPrependNS(strbuf, value->length, value->chars); } -static inline void ffStrbufPrependS(FFstrbuf* strbuf, const char* value) { +[[gnu::nonnull(1)]] static inline void ffStrbufPrependS(FFstrbuf* strbuf, const char* value) { if (value == nullptr) { return; } ffStrbufPrependNS(strbuf, (uint32_t) strlen(value), value); } -[[nodiscard]] static inline int ffStrbufComp(const FFstrbuf* strbuf, const FFstrbuf* comp) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufComp(const FFstrbuf* strbuf, const FFstrbuf* comp) { uint32_t length = strbuf->length > comp->length ? comp->length : strbuf->length; return memcmp(strbuf->chars, comp->chars, length + 1); } -[[nodiscard]] static inline bool ffStrbufEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) { return ffStrbufComp(strbuf, comp) == 0; } -[[nodiscard]] static inline int ffStrbufCompS(const FFstrbuf* strbuf, const char* comp) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufCompS(const FFstrbuf* strbuf, const char* comp) { return strcmp(strbuf->chars, comp); } -[[nodiscard]] static inline bool ffStrbufEqualS(const FFstrbuf* strbuf, const char* comp) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEqualS(const FFstrbuf* strbuf, const char* comp) { return ffStrbufCompS(strbuf, comp) == 0; } -[[nodiscard]] static inline int ffStrbufIgnCaseCompS(const FFstrbuf* strbuf, const char* comp) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufIgnCaseCompS(const FFstrbuf* strbuf, const char* comp) { return strcasecmp(strbuf->chars, comp); } -[[nodiscard]] static inline bool ffStrbufIgnCaseEqualS(const FFstrbuf* strbuf, const char* comp) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufIgnCaseEqualS(const FFstrbuf* strbuf, const char* comp) { return ffStrbufIgnCaseCompS(strbuf, comp) == 0; } -[[nodiscard]] static inline int ffStrbufIgnCaseComp(const FFstrbuf* strbuf, const FFstrbuf* comp) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufIgnCaseComp(const FFstrbuf* strbuf, const FFstrbuf* comp) { return ffStrbufIgnCaseCompS(strbuf, comp->chars); } -[[nodiscard]] static inline bool ffStrbufIgnCaseEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufIgnCaseEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) { return ffStrbufIgnCaseComp(strbuf, comp) == 0; } -[[nodiscard]] static inline bool ffStrbufContainC(const FFstrbuf* strbuf, char c) { +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufContainC(const FFstrbuf* strbuf, char c) { return memchr(strbuf->chars, c, strbuf->length) != nullptr; } -[[nodiscard]] static inline bool ffStrbufContainS(const FFstrbuf* strbuf, const char* str) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContainS(const FFstrbuf* strbuf, const char* str) { return strstr(strbuf->chars, str) != nullptr; } -[[nodiscard]] static inline bool ffStrbufContain(const FFstrbuf* strbuf, const FFstrbuf* str) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContain(const FFstrbuf* strbuf, const FFstrbuf* str) { return ffStrbufContainS(strbuf, str->chars); } -[[nodiscard]] static inline bool ffStrbufContainIgnCaseS(const FFstrbuf* strbuf, const char* str) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContainIgnCaseS(const FFstrbuf* strbuf, const char* str) { return strcasestr(strbuf->chars, str) != nullptr; } -[[nodiscard]] static inline bool ffStrbufContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* str) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* str) { return ffStrbufContainIgnCaseS(strbuf, str->chars); } -[[nodiscard]] static inline uint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c) { +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c) { assert(start <= strbuf->length); const char* ptr = (const char*) memchr(strbuf->chars + start, c, strbuf->length - start); return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length; } -[[nodiscard]] static inline uint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str) { +[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline uint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str) { assert(start <= strbuf->length); const char* ptr = strstr(strbuf->chars + start, str); return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length; } -[[nodiscard]] static inline uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c) { +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c) { assert(start <= strbuf->length); const char* ptr = (const char*) memrchr(strbuf->chars, c, start + 1); return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length; } -[[nodiscard]] static inline uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, char c) { +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, char c) { return ffStrbufNextIndexC(strbuf, 0, c); } -[[nodiscard]] static inline uint32_t ffStrbufFirstIndex(const FFstrbuf* strbuf, const FFstrbuf* searched) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline uint32_t ffStrbufFirstIndex(const FFstrbuf* strbuf, const FFstrbuf* searched) { return ffStrbufNextIndexS(strbuf, 0, searched->chars); } -[[nodiscard]] static inline uint32_t ffStrbufFirstIndexS(const FFstrbuf* strbuf, const char* str) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline uint32_t ffStrbufFirstIndexS(const FFstrbuf* strbuf, const char* str) { return ffStrbufNextIndexS(strbuf, 0, str); } -[[nodiscard]] static inline uint32_t ffStrbufLastIndexC(const FFstrbuf* strbuf, char c) { +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufLastIndexC(const FFstrbuf* strbuf, char c) { if (strbuf->length == 0) { return 0; } @@ -474,19 +500,19 @@ static inline void ffStrbufPrependS(FFstrbuf* strbuf, const char* value) { return ffStrbufPreviousIndexC(strbuf, strbuf->length - 1, c); } -static inline bool ffStrbufSubstrBeforeFirstC(FFstrbuf* strbuf, char c) { +[[gnu::nonnull(1)]] static inline bool ffStrbufSubstrBeforeFirstC(FFstrbuf* strbuf, char c) { return ffStrbufSubstrBefore(strbuf, ffStrbufFirstIndexC(strbuf, c)); } -static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) { +[[gnu::nonnull(1)]] static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) { return ffStrbufSubstrBefore(strbuf, ffStrbufLastIndexC(strbuf, c)); } -[[nodiscard]] static inline bool ffStrbufStartsWithC(const FFstrbuf* strbuf, char c) { +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithC(const FFstrbuf* strbuf, char c) { return strbuf->chars[0] == c; } -[[nodiscard]] static inline bool ffStrbufStartsWithSN(const FFstrbuf* strbuf, const char* start, uint32_t length) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithSN(const FFstrbuf* strbuf, const char* start, uint32_t length) { if (length > strbuf->length) { return false; } @@ -494,34 +520,34 @@ static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) { return memcmp(strbuf->chars, start, length) == 0; } -[[nodiscard]] static inline bool ffStrbufStartsWithS(const FFstrbuf* strbuf, const char* start) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithS(const FFstrbuf* strbuf, const char* start) { return ffStrbufStartsWithSN(strbuf, start, (uint32_t) strlen(start)); } -[[nodiscard]] static inline bool ffStrbufStartsWith(const FFstrbuf* strbuf, const FFstrbuf* start) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWith(const FFstrbuf* strbuf, const FFstrbuf* start) { return ffStrbufStartsWithSN(strbuf, start->chars, start->length); } -[[nodiscard]] static inline bool ffStrbufStartsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t length, const char* start) { +[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t length, const char* start) { if (length > strbuf->length) { return false; } return strncasecmp(strbuf->chars, start, length) == 0; } -[[nodiscard]] static inline bool ffStrbufStartsWithIgnCaseS(const FFstrbuf* strbuf, const char* start) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithIgnCaseS(const FFstrbuf* strbuf, const char* start) { return ffStrbufStartsWithIgnCaseNS(strbuf, (uint32_t) strlen(start), start); } -[[nodiscard]] static inline bool ffStrbufStartsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* start) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* start) { return ffStrbufStartsWithIgnCaseNS(strbuf, start->length, start->chars); } -[[nodiscard]] static inline bool ffStrbufEndsWithC(const FFstrbuf* strbuf, char c) { +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithC(const FFstrbuf* strbuf, char c) { return strbuf->length == 0 ? false : strbuf->chars[strbuf->length - 1] == c; } -[[nodiscard]] static inline bool ffStrbufEndsWithNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) { +[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) { if (endLength > strbuf->length) { return false; } @@ -529,104 +555,108 @@ static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) { return memcmp(strbuf->chars + strbuf->length - endLength, end, endLength) == 0; } -[[nodiscard]] static inline bool ffStrbufEndsWithS(const FFstrbuf* strbuf, const char* end) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithS(const FFstrbuf* strbuf, const char* end) { return ffStrbufEndsWithNS(strbuf, (uint32_t) strlen(end), end); } -[[nodiscard]] static inline bool ffStrbufEndsWithFn(const FFstrbuf* strbuf, int (*const fn)(int)) { +// Not `pure`: the caller-supplied `fn` may have side effects +[[gnu::nonnull(1, 2), nodiscard]] static inline bool ffStrbufEndsWithFn(const FFstrbuf* strbuf, int (*const fn)(int)) { return strbuf->length == 0 ? false : fn(strbuf->chars[strbuf->length - 1]); } -[[nodiscard]] static inline bool ffStrbufEndsWith(const FFstrbuf* strbuf, const FFstrbuf* end) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWith(const FFstrbuf* strbuf, const FFstrbuf* end) { return ffStrbufEndsWithNS(strbuf, end->length, end->chars); } -[[nodiscard]] static inline bool ffStrbufEndsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) { +[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) { if (endLength > strbuf->length) { return false; } return strcasecmp(strbuf->chars + strbuf->length - endLength, end) == 0; } -[[nodiscard]] static inline bool ffStrbufEndsWithIgnCaseS(const FFstrbuf* strbuf, const char* end) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithIgnCaseS(const FFstrbuf* strbuf, const char* end) { return ffStrbufEndsWithIgnCaseNS(strbuf, (uint32_t) strlen(end), end); } -[[nodiscard]] static inline bool ffStrbufEndsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* end) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* end) { return ffStrbufEndsWithIgnCaseNS(strbuf, end->length, end->chars); } -static inline void ffStrbufTrim(FFstrbuf* strbuf, char c) { +[[gnu::nonnull(1)]] static inline void ffStrbufTrim(FFstrbuf* strbuf, char c) { ffStrbufTrimRight(strbuf, c); ffStrbufTrimLeft(strbuf, c); } -static inline void ffStrbufTrimSpace(FFstrbuf* strbuf) { +[[gnu::nonnull(1)]] static inline void ffStrbufTrimSpace(FFstrbuf* strbuf) { ffStrbufTrimRightSpace(strbuf); ffStrbufTrimLeftSpace(strbuf); } -static inline bool ffStrbufMatchSeparatedS(const FFstrbuf* strbuf, const char* comp, char separator) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparatedS(const FFstrbuf* strbuf, const char* comp, char separator) { return ffStrbufMatchSeparatedNS(strbuf, (uint32_t) strlen(comp), comp, separator); } -static inline bool ffStrbufMatchSeparated(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparated(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { return ffStrbufMatchSeparatedNS(strbuf, comp->length, comp->chars, separator); } -static inline bool ffStrbufMatchSeparatedIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparatedIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) { return ffStrbufMatchSeparatedIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator); } -static inline bool ffStrbufMatchSeparatedIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparatedIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { return ffStrbufMatchSeparatedIgnCaseNS(strbuf, comp->length, comp->chars, separator); } -static inline bool ffStrbufSeparatedContainS(const FFstrbuf* strbuf, const char* comp, char separator) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContainS(const FFstrbuf* strbuf, const char* comp, char separator) { return ffStrbufSeparatedContainNS(strbuf, (uint32_t) strlen(comp), comp, separator); } -static inline bool ffStrbufSeparatedContain(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContain(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { return ffStrbufSeparatedContainNS(strbuf, comp->length, comp->chars, separator); } -static inline bool ffStrbufSeparatedContainIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContainIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) { return ffStrbufSeparatedContainIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator); } -static inline bool ffStrbufSeparatedContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { return ffStrbufSeparatedContainIgnCaseNS(strbuf, comp->length, comp->chars, separator); } -static inline void ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file) { +[[gnu::nonnull(1, 2)]] static inline void ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file) { fwrite(strbuf->chars, sizeof(*strbuf->chars), strbuf->length, file); } -static inline void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file) { +[[gnu::nonnull(1, 2)]] static inline void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file) { ffStrbufWriteTo(strbuf, file); fputc('\n', file); } -[[nodiscard]] static inline double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue) { +// Not `pure`: `strtod` reads the LC_NUMERIC locale and writes errno +[[gnu::nonnull(1), nodiscard]] static inline double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue) { char* str_end; double result = strtod(strbuf->chars, &str_end); return str_end == strbuf->chars ? defaultValue : result; } -[[nodiscard]] static inline uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue) { +// Not `pure`: `strtoull` reads the LC_NUMERIC locale and writes errno +[[gnu::nonnull(1), nodiscard]] static inline uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue) { char* str_end; unsigned long long result = strtoull(strbuf->chars, &str_end, 10); return str_end == strbuf->chars ? defaultValue : (uint64_t) result; } -[[nodiscard]] static inline int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue) { +// Not `pure`: `strtoll` reads the LC_NUMERIC locale and writes errno +[[gnu::nonnull(1), nodiscard]] static inline int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue) { char* str_end; long long result = strtoll(strbuf->chars, &str_end, 10); return str_end == strbuf->chars ? defaultValue : (int64_t) result; } // Returns true if the strbuf is modified -bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf); +[[gnu::nonnull(1)]] [[gnu::nonnull(1)]] bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf); #define FF_STRBUF_AUTO_DESTROY [[gnu::cleanup(ffStrbufDestroy)]] FFstrbuf #define FF_STRBUF_STATIC(str) { .allocated = 0, .length = (uint32_t) sizeof(str) - 1, .chars = str } diff --git a/src/common/base64.h b/src/common/base64.h index d17da5c70..52a60130f 100644 --- a/src/common/base64.h +++ b/src/common/base64.h @@ -2,8 +2,9 @@ #include "fastfetch.h" -void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output); -static inline FFstrbuf ffBase64EncodeStrbuf(const FFstrbuf* in) { +// `str`, `out_size` and `output` are dereferenced unconditionally +[[gnu::nonnull(2, 3, 4)]] void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output); +[[gnu::nonnull(1), nodiscard]] static inline FFstrbuf ffBase64EncodeStrbuf(const FFstrbuf* in) { FFstrbuf out = ffStrbufCreateA(10 + in->length * 4 / 3); ffBase64EncodeRaw(in->length, in->chars, &out.length, out.chars); assert(out.length < out.allocated); @@ -11,8 +12,8 @@ static inline FFstrbuf ffBase64EncodeStrbuf(const FFstrbuf* in) { return out; } -bool ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output); -static inline FFstrbuf ffBase64DecodeStrbuf(const FFstrbuf* in) { +[[gnu::nonnull(2, 3, 4)]] void ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output); +[[gnu::nonnull(1), nodiscard]] static inline FFstrbuf ffBase64DecodeStrbuf(const FFstrbuf* in) { FFstrbuf out = ffStrbufCreateA(10 + in->length * 3 / 4); ffBase64DecodeRaw(in->length, in->chars, &out.length, out.chars); assert(out.length < out.allocated); diff --git a/src/common/debug.h b/src/common/debug.h index ee7f4be04..ea7c27f4c 100644 --- a/src/common/debug.h +++ b/src/common/debug.h @@ -3,7 +3,7 @@ #include "fastfetch.h" #include "common/time.h" -static inline const char* ffFindFileName(const char* file) { +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline const char* ffFindFileName(const char* file) { const char* lastSlash = __builtin_strrchr(file, '/'); #ifdef _WIN32 if (lastSlash == nullptr) { diff --git a/src/common/duration.h b/src/common/duration.h index 30733c946..2336eb86e 100644 --- a/src/common/duration.h +++ b/src/common/duration.h @@ -2,4 +2,4 @@ #include "fastfetch.h" -void ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result); +[[gnu::nonnull(2)]] void ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result); diff --git a/src/common/frequency.h b/src/common/frequency.h index 7a3bdab02..9ffe51b7b 100644 --- a/src/common/frequency.h +++ b/src/common/frequency.h @@ -2,4 +2,6 @@ #include "fastfetch.h" -bool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result); +// Returns false when nothing was appended (e.g. `mhz == 0`); callers routinely discard that, so it +// is not `nodiscard`. +[[gnu::nonnull(2)]] bool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result); diff --git a/src/common/impl/FFstrbuf.c b/src/common/impl/FFstrbuf.c index 20693c1ec..494e487c8 100644 --- a/src/common/impl/FFstrbuf.c +++ b/src/common/impl/FFstrbuf.c @@ -21,8 +21,6 @@ void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate) { } void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments) { - assert(format != nullptr); - char* buffer = nullptr; int len = vasprintf(&buffer, format, arguments); assert(len >= 0); @@ -33,8 +31,6 @@ void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments) { // Takes ownership of `heapStr`. The caller must not free `heapStr` after calling this // function; the memory will be managed and freed via the associated FFstrbuf. void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr) { - assert(heapStr != nullptr); - strbuf->length = length; size_t allocSize = ffMallocUsableSize(heapStr); if (allocSize == 0) { @@ -157,8 +153,6 @@ void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transfo } void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments) { - assert(format != nullptr); - va_list copy; va_copy(copy, arguments); @@ -192,8 +186,6 @@ const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char unti } void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...) { - assert(format != nullptr); - va_list arguments; va_start(arguments, format); @@ -209,8 +201,6 @@ void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...) { } void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...) { - assert(format != nullptr); - va_list arguments; va_start(arguments, format); ffStrbufAppendVF(strbuf, format, arguments); @@ -236,15 +226,11 @@ void ffStrbufPrependC(FFstrbuf* strbuf, char c) { } void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value) { - assert(strbuf != nullptr); - if (length == 0) { ffStrbufClear(strbuf); return; } - assert(value != nullptr); - if (strbuf->allocated <= length) { char* newBuf = malloc(sizeof(char) * (length + 1)); memcpy(newBuf, value, length); @@ -262,7 +248,8 @@ void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value) { } void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value) { - assert(value && value != strbuf); + // `value` is non-null per the `nonnull(2)` contract; the aliasing check cannot be expressed by an attribute + assert(value != strbuf); if (value->length == 0) { ffStrbufClear(strbuf); @@ -647,7 +634,6 @@ void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c) { } bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) { - assert(lineptr && n && buffer); assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0)); assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length)); @@ -678,7 +664,6 @@ bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffe } void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) { - assert(buffer && lineptr && n); assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0)); assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length)); @@ -842,8 +827,6 @@ bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLeng } bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf) { - assert(strbuf); - if (strbuf->length < 4) { return false; } diff --git a/src/common/impl/base64.c b/src/common/impl/base64.c index 50d7d4718..6db4db5f6 100644 --- a/src/common/impl/base64.c +++ b/src/common/impl/base64.c @@ -66,7 +66,7 @@ static void init_decode_table() { #define next_char(x) uint8_t x = decode_table[(uint8_t) *str++]; -bool ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output) { +void ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output) { if (*(uint64_t*) decode_table == 0) { init_decode_table(); } @@ -113,5 +113,4 @@ bool ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* *out = '\0'; *out_size = (uint32_t) (out - output); - return true; } diff --git a/src/common/impl/library.c b/src/common/impl/library.c index 83a315d97..757890d62 100644 --- a/src/common/impl/library.c +++ b/src/common/impl/library.c @@ -161,8 +161,8 @@ void* dlsym(void* handle, const char* symbol) { } void* ffLibraryGetModule(const wchar_t* libraryFileName) { - assert(libraryFileName != nullptr && "Use \"ffGetPeb()->ImageBaseAddress\" instead"); - + // `libraryFileName` is non-null per the `nonnull(1)` contract. + // Prefer `ffGetPeb()->ImageBaseAddress` over looking the main module up by name. void* module = nullptr; USHORT libraryFileNameBytes = (USHORT) (wcslen(libraryFileName) * sizeof(wchar_t) + sizeof(wchar_t)); NTSTATUS status = LdrGetDllHandle(nullptr, nullptr, &(UNICODE_STRING) { diff --git a/src/common/impl/networking_common.c b/src/common/impl/networking_common.c index cc6f5e24b..949cc9ce0 100644 --- a/src/common/impl/networking_common.c +++ b/src/common/impl/networking_common.c @@ -64,7 +64,8 @@ static uint32_t guessGzipOutputSize(const void* data, uint32_t dataSize) { // Decompress gzip content bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd) { - assert(headerEnd != nullptr && *headerEnd == '\r'); + // `headerEnd` itself is non-null per the `nonnull(2)` contract; what it points to is not expressible as an attribute + assert(*headerEnd == '\r'); // Calculate header size uint32_t headerSize = (uint32_t) (headerEnd - buffer->chars); @@ -192,8 +193,6 @@ bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd) { #endif // FF_HAVE_ZLIB const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, const char* name, uint32_t* valueLen) { - assert(headers != nullptr && valueLen != nullptr); - uint32_t nameLen = (uint32_t) strlen(name); uint32_t pos = 0; @@ -225,8 +224,6 @@ const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, cons } FFNetworkingTransferEncoding ffNetworkingParseTransferEncoding(const char* value, uint32_t valueLen) { - assert(value != nullptr); - // The value is a comma-separated list of transfer codings (RFC 9112 6.1) uint32_t codingCount = 0; const char* coding = nullptr; @@ -267,8 +264,6 @@ FFNetworkingTransferEncoding ffNetworkingParseTransferEncoding(const char* value } int ffNetworkingChunkedComplete(const char* body, uint32_t bodyLen, uint32_t* consumed) { - assert(body != nullptr && consumed != nullptr); - uint32_t pos = 0; for (;;) { diff --git a/src/common/impl/option.c b/src/common/impl/option.c index 24b4c7acc..fb27abe71 100644 --- a/src/common/impl/option.c +++ b/src/common/impl/option.c @@ -7,8 +7,6 @@ // Return start position of the inner key if the argument key belongs to the module specified, nullptr otherwise const char* ffOptionTestPrefix(const char* argumentKey, const char* moduleName) { - assert(argumentKey && moduleName); - const char* subKey = argumentKey; if (!(subKey[0] == '-' && subKey[1] == '-')) { return nullptr; diff --git a/src/common/io.h b/src/common/io.h index eba05683f..c4a12fb3b 100644 --- a/src/common/io.h +++ b/src/common/io.h @@ -36,8 +36,8 @@ typedef int FFNativeFD; #endif // Only O_RDONLY is supported -HANDLE openat(HANDLE dfd, const char* fileName, int oflag); -HANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory); +[[gnu::nonnull(2)]] HANDLE openat(HANDLE dfd, const char* fileName, int oflag); +[[gnu::nonnull(2)]] HANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory); #endif static inline bool ffIsValidNativeFD(FFNativeFD fd) { @@ -51,8 +51,6 @@ static inline bool ffIsValidNativeFD(FFNativeFD fd) { [[gnu::always_inline, gnu::nonnull(1)]] static inline void wrapClose(FFNativeFD* pfd) { - assert(pfd); - if (ffIsValidNativeFD(*pfd)) { #ifndef _WIN32 close(*pfd); @@ -235,6 +233,7 @@ typedef enum FFPathType: uint8_t { [[gnu::format(scanf, 3, 4), gnu::nonnull(1, 3)]] const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...); // Not thread safe! +// Returns the previous state, which callers routinely discard (see ffUnsuppressIO), so not `nodiscard` bool ffSuppressIO(bool suppress); static inline void ffUnsuppressIO(bool* suppressed) { @@ -247,10 +246,9 @@ static inline void ffUnsuppressIO(bool* suppressed) { #define FF_SUPPRESS_IO() [[maybe_unused, gnu::cleanup(ffUnsuppressIO)]] bool io_suppressed__ = ffSuppressIO(true) -void ffListFilesRecursively(const char* path, bool pretty); +[[gnu::nonnull(1)]] void ffListFilesRecursively(const char* path, bool pretty); [[gnu::nonnull(1), gnu::always_inline]] static inline void wrapFclose(FILE** pfile) { - assert(pfile); if (*pfile) { fclose(*pfile); } @@ -260,14 +258,12 @@ void ffListFilesRecursively(const char* path, bool pretty); [[gnu::nonnull(1), gnu::always_inline]] #ifndef _WIN32 static inline void wrapClosedir(DIR** pdir) { - assert(pdir); if (*pdir) { closedir(*pdir); } } #else static inline void wrapClosedir(HANDLE* pdir) { - assert(pdir); if (*pdir) { FindClose(*pdir); } @@ -290,7 +286,8 @@ static inline void wrapClosedir(HANDLE* pdir) { } FFNativeFD ffGetNullFD(void); -bool ffRemoveFile(const char* fileName); +// Returns whether the file was removed; callers that only want it gone discard that, so not `nodiscard` +[[gnu::nonnull(1)]] bool ffRemoveFile(const char* fileName); // Modification time of a file, in milliseconds since the Unix epoch, or 0 if it can not be read. // The representation is uniform across platforms so that a value derived from it means the // same thing everywhere, which matters for callers that store it as a cache key. diff --git a/src/common/jsonconfig.h b/src/common/jsonconfig.h index a78aafd16..9790739db 100644 --- a/src/common/jsonconfig.h +++ b/src/common/jsonconfig.h @@ -3,25 +3,30 @@ #include "common/ffdata.h" #include "common/option.h" -bool ffJsonConfigParseModuleArgs(yyjson_val* key, yyjson_val* val, FFModuleArgs* moduleArgs); -const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[]); +// `key` / `val` may be null when the JSON object has no such member; `moduleArgs` is always real +[[gnu::nonnull(3)]] bool ffJsonConfigParseModuleArgs(yyjson_val* key, yyjson_val* val, FFModuleArgs* moduleArgs); +// `val` may be null (yyjson predicates tolerate it); `result` and `pairs` may not. +// Returns an error string, null on success, so it is `nodiscard`. +[[gnu::nonnull(2, 3), nodiscard]] const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[]); -yyjson_api_inline yyjson_mut_val* yyjson_mut_strbuf(yyjson_mut_doc* doc, const FFstrbuf* buf) { +// The three helpers below are routinely called as statements, to add a key to a document, so their +// result is not `nodiscard`. +[[gnu::nonnull(1, 2)]] yyjson_api_inline yyjson_mut_val* yyjson_mut_strbuf(yyjson_mut_doc* doc, const FFstrbuf* buf) { return yyjson_mut_strncpy(doc, buf->chars, buf->length); } -yyjson_api_inline bool yyjson_mut_obj_add_strbuf(yyjson_mut_doc* doc, +[[gnu::nonnull(1, 2, 3, 4)]] yyjson_api_inline bool yyjson_mut_obj_add_strbuf(yyjson_mut_doc* doc, yyjson_mut_val* obj, const char* _key, const FFstrbuf* buf) { return yyjson_mut_obj_add_strncpy(doc, obj, _key, buf->chars, buf->length); } -yyjson_api_inline bool yyjson_mut_arr_add_strbuf(yyjson_mut_doc* doc, +[[gnu::nonnull(1, 2, 3)]] yyjson_api_inline bool yyjson_mut_arr_add_strbuf(yyjson_mut_doc* doc, yyjson_mut_val* obj, const FFstrbuf* buf) { return yyjson_mut_arr_add_strncpy(doc, obj, buf->chars, buf->length); } -void ffPrintJsonConfig(FFdata* data, bool prepare); -void ffJsonConfigGenerateModuleArgsConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFModuleArgs* moduleArgs); +[[gnu::nonnull(1)]] void ffPrintJsonConfig(FFdata* data, bool prepare); +[[gnu::nonnull(1, 2, 3)]] void ffJsonConfigGenerateModuleArgsConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFModuleArgs* moduleArgs); diff --git a/src/common/library.h b/src/common/library.h index fc5ddbe95..a1b4d772a 100644 --- a/src/common/library.h +++ b/src/common/library.h @@ -21,8 +21,7 @@ int dlclose(void* handle); #define FF_LIBRARY_EXTENSION ".so" #endif -static inline void ffLibraryUnload(void** handle) { - assert(handle); +[[gnu::nonnull(1)]] static inline void ffLibraryUnload(void** handle) { if (*handle) { dlclose(*handle); } @@ -66,8 +65,8 @@ static inline void ffLibraryUnload(void** handle) { #define FF_LIBRARY_LOAD_SYMBOL_PTR(library, varName, symbolName, returnValue) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName)->ff##symbolName, symbolName, returnValue); -void* ffLibraryLoadSingle(const char* path, int maxVersion); -void* ffLibraryLoadMulti(const char* path, int maxVersion, ...); +[[gnu::nonnull(1), nodiscard]] void* ffLibraryLoadSingle(const char* path, int maxVersion); +[[gnu::nonnull(1), nodiscard]] void* ffLibraryLoadMulti(const char* path, int maxVersion, ...); #else @@ -109,10 +108,10 @@ void* ffLibraryLoadMulti(const char* path, int maxVersion, ...); #endif #if _WIN32 -void* ffLibraryGetModule(const wchar_t* libraryFileName); +[[gnu::nonnull(1), nodiscard]] void* ffLibraryGetModule(const wchar_t* libraryFileName); #endif // Return false to stop iterating, true to continue typedef bool (*FFLibraryIterateCallback)(const char* name, void* userData); // Iterate over all loaded dynamic libraries. Returns true on success, false on failure. -bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback callback, void* userData); +[[nodiscard]] bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback callback, void* userData); diff --git a/src/common/mallocHelper.h b/src/common/mallocHelper.h index e0c182948..e3d8321ea 100644 --- a/src/common/mallocHelper.h +++ b/src/common/mallocHelper.h @@ -15,7 +15,6 @@ [[gnu::always_inline, gnu::nonnull(1)]] static inline void ffWrapFree(const void* pPtr) { - assert(pPtr); if (*(void**) pPtr) { free(*(void**) pPtr); } @@ -24,8 +23,8 @@ static inline void ffWrapFree(const void* pPtr) { #define FF_AUTO_FREE [[gnu::cleanup(ffWrapFree)]] // ptr MUST be a malloc'ed pointer +[[gnu::nonnull(1), nodiscard]] static inline size_t ffMallocUsableSize(const void* ptr) { - assert(ptr); #if FF_HAVE_MALLOC_USABLE_SIZE return malloc_usable_size((void*) ptr); #elif FF_HAVE_MALLOC_SIZE diff --git a/src/common/networking.h b/src/common/networking.h index f7dbba422..55949027d 100644 --- a/src/common/networking.h +++ b/src/common/networking.h @@ -29,14 +29,16 @@ typedef struct FFNetworkingState { bool tfo; // if true, TCP Fast Open will be attempted first, and fallback to traditional connection if it fails } FFNetworkingState; -const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers); -const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer); +// `headers` is optional and may be null; the other pointer arguments are dereferenced unconditionally. +// Both functions report failure through their return value, which must be checked. +[[gnu::nonnull(1, 2, 4), nodiscard]] const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers); +[[gnu::nonnull(1, 2), nodiscard]] const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer); // Case-insensitive header lookup restricted to the header block [0, headerEnd). // Restricting the range matters because the body may already share the same buffer. // Returns a pointer to the first character of the value; `valueLen` receives its // length excluding the terminating CRLF. Returns nullptr when the header is absent. -const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, const char* name, uint32_t* valueLen); +[[gnu::nonnull(1, 3, 4), gnu::pure, nodiscard]] const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, const char* name, uint32_t* valueLen); // Checks whether a `Transfer-Encoding: chunked` body has been received in full, so that // framing does not have to rely on the server closing the connection. @@ -44,11 +46,12 @@ const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, cons // 0 when more data is needed, and -1 when the body is malformed. // The caller must not wait for a fixed amount of data (e.g. `MSG_WAITALL`) while the // response length is still unknown, otherwise this check never gets to run. -int ffNetworkingChunkedComplete(const char* body, uint32_t bodyLen, uint32_t* consumed); +// Not `pure`: the `strtoul` it calls writes `errno`. +[[gnu::nonnull(1, 3), nodiscard]] int ffNetworkingChunkedComplete(const char* body, uint32_t bodyLen, uint32_t* consumed); // Decodes a `Transfer-Encoding: chunked` body in place and rewrites the response with a // `Content-Length` header in place of `Transfer-Encoding`. -bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t headerEnd); +[[gnu::nonnull(1), nodiscard]] bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t headerEnd); // Result of parsing a `Transfer-Encoding` header value typedef enum FFNetworkingTransferEncoding { @@ -62,9 +65,9 @@ typedef enum FFNetworkingTransferEncoding { // and any other coding (e.g. `gzip, chunked`) leaves the payload encoded, which this // client cannot decode. Only a lone `chunked` is accepted; everything else is reported // as unsupported so that the caller fails the response instead of returning garbage. -FFNetworkingTransferEncoding ffNetworkingParseTransferEncoding(const char* value, uint32_t valueLen); +[[gnu::nonnull(1), gnu::pure, nodiscard]] FFNetworkingTransferEncoding ffNetworkingParseTransferEncoding(const char* value, uint32_t valueLen); #ifdef FF_HAVE_ZLIB const char* ffNetworkingLoadZlibLibrary(void); -bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd); +[[gnu::nonnull(1, 2), nodiscard]] bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd); #endif diff --git a/src/common/option.h b/src/common/option.h index 0afaa449d..ba3940a94 100644 --- a/src/common/option.h +++ b/src/common/option.h @@ -89,19 +89,25 @@ typedef struct FFKeyValuePair { int value; } FFKeyValuePair; -const char* ffOptionTestPrefix(const char* argumentKey, const char* moduleName); -void ffOptionParseString(const char* argumentKey, const char* value, FFstrbuf* buffer); -[[nodiscard]] uint32_t ffOptionParseUInt32(const char* argumentKey, const char* value); -[[nodiscard]] int32_t ffOptionParseInt32(const char* argumentKey, const char* value); -[[nodiscard]] int ffOptionParseEnum(const char* argumentKey, const char* requestedKey, FFKeyValuePair pairs[]); -[[nodiscard]] bool ffOptionParseBoolean(const char* str); -void ffOptionParseColorNoClear(const char* value, FFstrbuf* buffer); -static inline void ffOptionParseColor(const char* value, FFstrbuf* buffer) { +// `moduleName` and `argumentKey` are both dereferenced unconditionally; reads no global state, so `pure` +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] const char* ffOptionTestPrefix(const char* argumentKey, const char* moduleName); +// `value` is optional (null exits with a usage error), `argumentKey` and `buffer` are not +[[gnu::nonnull(1, 3)]] void ffOptionParseString(const char* argumentKey, const char* value, FFstrbuf* buffer); +[[gnu::nonnull(1), nodiscard]] uint32_t ffOptionParseUInt32(const char* argumentKey, const char* value); +[[gnu::nonnull(1), nodiscard]] int32_t ffOptionParseInt32(const char* argumentKey, const char* value); +// `requestedKey` is optional (null exits with a usage error); `pairs` is walked until its null key +[[gnu::nonnull(1, 3), nodiscard]] int ffOptionParseEnum(const char* argumentKey, const char* requestedKey, FFKeyValuePair pairs[]); +// `str` is optional: a null or empty value is reported as `true` +[[gnu::pure, nodiscard]] bool ffOptionParseBoolean(const char* str); +// `value` is optional (a null or empty value is a no-op); `buffer` is not +[[gnu::nonnull(2)]] void ffOptionParseColorNoClear(const char* value, FFstrbuf* buffer); +[[gnu::nonnull(2)]] static inline void ffOptionParseColor(const char* value, FFstrbuf* buffer) { ffStrbufClear(buffer); ffOptionParseColorNoClear(value, buffer); } -static inline void ffOptionInitModuleArg(FFModuleArgs* args, const char* icon) { +// `icon` is optional, `args` is not +[[gnu::nonnull(1)]] static inline void ffOptionInitModuleArg(FFModuleArgs* args, const char* icon) { ffStrbufInit(&args->key); ffStrbufInit(&args->keyColor); ffStrbufInitStatic(&args->keyIcon, icon); @@ -110,7 +116,7 @@ static inline void ffOptionInitModuleArg(FFModuleArgs* args, const char* icon) { args->keyWidth = 0; } -static inline void ffOptionDestroyModuleArg(FFModuleArgs* args) { +[[gnu::nonnull(1)]] static inline void ffOptionDestroyModuleArg(FFModuleArgs* args) { ffStrbufDestroy(&args->key); ffStrbufDestroy(&args->keyColor); ffStrbufDestroy(&args->keyIcon); diff --git a/src/common/parsing.h b/src/common/parsing.h index c3a4bee93..cb6446620 100644 --- a/src/common/parsing.h +++ b/src/common/parsing.h @@ -17,8 +17,9 @@ typedef struct FFColorRangeConfig { #define FF_VERSION_INIT ((FFVersion) { 0 }) -void ffParseSemver(FFstrbuf* buffer, const FFstrbuf* major, const FFstrbuf* minor, const FFstrbuf* patch); -void ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, const FFstrbuf* gtk4); +// Every argument is dereferenced unconditionally +[[gnu::nonnull(1, 2, 3, 4)]] void ffParseSemver(FFstrbuf* buffer, const FFstrbuf* major, const FFstrbuf* minor, const FFstrbuf* patch); +[[gnu::nonnull(1, 2, 3, 4)]] void ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, const FFstrbuf* gtk4); -void ffVersionToPretty(const FFVersion* version, FFstrbuf* pretty); -int8_t ffVersionCompare(const FFVersion* version1, const FFVersion* version2); +[[gnu::nonnull(1, 2)]] void ffVersionToPretty(const FFVersion* version, FFstrbuf* pretty); +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] int8_t ffVersionCompare(const FFVersion* version1, const FFVersion* version2); diff --git a/src/common/path.h b/src/common/path.h index 0d292d868..dafcc8d5d 100644 --- a/src/common/path.h +++ b/src/common/path.h @@ -4,8 +4,8 @@ #include "common/strutil.h" #include "fastfetch_config.h" -const char* ffFindExecutableInPath(const char* name, FFstrbuf* result); -static inline bool ffIsAbsolutePath(const char* path) { +[[gnu::nonnull(1, 2), nodiscard]] const char* ffFindExecutableInPath(const char* name, FFstrbuf* result); +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffIsAbsolutePath(const char* path) { #ifdef _WIN32 return (ffCharIsEnglishAlphabet(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) // drive letter path || (path[0] == '\\' && path[1] == '\\'); // UNC path @@ -34,4 +34,4 @@ ssize_t readlink(const char* path, char* buf, size_t bufsiz); #define FF_PATH_PKG_BASE "/usr/pkg" #else #define FF_PATH_PKG_BASE FASTFETCH_TARGET_DIR_USR -#endif \ No newline at end of file +#endif diff --git a/src/common/percent.h b/src/common/percent.h index 5e71dc7d7..a71a62267 100644 --- a/src/common/percent.h +++ b/src/common/percent.h @@ -30,13 +30,14 @@ typedef struct FFPercentageModuleConfig { // [yellow, green): print yellow // [0, yellow): print red -void ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, const FFModuleArgs* module); -void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, bool parentheses, const FFModuleArgs* module); +// `buffer` and `module` are dereferenced unconditionally; `module` carries the key/color formatting +[[gnu::nonnull(1, 4)]] void ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, const FFModuleArgs* module); +[[gnu::nonnull(1, 5)]] void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, bool parentheses, const FFModuleArgs* module); typedef struct yyjson_val yyjson_val; typedef struct yyjson_mut_doc yyjson_mut_doc; typedef struct yyjson_mut_val yyjson_mut_val; bool ffPercentParseCommandOptions(const char* key, const char* subkey, const char* value, FFPercentageModuleConfig* config); bool ffPercentParseJsonObject(yyjson_val* key, yyjson_val* value, FFPercentageModuleConfig* config); -void ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config); +[[gnu::nonnull(1, 2)]] void ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config); const char* ffPercentParseTypeJsonConfig(yyjson_val* value, FFPercentageTypeFlags* result); diff --git a/src/common/printing.h b/src/common/printing.h index 29f8c1227..fd1f0a44d 100644 --- a/src/common/printing.h +++ b/src/common/printing.h @@ -11,10 +11,17 @@ typedef enum FFPrintType: uint8_t { FF_PRINT_TYPE_NO_CUSTOM_OUTPUT_FORMAT = 1 << 3, // reserved } FFPrintType; +// Both `moduleName` and `moduleArgs` are optional and explicitly handled: a null `moduleName` is what +// `--set-keyless` relies on, and a null `moduleArgs` (used by ffPrintError callers) skips the custom +// key entirely. So neither takes `nonnull`. void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType); +// Same for `moduleArgs`; `numArgs` / `arguments` are only read when `moduleArgs` is non-null, so +// `arguments` is not `nonnull(6)` either. At least one caller discards the result, so not `nodiscard`. bool ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments); #define FF_PRINT_FORMAT_CHECKED(moduleName, moduleIndex, moduleArgs, printType, arguments) \ ffPrintFormat((moduleName), (moduleIndex), (moduleArgs), (printType), (sizeof(arguments) / sizeof(*arguments)), (arguments)); -[[gnu::format(printf, 5, 6)]] void ffPrintError(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...); -void ffPrintColor(const FFstrbuf* colorValue); +// `moduleName` / `moduleArgs` are forwarded to ffPrintLogoAndKey, which accepts null for both; +// only `message` is dereferenced here (by `vprintf`). +[[gnu::format(printf, 5, 6), gnu::nonnull(5)]] void ffPrintError(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...); +[[gnu::nonnull(1)]] void ffPrintColor(const FFstrbuf* colorValue); void ffPrintCharTimes(char c, uint32_t times); diff --git a/src/common/properties.h b/src/common/properties.h index 789a757c3..438807240 100644 --- a/src/common/properties.h +++ b/src/common/properties.h @@ -7,41 +7,45 @@ typedef struct FFpropquery { FFstrbuf* buffer; } FFpropquery; -bool ffParsePropLines(const char* lines, const char* start, FFstrbuf* buffer); -bool ffParsePropFileValues(const char* filename, uint32_t numQueries, FFpropquery* queries); -bool ffParsePropFileHomeValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries); -bool ffParsePropFileListValues(const FFlist* list, const char* relativeFile, uint32_t numQueries, FFpropquery* queries); +// These report "was the file/property found", which callers routinely discard, so they are not +// `nodiscard`. +// +// `queries` is only walked when `numQueries > 0`, so it is intentionally not `nonnull(3)`. +[[gnu::nonnull(1, 2, 3)]] bool ffParsePropLines(const char* lines, const char* start, FFstrbuf* buffer); +[[gnu::nonnull(1)]] bool ffParsePropFileValues(const char* filename, uint32_t numQueries, FFpropquery* queries); +[[gnu::nonnull(1)]] bool ffParsePropFileHomeValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries); +[[gnu::nonnull(1, 2)]] bool ffParsePropFileListValues(const FFlist* list, const char* relativeFile, uint32_t numQueries, FFpropquery* queries); -bool ffParsePropLinePointer(const char** line, const char* start, FFstrbuf* buffer); +[[gnu::nonnull(1, 2, 3)]] bool ffParsePropLinePointer(const char** line, const char* start, FFstrbuf* buffer); -static inline bool ffParsePropLine(const char* line, const char* start, FFstrbuf* buffer) { +[[gnu::nonnull(1, 2, 3)]] static inline bool ffParsePropLine(const char* line, const char* start, FFstrbuf* buffer) { return ffParsePropLinePointer(&line, start, buffer); } -static inline bool ffParsePropFile(const char* filename, const char* start, FFstrbuf* buffer) { +[[gnu::nonnull(1, 2, 3)]] static inline bool ffParsePropFile(const char* filename, const char* start, FFstrbuf* buffer) { return ffParsePropFileValues(filename, 1, (FFpropquery[]) { { start, buffer } }); } -static inline bool ffParsePropFileHome(const char* relativeFile, const char* start, FFstrbuf* buffer) { +[[gnu::nonnull(1, 2, 3)]] static inline bool ffParsePropFileHome(const char* relativeFile, const char* start, FFstrbuf* buffer) { return ffParsePropFileHomeValues(relativeFile, 1, (FFpropquery[]) { { start, buffer } }); } -static inline bool ffParsePropFileList(const FFlist* list, const char* relativeFile, const char* start, FFstrbuf* buffer) { +[[gnu::nonnull(1, 2, 3, 4)]] static inline bool ffParsePropFileList(const FFlist* list, const char* relativeFile, const char* start, FFstrbuf* buffer) { return ffParsePropFileListValues(list, relativeFile, 1, (FFpropquery[]) { { start, buffer } }); } -static inline bool ffParsePropFileConfigValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries) { +[[gnu::nonnull(1)]] static inline bool ffParsePropFileConfigValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries) { return ffParsePropFileListValues(&instance.state.platform.configDirs, relativeFile, numQueries, queries); } -static inline bool ffParsePropFileConfig(const char* relativeFile, const char* start, FFstrbuf* buffer) { +[[gnu::nonnull(1, 2, 3)]] static inline bool ffParsePropFileConfig(const char* relativeFile, const char* start, FFstrbuf* buffer) { return ffParsePropFileConfigValues(relativeFile, 1, (FFpropquery[]) { { start, buffer } }); } -static inline bool ffParsePropFileDataValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries) { +[[gnu::nonnull(1)]] static inline bool ffParsePropFileDataValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries) { return ffParsePropFileListValues(&instance.state.platform.dataDirs, relativeFile, numQueries, queries); } -static inline bool ffParsePropFileData(const char* relativeFile, const char* start, FFstrbuf* buffer) { +[[gnu::nonnull(1, 2, 3)]] static inline bool ffParsePropFileData(const char* relativeFile, const char* start, FFstrbuf* buffer) { return ffParsePropFileDataValues(relativeFile, 1, (FFpropquery[]) { { start, buffer } }); } diff --git a/src/common/size.h b/src/common/size.h index 7f1da020c..a78f3a9bf 100644 --- a/src/common/size.h +++ b/src/common/size.h @@ -2,4 +2,4 @@ #include "fastfetch.h" -void ffSizeAppendNum(uint64_t bytes, FFstrbuf* result); +[[gnu::nonnull(2)]] void ffSizeAppendNum(uint64_t bytes, FFstrbuf* result); diff --git a/src/common/smbios.h b/src/common/smbios.h index 15c0b0ce6..cd5418f19 100644 --- a/src/common/smbios.h +++ b/src/common/smbios.h @@ -2,8 +2,9 @@ #include "common/FFstrbuf.h" -bool ffIsSmbiosValueSet(FFstrbuf* value); -static inline void ffCleanUpSmbiosValue(FFstrbuf* value) { +// Not `pure`: it trims the trailing spaces of `value` before reporting whether it is set +[[gnu::nonnull(1), nodiscard]] bool ffIsSmbiosValueSet(FFstrbuf* value); +[[gnu::nonnull(1)]] static inline void ffCleanUpSmbiosValue(FFstrbuf* value) { if (!ffIsSmbiosValueSet(value)) { ffStrbufClear(value); } @@ -82,7 +83,8 @@ typedef struct [[gnu::packed]] FFSmbiosHeader { } FFSmbiosHeader; static_assert(sizeof(FFSmbiosHeader) == 4, "FFSmbiosHeader should be 4 bytes"); -static inline const char* ffSmbiosLocateString(const char* start, uint8_t index /* start from 1 */) { +// `start` points into the unformatted string section and is walked with `strlen`, so it may not be null +[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline const char* ffSmbiosLocateString(const char* start, uint8_t index /* start from 1 */) { if (index == 0 || *start == '\0') { return nullptr; } @@ -94,9 +96,9 @@ static inline const char* ffSmbiosLocateString(const char* start, uint8_t index typedef const FFSmbiosHeader* FFSmbiosHeaderTable[FF_SMBIOS_TYPE__MAX]; -const FFSmbiosHeader* ffSmbiosNextEntry(const FFSmbiosHeader* header); -const FFSmbiosHeaderTable* ffGetSmbiosHeaderTable(void); +[[gnu::nonnull(1), gnu::pure, nodiscard]] const FFSmbiosHeader* ffSmbiosNextEntry(const FFSmbiosHeader* header); +[[nodiscard]] const FFSmbiosHeaderTable* ffGetSmbiosHeaderTable(void); #ifdef __linux__ -bool ffGetSmbiosValue(const char* devicesPath, const char* classPath, FFstrbuf* buffer); +[[gnu::nonnull(1, 2, 3), nodiscard]] bool ffGetSmbiosValue(const char* devicesPath, const char* classPath, FFstrbuf* buffer); #endif diff --git a/src/common/strutil.h b/src/common/strutil.h index a9c43c495..ee30dbc41 100644 --- a/src/common/strutil.h +++ b/src/common/strutil.h @@ -14,6 +14,15 @@ __stdcall char* StrStrIA(const char* lpFirst, const char* lpSrch); #define FF_STR_INDIR(x) #x #define FF_STR(x) FF_STR_INDIR(x) +// Everything below is a leaf predicate over the bytes it is given: it reads memory through its +// arguments and nothing else, so `gnu::pure` is accurate. `gnu::const` is deliberately not used -- +// these are already `always_inline`, so it would buy no optimization, and it would become a lie the +// day one of them is rewritten around a lookup table. +// +// The `gnu::nonnull` indexes name the arguments that are dereferenced unconditionally. `ffStrSet` +// and `ffStrCopy` are not listed: both handle a null pointer explicitly. + +[[nodiscard, gnu::pure]] static inline bool ffStrSet(const char* str) { if (str == nullptr) { return false; @@ -26,21 +35,22 @@ static inline bool ffStrSet(const char* str) { return *str != '\0'; } -[[gnu::always_inline]] +[[gnu::always_inline, gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrStartsWithIgnCase(const char* str, const char* compareTo) { return strncasecmp(str, compareTo, strlen(compareTo)) == 0; } -[[gnu::always_inline]] +[[gnu::always_inline, gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrEqualsIgnCase(const char* str, const char* compareTo) { return strcasecmp(str, compareTo) == 0; } -[[gnu::always_inline]] +[[gnu::always_inline, gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrStartsWith(const char* str, const char* compareTo) { return strncmp(str, compareTo, strlen(compareTo)) == 0; } +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrEndsWith(const char* str, const char* compareTo) { size_t strLength = strlen(str); size_t compareToLength = strlen(compareTo); @@ -50,6 +60,7 @@ static inline bool ffStrEndsWith(const char* str, const char* compareTo) { return memcmp(str + strLength - compareToLength, compareTo, compareToLength) == 0; } +[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrEndsWithIgnCase(const char* str, const char* compareTo) { size_t strLength = strlen(str); size_t compareToLength = strlen(compareTo); @@ -59,32 +70,32 @@ static inline bool ffStrEndsWithIgnCase(const char* str, const char* compareTo) return strncasecmp(str + strLength - compareToLength, compareTo, compareToLength) == 0; } -[[gnu::always_inline]] +[[gnu::always_inline, gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrEquals(const char* str, const char* compareTo) { return strcmp(str, compareTo) == 0; } -[[gnu::always_inline]] +[[gnu::always_inline, gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrContains(const char* str, const char* compareTo) { return strstr(str, compareTo) != nullptr; } -[[gnu::always_inline]] +[[gnu::always_inline, gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrContainsIgnCase(const char* str, const char* compareTo) { return strcasestr(str, compareTo) != nullptr; } -[[gnu::always_inline]] +[[gnu::always_inline, gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrContainsC(const char* str, char compareTo) { return strchr(str, compareTo) != nullptr; } -[[gnu::always_inline]] +[[gnu::always_inline, gnu::pure, nodiscard]] static inline bool ffCharIsEnglishAlphabet(char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); } -[[gnu::always_inline]] +[[gnu::always_inline, gnu::pure, nodiscard]] static inline bool ffCharIsDigit(char c) { return '0' <= c && c <= '9'; } @@ -92,15 +103,19 @@ static inline bool ffCharIsDigit(char c) { // Parse one UTF-8 character, returning consumed byte count and display width. // Invalid / incomplete sequence falls back to one-byte width=1. // If the Unicode codepoint is non-printable, width becomes 0. +// Not `pure`: it stores the width through `width` when that is not null. +[[gnu::nonnull(1)]] uint8_t ffUtf8CharLenWidth(const char* str, uint32_t length, uint8_t* width); +[[gnu::nonnull(1), gnu::pure, nodiscard]] uint32_t ffUtf8StrWidth(const char* str, uint32_t length); -[[gnu::always_inline]] +[[gnu::always_inline, gnu::pure, nodiscard]] static inline bool ffCharIsHexDigit(char c) { return ffCharIsDigit(c) || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'); } +[[gnu::always_inline, gnu::pure, nodiscard]] static inline int8_t ffHexCharToInt(char c) { if (ffCharIsDigit(c)) { return (int8_t) (c - '0'); @@ -114,6 +129,9 @@ static inline int8_t ffHexCharToInt(char c) { } // Copies at most (dstBufSiz - 1) bytes from src to dst; dst is always null-terminated +// Returns a pointer to the end of the copy, which callers are free to ignore, so not `nodiscard`. +// Not `pure`: it writes through `dst`. `dst` may be null, `src` may not. +[[gnu::nonnull(2)]] static inline char* ffStrCopy(char* __restrict__ dst, const char* __restrict__ src, size_t dstBufSiz) { if (__builtin_expect(dst == nullptr, false) || dstBufSiz == 0) { return dst; diff --git a/src/common/sysctl.h b/src/common/sysctl.h index 9d12df6fc..b40c3883c 100644 --- a/src/common/sysctl.h +++ b/src/common/sysctl.h @@ -5,12 +5,13 @@ #include #include +// `result` is always written to, and `propName` is always read by the sysctl wrapper. #ifdef __OpenBSD__ -const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result); +[[gnu::nonnull(3)]] const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result); [[nodiscard]] int ffSysctlGetInt(int mib1, int mib2, int defaultValue); [[nodiscard]] int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue); #else -const char* ffSysctlGetString(const char* propName, FFstrbuf* result); -[[nodiscard]] int ffSysctlGetInt(const char* propName, int defaultValue); -[[nodiscard]] int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue); +[[gnu::nonnull(1, 2)]] const char* ffSysctlGetString(const char* propName, FFstrbuf* result); +[[gnu::nonnull(1), nodiscard]] int ffSysctlGetInt(const char* propName, int defaultValue); +[[gnu::nonnull(1), nodiscard]] int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue); #endif diff --git a/src/common/temps.h b/src/common/temps.h index f2f8eda89..4ddb3f096 100644 --- a/src/common/temps.h +++ b/src/common/temps.h @@ -3,7 +3,8 @@ #include "common/parsing.h" #include "common/option.h" -void ffTempsAppendNum(double celsius, FFstrbuf* buffer, FFColorRangeConfig config, const FFModuleArgs* module); +// `buffer` and `module` are dereferenced unconditionally; `module` carries the key/color formatting +[[gnu::nonnull(2, 4)]] void ffTempsAppendNum(double celsius, FFstrbuf* buffer, FFColorRangeConfig config, const FFModuleArgs* module); bool ffTempsParseCommandOptions(const char* key, const char* subkey, const char* value, bool* useTemp, FFColorRangeConfig* config); bool ffTempsParseJsonObject(yyjson_val* key, yyjson_val* value, bool* useTemp, FFColorRangeConfig* config); -void ffTempsGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, bool temp, FFColorRangeConfig config); +[[gnu::nonnull(1, 2)]] void ffTempsGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, bool temp, FFColorRangeConfig config); diff --git a/src/common/thread.h b/src/common/thread.h index c8212c255..43b72ba43 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -11,13 +11,14 @@ #define FF_THREAD_MUTEX_INITIALIZER SRWLOCK_INIT typedef SRWLOCK FFThreadMutex; typedef HANDLE FFThreadType; -static inline void ffThreadMutexLock(FFThreadMutex* mutex) { +[[gnu::nonnull(1)]] static inline void ffThreadMutexLock(FFThreadMutex* mutex) { AcquireSRWLockExclusive(mutex); } -static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { +[[gnu::nonnull(1)]] static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { ReleaseSRWLockExclusive(mutex); } -static inline FFThreadType ffThreadCreate(unsigned(__stdcall* func)(void*), void* data) { +// `data` is the thread argument and may be null; `func` may not +[[gnu::nonnull(1)]] static inline FFThreadType ffThreadCreate(unsigned(__stdcall* func)(void*), void* data) { return (FFThreadType) _beginthreadex(nullptr, 0, func, data, 0, nullptr); } #define FF_THREAD_ENTRY_DECL_WRAPPER(fn, paramType) \ @@ -56,23 +57,24 @@ typedef pthread_t FFThreadType; #include #define FF_THREAD_MUTEX_INITIALIZER OS_UNFAIR_LOCK_INIT typedef os_unfair_lock FFThreadMutex; -static inline void ffThreadMutexLock(os_unfair_lock* mutex) { +[[gnu::nonnull(1)]] static inline void ffThreadMutexLock(os_unfair_lock* mutex) { os_unfair_lock_lock(mutex); } -static inline void ffThreadMutexUnlock(os_unfair_lock* mutex) { +[[gnu::nonnull(1)]] static inline void ffThreadMutexUnlock(os_unfair_lock* mutex) { os_unfair_lock_unlock(mutex); } #else #define FF_THREAD_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER typedef pthread_mutex_t FFThreadMutex; -static inline void ffThreadMutexLock(FFThreadMutex* mutex) { +[[gnu::nonnull(1)]] static inline void ffThreadMutexLock(FFThreadMutex* mutex) { pthread_mutex_lock(mutex); } -static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { +[[gnu::nonnull(1)]] static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { pthread_mutex_unlock(mutex); } #endif -static inline FFThreadType ffThreadCreate(void* (*func)(void*), void* data) { +// `data` is the thread argument and may be null; `func` may not +[[gnu::nonnull(1)]] static inline FFThreadType ffThreadCreate(void* (*func)(void*), void* data) { FFThreadType newThread = 0; pthread_create(&newThread, nullptr, func, data); return newThread; @@ -115,6 +117,7 @@ static inline uintptr_t ffThreadGetCurrentId() { #else // FF_HAVE_THREADS #define FF_THREAD_MUTEX_INITIALIZER 0 typedef char FFThreadMutex; +// The stubs deliberately ignore their argument, so no `nonnull` here static inline void ffThreadMutexLock([[maybe_unused]] FFThreadMutex* mutex) {} static inline void ffThreadMutexUnlock([[maybe_unused]] FFThreadMutex* mutex) {} #define FF_THREAD_ENTRY_DECL_WRAPPER(fn, paramType) diff --git a/src/common/wcwidth.h b/src/common/wcwidth.h index 8d093da52..ada6279f4 100644 --- a/src/common/wcwidth.h +++ b/src/common/wcwidth.h @@ -3,9 +3,10 @@ #include #if FF_ENABLE_WCWIDTH -int mk_wcwidth(uint32_t wc); +// A pure table lookup: the width depends only on the code point +[[gnu::pure, nodiscard]] int mk_wcwidth(uint32_t wc); #else -static inline int mk_wcwidth(uint32_t wc) { +[[gnu::pure, nodiscard]] static inline int mk_wcwidth(uint32_t wc) { (void) wc; return 1; }