diff --git a/API/fleece/CompilerSupport.h b/API/fleece/CompilerSupport.h index 31f52ff0..8b51a3a4 100644 --- a/API/fleece/CompilerSupport.h +++ b/API/fleece/CompilerSupport.h @@ -14,13 +14,22 @@ #ifndef _FLEECE_COMPILER_SUPPORT_H #define _FLEECE_COMPILER_SUPPORT_H +#ifdef __APPLE__ +#include // include this first to avoid conflict with our definition of __printflike +#endif + + // The __has_xxx() macros are supported by [at least] Clang and GCC. // Define them to return 0 on other compilers. // https://clang.llvm.org/docs/AttributeReference.html // https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html #ifndef __has_attribute - #define __has_attribute(x) 0 +#define __has_attribute(x) 0 +#endif + +#ifndef __has_cpp_attribute +#define __has_cpp_attribute(x) 0 #endif #ifndef __has_builtin @@ -43,24 +52,22 @@ # define RETURNS_NONNULL #endif -// deprecated; use NODISCARD instead -#if __has_attribute(returns_nonnull) -# define MUST_USE_RESULT __attribute__((warn_unused_result)) -#else -# define MUST_USE_RESULT -#endif -// NODISCARD expands to the C++17/C23 `[[nodiscard]]` attribute, or else MUST_USE_RESULT. -// (We can't just redefine MUST_USE_RESULT as `[[nodiscard]]` unfortunately, because the former is -// already in use in positions where `[[nodiscard]]` isn't valid, like at the end of a declaration.) +// NODISCARD expands to the C++17/C23 `[[nodiscard]]` attribute. +// Use it before a function declaration when it's a mistake to ignore the function's result. #if (__cplusplus >= 201700L) || (__STDC_VERSION__ >= 202000) # define NODISCARD [[nodiscard]] +#elif __has_attribute(warn_unused_result) +# define NODISCARD __attribute__((warn_unused_result)) #else -# define NODISCARD MUST_USE_RESULT +# define NODISCARD #endif + // These have no effect on behavior, but they hint to the optimizer which branch of an 'if' // statement to make faster. +// Note: In C++20 the standard attributes `[[likely]]` and `[[unlikely]]` can be used instead, +// but they're not syntactically identical. #if __has_builtin(__builtin_expect) #define _usuallyTrue(VAL) __builtin_expect(VAL, true) #define _usuallyFalse(VAL) __builtin_expect(VAL, false) @@ -75,7 +82,7 @@ // disallow NULL values, unless annotated with FL_NULLABLE (which must come after the `*`.) // (FL_NONNULL is occasionally necessary when there are multiple levels of pointers.) // NOTE: Only supported in Clang, so far. -#if defined(__clang__) +#if __has_feature(nullability) # define FL_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin") # define FL_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end") # define FL_NULLABLE _Nullable @@ -146,18 +153,6 @@ #endif -// `constexpr14` is for uses of `constexpr` that are valid in C++14 but not earlier. -// In constexpr functions this includes `if`, `for`, `while` statements; or multiple `return`s. -// The macro expands to `constexpr` in C++14 or later, otherwise to nothing. -#ifdef __cplusplus - #if __cplusplus >= 201400L || _MSVC_LANG >= 201400L - #define constexpr14 constexpr - #else - #define constexpr14 - #endif -#endif // __cplusplus - - // STEPOVER is for trivial little glue functions that are annoying to step into in the debugger // on the way to the function you _do_ want to step into. Examples are RefCounted's operator->, // or slice constructors. Suppressing debug info for those functions means the debugger @@ -279,6 +274,29 @@ #define FLAPI #endif +// `LIFETIMEBOUND` helps Clang detect value lifetime errors, where one value's lifetime is tied to another and the +// dependent value is used after the value it depends on exits scope. For examples of usage, see slice.hh. +// "The `lifetimebound` attribute on a function parameter or implicit object parameter indicates that objects that are +// referred to by that parameter may also be referred to by the return value of the annotated function (or, for a +// parameter of a constructor, by the value of the constructed object)." +// -- https://clang.llvm.org/docs/AttributeReference.html#lifetimebound +#if __has_cpp_attribute(clang::lifetimebound) +# define LIFETIMEBOUND [[clang::lifetimebound]] +#else +# define LIFETIMEBOUND +#endif + + +// Type-checking for printf-style vararg functions: +#ifndef __printflike +# if __has_attribute(__format__) +# define __printflike(fmtarg, firstvararg) __attribute__((__format__(__printf__, fmtarg, firstvararg))) +# else +# define __printflike(A, B) +# endif +#endif + + #else // _FLEECE_COMPILER_SUPPORT_H #warn "Compiler is not honoring #pragma once" #endif diff --git a/API/fleece/FLDoc.h b/API/fleece/FLDoc.h index b62d8f33..6fd91518 100644 --- a/API/fleece/FLDoc.h +++ b/API/fleece/FLDoc.h @@ -48,13 +48,13 @@ extern "C" { FLEECE_PUBLIC FLDoc FLDoc_Retain(FLDoc FL_NULLABLE) FLAPI; /** Returns the encoded Fleece data backing the document. */ - FLEECE_PUBLIC FLSlice FLDoc_GetData(FLDoc FL_NULLABLE) FLAPI FLPURE; + FLEECE_PUBLIC FLSlice FLDoc_GetData(FLDoc FL_NULLABLE doc LIFETIMEBOUND) FLAPI FLPURE; /** Returns the FLSliceResult data owned by the document, if any, else a null slice. */ FLEECE_PUBLIC FLSliceResult FLDoc_GetAllocedData(FLDoc FL_NULLABLE) FLAPI FLPURE; /** Returns the root value in the FLDoc, usually an FLDict. */ - FLEECE_PUBLIC FLValue FLDoc_GetRoot(FLDoc FL_NULLABLE) FLAPI FLPURE; + FLEECE_PUBLIC FLValue FLDoc_GetRoot(FLDoc FL_NULLABLE doc LIFETIMEBOUND) FLAPI FLPURE; /** Returns the FLSharedKeys used by this FLDoc, as specified when it was created. */ FLEECE_PUBLIC FLSharedKeys FLDoc_GetSharedKeys(FLDoc FL_NULLABLE) FLAPI FLPURE; diff --git a/API/fleece/FLEncoder.h b/API/fleece/FLEncoder.h index fe328fd5..261c5ed4 100644 --- a/API/fleece/FLEncoder.h +++ b/API/fleece/FLEncoder.h @@ -213,7 +213,6 @@ extern "C" { /** Ends encoding; if there has been no error, it returns the encoded data, else null. This does not free the FLEncoder; call FLEncoder_Free (or FLEncoder_Reset) next. */ - NODISCARD FLEECE_PUBLIC FLSliceResult FLEncoder_Finish(FLEncoder, FLError* FL_NULLABLE outError) FLAPI; /** @} */ @@ -226,7 +225,7 @@ extern "C" { FLEECE_PUBLIC FLError FLEncoder_GetError(FLEncoder) FLAPI; /** Returns the error message of an encoder, or NULL if there's no error. */ - FLEECE_PUBLIC const char* FL_NULLABLE FLEncoder_GetErrorMessage(FLEncoder) FLAPI; + FLEECE_PUBLIC const char* FL_NULLABLE FLEncoder_GetErrorMessage(FLEncoder e LIFETIMEBOUND) FLAPI; /** @} */ /** @} */ diff --git a/API/fleece/FLSlice.h b/API/fleece/FLSlice.h index 73d8f732..a1657338 100644 --- a/API/fleece/FLSlice.h +++ b/API/fleece/FLSlice.h @@ -47,8 +47,11 @@ typedef struct FLSlice { size_t size; #ifdef __cplusplus - explicit operator bool() const noexcept FLPURE {return buf != nullptr;} - explicit operator std::string() const {return std::string((char*)buf, size);} + constexpr const void* FL_NULLABLE data() const noexcept FLPURE {return buf;} + constexpr size_t size_bytes() const noexcept FLPURE {return size;} // compatibility with std::span + constexpr bool empty() const noexcept FLPURE {return size == 0;} + constexpr explicit operator bool() const noexcept FLPURE {return buf != nullptr;} + explicit operator std::string() const {return std::string((char*)buf, size);} #endif } FLSlice; @@ -65,8 +68,11 @@ struct NODISCARD FLSliceResult { size_t size; #ifdef __cplusplus - explicit operator bool() const noexcept FLPURE {return buf != nullptr;} - explicit operator FLSlice () const {return {buf, size};} + constexpr const void* FL_NULLABLE data() const noexcept FLPURE {return buf;} + constexpr size_t size_bytes() const noexcept FLPURE {return size;} // compatibility with std::span + constexpr bool empty() const noexcept FLPURE {return size == 0;} + constexpr explicit operator bool() const noexcept FLPURE {return buf != nullptr;} + constexpr explicit operator FLSlice () const {return {buf, size};} inline explicit operator std::string() const; #endif }; @@ -123,7 +129,7 @@ inline void FLMemCpy(void* FL_NULLABLE dst, const void* FL_NULLABLE src, size_t It's OK to pass NULL; this returns an empty slice. \note If the string is a literal, it's more efficient to use \ref FLSTR instead. \note Performance is O(n) with the length of the string, since it has to call `strlen`. */ -inline FLSlice FLStr(const char* FL_NULLABLE str) FLAPI { +inline FLSlice FLStr(const char* FL_NULLABLE str LIFETIMEBOUND) FLAPI { FLSlice foo = { str, str ? strlen(str) : 0 }; return foo; } @@ -185,7 +191,7 @@ inline void FLSliceResult_Release(FLSliceResult s) FLAPI { } /** Type-casts a FLSliceResult to FLSlice, since C doesn't know it's a subclass. */ -inline FLSlice FLSliceResult_AsSlice(FLSliceResult sr) { +inline FLSlice FLSliceResult_AsSlice(FLSliceResult sr LIFETIMEBOUND) { FLSlice ret; memcpy(&ret, &sr, sizeof(ret)); return ret; diff --git a/API/fleece/Fleece.hh b/API/fleece/Fleece.hh index aac02d65..37ef6d9b 100644 --- a/API/fleece/Fleece.hh +++ b/API/fleece/Fleece.hh @@ -79,7 +79,6 @@ namespace fleece { bool isEqual(Value v) const {return FLValue_IsEqual(_val, v);} - Value& operator= (Value v) & {_val = v._val; return *this;} Value& operator= (std::nullptr_t) & {_val = nullptr; return *this;} inline Value operator[] (const KeyPath &kp) const; @@ -129,9 +128,8 @@ namespace fleece { inline Value operator[] (int index) const {return get(index);} inline Value operator[] (const KeyPath &kp) const {return Value::operator[](kp);} - Array& operator= (Array a) & {_val = a._val; return *this;} - Array& operator= (std::nullptr_t) & {_val = nullptr; return *this;} - Value& operator= (Value v) =delete; + Array& operator= (std::nullptr_t) & {_val = nullptr; return *this;} + Value& operator= (Value v) =delete; [[nodiscard]] inline MutableArray asMutable() const; @@ -149,7 +147,7 @@ namespace fleece { inline Value operator * () const {return value();} inline explicit operator bool() const {return (bool)value();} inline iterator& operator++ () {next(); return *this;} - inline bool operator!= (const iterator&) {return value() != nullptr;} + inline bool operator!= (const iterator&) const {return value() != nullptr;} inline Value operator[] (unsigned n) const {return FLArrayIterator_GetValueAt(this,n);} private: iterator() =default; @@ -187,7 +185,6 @@ namespace fleece { inline Value operator[] (const char *key) const {return get(key);} inline Value operator[] (const KeyPath &kp) const {return Value::operator[](kp);} - Dict& operator= (Dict d) & {_val = d._val; return *this;} Dict& operator= (std::nullptr_t) & {_val = nullptr; return *this;} Value& operator= (Value v) =delete; @@ -261,8 +258,8 @@ namespace fleece { KeyPath(slice_NONNULL spec, FLError* FL_NULLABLE err) :_path(FLKeyPath_New(spec, err)) { } ~KeyPath() {FLKeyPath_Free(_path);} - KeyPath(KeyPath &&kp) :_path(kp._path) {kp._path = nullptr;} - KeyPath& operator=(KeyPath &&kp) & {FLKeyPath_Free(_path); _path = kp._path; + KeyPath(KeyPath &&kp) noexcept :_path(kp._path) {kp._path = nullptr;} + KeyPath& operator=(KeyPath &&kp) & noexcept {FLKeyPath_Free(_path); _path = kp._path; kp._path = nullptr; return *this;} KeyPath(const KeyPath &kp) :KeyPath(std::string(kp), nullptr) { } @@ -334,7 +331,7 @@ namespace fleece { external pointers to. */ class Doc { public: - Doc(alloc_slice fleeceData, + Doc(const alloc_slice& fleeceData, FLTrust trust =kFLUntrusted, FLSharedKeys FL_NULLABLE sk =nullptr, slice externDest =nullslice) noexcept @@ -356,23 +353,23 @@ namespace fleece { Doc& operator=(Doc &&other) noexcept; ~Doc() {FLDoc_Release(_doc);} - slice data() const {return FLDoc_GetData(_doc);} + slice data() const LIFETIMEBOUND {return FLDoc_GetData(_doc);} alloc_slice allocedData() const {return FLDoc_GetAllocedData(_doc);} FLSharedKeys sharedKeys() const {return FLDoc_GetSharedKeys(_doc);} - Value root() const {return FLDoc_GetRoot(_doc);} + Value root() const LIFETIMEBOUND {return FLDoc_GetRoot(_doc);} explicit operator bool () const {return root() != nullptr;} Array asArray() const {return root().asArray();} Dict asDict() const {return root().asDict();} - operator Value () const {return root();} - operator Dict () const {return asDict();} - operator FLDict FL_NULLABLE () const {return asDict();} + operator Value () const LIFETIMEBOUND {return root();} + operator Dict () const LIFETIMEBOUND {return asDict();} + operator FLDict FL_NULLABLE () const LIFETIMEBOUND {return asDict();} - Value operator[] (int index) const {return asArray().get(index);} - Value operator[] (slice key) const {return asDict().get(key);} - Value operator[] (const char *key) const {return asDict().get(key);} - Value operator[] (const KeyPath &kp) const {return root().operator[](kp);} + Value operator[] (int index) const LIFETIMEBOUND {return asArray().get(index);} + Value operator[] (slice key) const LIFETIMEBOUND {return asDict().get(key);} + Value operator[] (const char *key) const LIFETIMEBOUND {return asDict().get(key);} + Value operator[] (const KeyPath &kp) const LIFETIMEBOUND {return root().operator[](kp);} bool operator== (const Doc &d) const {return _doc == d._doc;} @@ -415,7 +412,7 @@ namespace fleece { explicit Encoder(FLSharedKeys FL_NULLABLE sk) :Encoder() {setSharedKeys(sk);} explicit Encoder(FLEncoder enc) :_enc(enc) { } - Encoder(Encoder&& enc) :_enc(enc._enc) {enc._enc = nullptr;} + Encoder(Encoder&& enc) noexcept :_enc(enc._enc) {enc._enc = nullptr;} void detach() {_enc = nullptr;} @@ -434,7 +431,7 @@ namespace fleece { inline bool writeDouble(double); inline bool writeString(slice); inline bool writeString(const char *s) {return writeString(slice(s));} - inline bool writeString(std::string s) {return writeString(slice(s));} + inline bool writeString(const std::string& s) {return writeString(slice(s));} inline bool writeDateString(FLTimestamp, bool asUTC =true); inline bool writeData(slice); inline bool writeValue(Value); @@ -537,6 +534,10 @@ namespace fleece { //====== IMPLEMENTATION GUNK: + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); + inline FLValueType Value::type() const {return FLValue_GetType(_val);} inline bool Value::isInteger() const {return FLValue_IsInteger(_val);} inline bool Value::isUnsigned() const {return FLValue_IsUnsigned(_val);} diff --git a/API/fleece/PlatformCompat.hh b/API/fleece/PlatformCompat.hh index 22f5c1d3..1a488beb 100644 --- a/API/fleece/PlatformCompat.hh +++ b/API/fleece/PlatformCompat.hh @@ -12,15 +12,12 @@ #pragma once #include "fleece/CompilerSupport.h" -#ifdef __APPLE__ - #include - #include "TargetConditionals.h" -#endif #ifdef _MSC_VER #define NOINLINE __declspec(noinline) #define ALWAYS_INLINE inline #define ASSUME(cond) __assume(cond) + #define __typeof decltype #define __func__ __FUNCTION__ diff --git a/API/fleece/RefCounted.hh b/API/fleece/RefCounted.hh index 821d3f91..a7108e02 100644 --- a/API/fleece/RefCounted.hh +++ b/API/fleece/RefCounted.hh @@ -12,7 +12,6 @@ #pragma once #include "fleece/PlatformCompat.hh" -#include #include #include @@ -108,9 +107,9 @@ namespace fleece { ~Retained() {release(_ref);} - operator T* () const & noexcept FLPURE STEPOVER {return _ref;} - T* operator-> () const noexcept FLPURE STEPOVER {return _ref;} - T* get() const noexcept FLPURE STEPOVER {return _ref;} + operator T* () const & noexcept LIFETIMEBOUND FLPURE STEPOVER {return _ref;} + T* operator-> () const noexcept LIFETIMEBOUND FLPURE STEPOVER {return _ref;} + T* get() const noexcept LIFETIMEBOUND FLPURE STEPOVER {return _ref;} explicit operator bool () const FLPURE {return (_ref != nullptr);} @@ -150,6 +149,11 @@ namespace fleece { [[nodiscard]] T* detach() && noexcept {auto r = _ref; _ref = nullptr; return r;} + /// Equivalent to `get` but without the `LIFETIMEBOUND` attribute. For use in rare cases where you have + /// a `Retained` and need to return it as a `T*`, which is normally illegal, but you know that there's + /// another `Retained` value keeping the object alive even after this function returns. + T* unsafe_get() const noexcept {return _ref;} + // The operator below is often a dangerous mistake, so it's deliberately made impossible. // It happens in these sorts of contexts, where it can produce a dangling pointer to a // deleted object: @@ -198,9 +202,9 @@ namespace fleece { RetainedConst(Retained &&r) noexcept :_ref(std::move(r).detach()) { } ALWAYS_INLINE ~RetainedConst() {release(_ref);} - operator const T* () const & noexcept FLPURE STEPOVER {return _ref;} - const T* operator-> () const noexcept FLPURE STEPOVER {return _ref;} - const T* get() const noexcept FLPURE STEPOVER {return _ref;} + operator const T* () const & noexcept LIFETIMEBOUND FLPURE STEPOVER {return _ref;} + const T* operator-> () const noexcept LIFETIMEBOUND FLPURE STEPOVER {return _ref;} + const T* get() const noexcept LIFETIMEBOUND FLPURE STEPOVER {return _ref;} RetainedConst& operator=(const T *t) & noexcept { auto oldRef = _ref; diff --git a/API/fleece/slice.hh b/API/fleece/slice.hh index ad0efbe0..4d83be41 100644 --- a/API/fleece/slice.hh +++ b/API/fleece/slice.hh @@ -16,14 +16,18 @@ #include "FLSlice.h" #include // for std::min() -#include -#include -#include // for fputs() -#include // for memcpy(), memcmp() +#include +#include +#include // for memcpy(), memcmp() #include +#include + +#ifndef __cpp_exceptions +#include // for fputs() +#endif #ifndef assert -# include +# include #endif # ifndef assert_precondition # define assert_precondition(e) assert(e) @@ -39,14 +43,6 @@ # endif #endif -// Figure out whether and how string_view is available -#ifdef __has_include -# if __has_include() -# include -# define SLICE_SUPPORTS_STRING_VIEW -# endif -#endif - // Utility for using slice with printf-style formatting. // Use "%.*" in the format string; then for the corresponding argument put FMTSLICE(theslice). @@ -62,10 +58,6 @@ namespace fleece { struct alloc_slice; struct nullslice_t; -#ifdef SLICE_SUPPORTS_STRING_VIEW - using string_view = std::string_view; // create typedef with correct namespace -#endif - #ifdef __APPLE__ using CFStringRef = const struct ::__CFString *; using CFDataRef = const struct ::__CFData *; @@ -73,13 +65,13 @@ namespace fleece { /** Adds a byte offset to a pointer. */ template - FLCONST constexpr14 inline const T* FL_NONNULL offsetby(const T * FL_NONNULL t, ptrdiff_t offset) noexcept { + FLCONST constexpr inline const T* FL_NONNULL offsetby(const T * FL_NONNULL t, ptrdiff_t offset) noexcept { return (const T*)((uint8_t*)t + offset); } /** Adds a byte offset to a pointer. */ template - FLCONST constexpr14 inline T* FL_NONNULL offsetby(T * FL_NONNULL t, ptrdiff_t offset) noexcept { + FLCONST constexpr inline T* FL_NONNULL offsetby(T * FL_NONNULL t, ptrdiff_t offset) noexcept { return (T*)((uint8_t*)t + offset); } @@ -110,13 +102,14 @@ namespace fleece { ownership: it manages a ref-counted heap-allocated buffer. * Instances are immutable: `buf` and `size` cannot be changed. The `slice` subclass changes this. - * The memory pointed to cannot be modified through this class. `slice` has some - methods that allow writes. */ + * The memory pointed to cannot be modified through this class. */ struct pure_slice { - const void* FL_NULLABLE const buf; - size_t const size; + const void* FL_NULLABLE const buf = nullptr; + size_t const size = 0; + + constexpr const void* data() const noexcept FLPURE {return buf;} //like std::span + constexpr size_t size_bytes() const noexcept FLPURE {return size;} //like std::span - pure_slice(const pure_slice &) noexcept = default; /// True if the slice's length is zero. bool empty() const noexcept FLPURE {return size == 0;} @@ -178,11 +171,9 @@ namespace fleece { /// Copies my contents to memory starting at `dst`, using `memcpy`. void copyTo(void *dst) const noexcept {FLMemCpy(dst, buf, size);} - /// Returns new malloc'ed slice containing same data. Call free() on it when done. - inline slice copy() const; - // String conversions: + operator std::string_view() const noexcept STEPOVER {return {(const char*)buf, size};} explicit operator std::string() const {return std::string((const char*)buf, size);} std::string asString() const {return (std::string)*this;} @@ -192,15 +183,8 @@ namespace fleece { will not overflow the buffer. Returns false if the slice was truncated. */ inline bool toCString(char *buf, size_t bufSize) const noexcept; - // FLSlice interoperability: constexpr operator FLSlice () const noexcept {return {buf, size};} -#ifdef SLICE_SUPPORTS_STRING_VIEW - // std::string_view interoperability: - constexpr pure_slice(string_view str) noexcept :pure_slice(str.data(), str.length()) {} - operator string_view() const noexcept STEPOVER {return string_view((const char*)buf, size);} -#endif - #ifdef __APPLE__ // Implementations in slice+CoreFoundation.cc and slice+ObjC.mm explicit pure_slice(CFDataRef FL_NULLABLE data) noexcept; @@ -218,17 +202,20 @@ namespace fleece { # endif #endif - constexpr pure_slice(std::nullptr_t) noexcept :pure_slice() {} + constexpr pure_slice(std::nullptr_t) noexcept :pure_slice() {} constexpr pure_slice(const char* FL_NULLABLE str) noexcept :buf(str), size(_strlen(str)) {} - pure_slice(const std::string& str) noexcept :buf(&str[0]), size(str.size()) {} + pure_slice(const std::string& str LIFETIMEBOUND) noexcept :buf(str.data()), size(str.size()) {} + constexpr pure_slice(std::string_view str) noexcept :pure_slice(str.data(), str.length()) {} // Raw memory allocation. These throw std::bad_alloc on failure. RETURNS_NONNULL inline static void* newBytes(size_t sz); template RETURNS_NONNULL static inline T* FL_NONNULL reallocBytes(T* FL_NULLABLE bytes, size_t newSz); + // Returns new malloc'ed slice containing same data. Call free() on it when done. + inline slice copy() const; protected: - constexpr pure_slice() noexcept :buf(nullptr), size(0) {} + constexpr pure_slice() noexcept = default; inline constexpr pure_slice(const void* FL_NULLABLE b, size_t s) noexcept; inline void setBuf(const void *b) noexcept; @@ -259,19 +246,21 @@ namespace fleece { * `buf` may be NULL, but only if `size` is zero; this is called `nullslice`. * `size` may be zero with a non-NULL `buf`; that's called an "empty slice". */ struct slice : public pure_slice { - constexpr slice() noexcept STEPOVER :pure_slice() {} + constexpr slice() noexcept STEPOVER = default; constexpr slice(std::nullptr_t) noexcept STEPOVER :pure_slice() {} inline constexpr slice(nullslice_t) noexcept STEPOVER; - constexpr slice(const void* FL_NULLABLE b, size_t s) noexcept STEPOVER :pure_slice(b, s) {} - inline constexpr slice(const void* start, const void* end) noexcept STEPOVER; - inline constexpr slice(const alloc_slice&) noexcept STEPOVER; + constexpr slice(const void* FL_NULLABLE b LIFETIMEBOUND, size_t s) noexcept STEPOVER :pure_slice(b, s) {} + inline constexpr slice(const void* start LIFETIMEBOUND, const void* end) noexcept STEPOVER; + inline slice(const alloc_slice& LIFETIMEBOUND) noexcept STEPOVER; - slice(const std::string& str) noexcept STEPOVER :pure_slice(str) {} - constexpr slice(const char* FL_NULLABLE str) noexcept STEPOVER :pure_slice(str) {} + constexpr slice(std::string_view str) noexcept STEPOVER :pure_slice(str) {} + slice(const std::string& str LIFETIMEBOUND) noexcept STEPOVER :pure_slice(str) {} + constexpr slice(const char* FL_NULLABLE str LIFETIMEBOUND) noexcept STEPOVER :pure_slice(str) {} slice& operator= (const char* s) & noexcept {return *this = slice(s);} - slice& operator= (const std::string &s) & noexcept {return *this = slice(s);} - slice& operator= (const alloc_slice &s) & noexcept {return *this = slice(s);} + slice& operator= (alloc_slice&&) =delete; // Disallowed: might lead to ptr to freed buf + slice& operator= (std::string&&) =delete; // Disallowed: might lead to ptr to freed buf + slice& operator= (const alloc_slice &s LIFETIMEBOUND) & noexcept {return *this = slice(s);} slice& operator= (std::nullptr_t) & noexcept {set(nullptr, 0); return *this;} inline slice& operator= (nullslice_t) & noexcept; @@ -290,33 +279,26 @@ namespace fleece { bool checkedMoveStart(size_t delta) noexcept {if (size= 201400L || _MSVC_LANG >= 201400L inline constexpr size_t pure_slice::_strlen(const char* FL_NULLABLE str) noexcept { if (!str) return 0; @@ -499,15 +464,6 @@ namespace fleece { while (*c) ++c; return c - str; } -#else - // (In C++11, constexpr functions couldn't contain loops; use (tail-)recursion instead) - inline constexpr size_t pure_slice::_strlen(const char* FL_NULLABLE str) noexcept { - return str ? _strlen(str, 0) : 0; - } - inline constexpr size_t pure_slice::_strlen(const char *str, size_t n) noexcept { - return *str ? _strlen(str + 1, n + 1) : n; - } -#endif inline constexpr pure_slice::pure_slice(const void* FL_NULLABLE b, size_t s) noexcept @@ -764,7 +720,7 @@ namespace fleece { inline constexpr slice::slice(nullslice_t) noexcept :pure_slice() {} - inline constexpr slice::slice(const alloc_slice &s) noexcept :pure_slice(s) { } + inline slice::slice(const alloc_slice &s LIFETIMEBOUND) noexcept :pure_slice(s) { } inline constexpr slice::slice(const void* start, const void* end) noexcept diff --git a/Fleece/Integration/MDict.hh b/Fleece/Integration/MDict.hh index af5d0a26..10446efd 100644 --- a/Fleece/Integration/MDict.hh +++ b/Fleece/Integration/MDict.hh @@ -26,7 +26,7 @@ namespace fleece { public: using MValue = MValue; using MCollection = MCollection; - using MapType = std::unordered_map; + using MapType = std::unordered_map; /** Constructs an empty MDict not connected to any existing Fleece Dict. */ MDict() :MCollection() { } diff --git a/Fleece/Mutable/HeapArray.cc b/Fleece/Mutable/HeapArray.cc index 5ee044f2..c10c4d4c 100644 --- a/Fleece/Mutable/HeapArray.cc +++ b/Fleece/Mutable/HeapArray.cc @@ -93,14 +93,16 @@ namespace fleece { namespace impl { namespace internal { HeapCollection* HeapArray::getMutable(uint32_t index, tags ifType) { if (index >= count()) return nullptr; - Retained result = nullptr; + HeapCollection* result = nullptr; auto &mval = _items[index]; if (mval) { result = mval.makeMutable(ifType); } else if (_source) { - result = HeapCollection::mutableCopy(_source->get(index), ifType); - if (result) - _items[index].set(result->asValue()); + Retained copied = HeapCollection::mutableCopy(_source->get(index), ifType); + if (copied) { + _items[index].set(copied->asValue()); + result = copied; + } } if (result) setChanged(true); diff --git a/Fleece/Mutable/HeapDict.cc b/Fleece/Mutable/HeapDict.cc index ea99fb58..d9bfa7d3 100644 --- a/Fleece/Mutable/HeapDict.cc +++ b/Fleece/Mutable/HeapDict.cc @@ -166,14 +166,16 @@ namespace fleece { namespace impl { namespace internal { HeapCollection* HeapDict::getMutable(slice stringKey, tags ifType) { key_t key = encodeKey(stringKey); - Retained result; + HeapCollection* result = nullptr; ValueSlot* mval = _findValueFor(key); if (mval) { result = mval->makeMutable(ifType); } else if (_source) { - result = HeapCollection::mutableCopy(_source->get(key), ifType); - if (result) - _map.emplace(_allocateKey(key), result.get()); + Retained copied = HeapCollection::mutableCopy(_source->get(key), ifType); + if (copied) { + _map.emplace(_allocateKey(key), copied.get()); // Retains `copied`, making it safe to return `result` + result = copied; + } } if (result) markChanged(); diff --git a/Fleece/Mutable/ValueSlot.cc b/Fleece/Mutable/ValueSlot.cc index eab05edc..8b54b646 100644 --- a/Fleece/Mutable/ValueSlot.cc +++ b/Fleece/Mutable/ValueSlot.cc @@ -235,8 +235,8 @@ namespace fleece { namespace impl { return nullptr; Retained mval = HeapCollection::mutableCopy(pointer(), ifType); if (mval) - set(mval->asValue()); - return mval; + set(mval->asValue()); // this retains mval, making it safe to return it as a pointer + return mval.unsafe_get(); } diff --git a/Fleece/Support/Base64.hh b/Fleece/Support/Base64.hh index 106b7fb7..07be9e3d 100644 --- a/Fleece/Support/Base64.hh +++ b/Fleece/Support/Base64.hh @@ -25,6 +25,6 @@ namespace fleece { namespace base64 { /** Decodes Base64 data from receiver into output. On success returns subrange of output where the decoded data is. If output is too small to hold all the decoded data, returns a null slice. */ - slice decode(slice input, void *outputBuffer, size_t bufferSize) noexcept; + slice decode(slice input, void *outputBuffer LIFETIMEBOUND, size_t bufferSize) noexcept; } } diff --git a/Fleece/Support/ConcurrentArena.hh b/Fleece/Support/ConcurrentArena.hh index 43262956..24bb9559 100644 --- a/Fleece/Support/ConcurrentArena.hh +++ b/Fleece/Support/ConcurrentArena.hh @@ -43,11 +43,11 @@ namespace fleece { /** Allocates a new block of the given size. @return The new block, or nullptr if there's no space. */ - void* alloc(size_t size); + void* alloc(size_t size) LIFETIMEBOUND; /** Allocates and zeroes a new block of the given size. @return The new block, or nullptr if there's no space. */ - void* calloc(size_t size); + void* calloc(size_t size) LIFETIMEBOUND; /** _Attempts_ to free the given block. This only works if it's the latest allocated block. @param allocatedBlock A block allocated by `alloc` or `calloc`. @@ -68,7 +68,7 @@ namespace fleece { } /** Converts a heap offset back into a pointer. */ - void* toPointer(size_t off) const FLPURE { + void* toPointer(size_t off) const LIFETIMEBOUND FLPURE { void *ptr = _heap.get() + off; assert(ptr < _heapEnd); return ptr; @@ -89,7 +89,7 @@ namespace fleece { ConcurrentArenaAllocator(ConcurrentArena &arena) :_arena(arena) { } - [[nodiscard]] T* allocate(size_t n) { + [[nodiscard]] T* allocate(size_t n) LIFETIMEBOUND { if (Zeroing) return (T*) _arena.calloc(n * sizeof(T)); else diff --git a/Fleece/Support/ParseDate.hh b/Fleece/Support/ParseDate.hh index 4bec1953..1484ea41 100644 --- a/Fleece/Support/ParseDate.hh +++ b/Fleece/Support/ParseDate.hh @@ -90,7 +90,7 @@ namespace fleece { @param format The model to use for formatting (i.e. which portions to include). If null, then the full ISO-8601 format is used @return The formatted string (points to `buf`). */ - slice FormatISO8601Date(char buf[], int64_t timestamp, bool asUTC, const DateTime* format); + slice FormatISO8601Date(char buf[] LIFETIMEBOUND, int64_t timestamp, bool asUTC, const DateTime* format); /** Formats a timestamp (milliseconds since 1/1/1970) as an ISO-8601 date-time. @param buf The location to write the formatted C string. At least @@ -100,7 +100,7 @@ namespace fleece { @param format The model to use for formatting (i.e. which portions to include). If null, then the full ISO-8601 format is used @return The formatted string (points to `buf`). */ - slice FormatISO8601Date(char buf[], int64_t timestamp, std::chrono::minutes tzoffset, const DateTime* format); + slice FormatISO8601Date(char buf[] LIFETIMEBOUND, int64_t timestamp, std::chrono::minutes tzoffset, const DateTime* format); /** Creates a tm out of a timestamp, but it will not be fully valid until passed through mktime. diff --git a/Fleece/Support/SmallVector.hh b/Fleece/Support/SmallVector.hh index a47b9f79..5a36b16f 100644 --- a/Fleece/Support/SmallVector.hh +++ b/Fleece/Support/SmallVector.hh @@ -105,36 +105,36 @@ namespace fleece { void clear() {shrinkTo(0);} void reserve(size_t cap) {if (cap>_capacity) setCapacity(cap);} - const T& get(size_t i) const FLPURE { + const T& get(size_t i) const LIFETIMEBOUND FLPURE { assert_precondition(i < _size); return _get(i); } - T& get(size_t i) FLPURE { + T& get(size_t i) LIFETIMEBOUND FLPURE { assert_precondition(i < _size); return _get(i); } - const T& operator[] (size_t i) const FLPURE {return get(i);} - T& operator[] (size_t i) FLPURE {return get(i);} - const T& back() const FLPURE {return get(_size - 1);} - T& back() FLPURE {return get(_size - 1);} + const T& operator[] (size_t i) const LIFETIMEBOUND FLPURE {return get(i);} + T& operator[] (size_t i) LIFETIMEBOUND FLPURE {return get(i);} + const T& back() const LIFETIMEBOUND FLPURE {return get(_size - 1);} + T& back() LIFETIMEBOUND FLPURE {return get(_size - 1);} using iterator = T*; using const_iterator = const T*; - iterator begin() FLPURE {return &_get(0);} - iterator end() FLPURE {return &_get(_size);} - const_iterator begin() const FLPURE {return &_get(0);} - const_iterator end() const FLPURE {return &_get(_size);} + iterator begin() LIFETIMEBOUND FLPURE {return &_get(0);} + iterator end() LIFETIMEBOUND FLPURE {return &_get(_size);} + const_iterator begin() const LIFETIMEBOUND FLPURE {return &_get(0);} + const_iterator end() const LIFETIMEBOUND FLPURE {return &_get(_size);} - T& push_back(const T& t) {return * new(grow()) T(t);} - T& push_back(T&& t) {return * new(grow()) T(std::move(t));} + T& push_back(const T& t) LIFETIMEBOUND {return * new(grow()) T(t);} + T& push_back(T&& t) LIFETIMEBOUND {return * new(grow()) T(std::move(t));} - void pop_back() {get(_size - 1).~T(); --_size;} + void pop_back() {get(_size - 1).~T(); --_size;} template - T& emplace_back(Args&&... args) { + T& emplace_back(Args&&... args) LIFETIMEBOUND { return * new(grow()) T(std::forward(args)...); } @@ -154,11 +154,11 @@ namespace fleece { *dst++ = *b++; } - iterator erase(iterator first) { + iterator erase(iterator first) LIFETIMEBOUND { return erase(first, first+1); } - iterator erase(iterator first, iterator last) { + iterator erase(iterator first, iterator last) LIFETIMEBOUND { assert_precondition(begin() <= first && first <= last && last <= end()); if (first == last) return first; @@ -208,7 +208,7 @@ namespace fleece { // Returns a pointer to the space where the new item would go; the caller needs to // put a valid value there or Bad Things will happen later. // (This method was added for the user of Encoder and is probably not too useful elsewhere.) - void* push_back_new() {return grow();} + void* push_back_new() LIFETIMEBOUND {return grow();} private: T& _get(size_t i) FLPURE {return ((T*)_begin())[i];} diff --git a/Fleece/Support/StringTable.cc b/Fleece/Support/StringTable.cc index 360c2960..acacafdb 100644 --- a/Fleece/Support/StringTable.cc +++ b/Fleece/Support/StringTable.cc @@ -82,7 +82,7 @@ namespace fleece { } - __hot const StringTable::entry_t* StringTable::find(key_t key, hash_t hash) const noexcept { + __hot const StringTable::entry_t* StringTable::find(key_t key, hash_t hash) const noexcept LIFETIMEBOUND { assert_precondition(key.buf != nullptr); assert_precondition(hash != hash_t::Empty); size_t end = wrap(size_t(hash) + _maxDistance + 1); diff --git a/Fleece/Support/StringTable.hh b/Fleece/Support/StringTable.hh index 99d8f724..5d30fe1b 100644 --- a/Fleece/Support/StringTable.hh +++ b/Fleece/Support/StringTable.hh @@ -44,8 +44,8 @@ namespace fleece { void clear() noexcept; /// Looks up an existing key, returning a pointer to its entry (or NULL.) - const entry_t* find(key_t key) const noexcept FLPURE {return find(key, hashCode(key));} - const entry_t* find(key_t key, hash_t) const noexcept FLPURE; + const entry_t* find(key_t key) const noexcept LIFETIMEBOUND FLPURE {return find(key, hashCode(key));} + const entry_t* find(key_t key, hash_t) const noexcept LIFETIMEBOUND FLPURE; using insertResult = std::pair; diff --git a/Fleece/Support/TempArray.hh b/Fleece/Support/TempArray.hh index 369c9334..bcfa30b5 100644 --- a/Fleece/Support/TempArray.hh +++ b/Fleece/Support/TempArray.hh @@ -48,8 +48,8 @@ delete[] _array; } - operator T* () {return _array;} - template explicit operator U* () {return (U*)_array;} + operator T* () LIFETIMEBOUND {return _array;} + template explicit operator U* () LIFETIMEBOUND {return (U*)_array;} bool const _onHeap; T* _array; diff --git a/Fleece/Support/Writer.hh b/Fleece/Support/Writer.hh index aaa11641..0feb43d0 100644 --- a/Fleece/Support/Writer.hh +++ b/Fleece/Support/Writer.hh @@ -38,9 +38,9 @@ namespace fleece { //-------- Writing: /// Writes data. Returns a pointer to where the data got written to. - const void* write(slice s) {return _write(s.buf, s.size);} + const void* write(slice s) LIFETIMEBOUND {return _write(s.buf, s.size);} - const void* write(const void* data NONNULL, size_t length) {return _write(data, length);} + const void* write(const void* data NONNULL, size_t length) LIFETIMEBOUND {return _write(data, length);} Writer& operator<< (uint8_t byte) {_write(&byte,1); return *this;} Writer& operator<< (slice s) {write(s); return *this;} @@ -60,16 +60,16 @@ namespace fleece { /// Reserves space for data, but leaves that space uninitialized. /// The data must be filled in later, before accessing the output, /// otherwise there will be garbage in the output. - void* reserveSpace(size_t length) {return _write(nullptr, length);} + void* reserveSpace(size_t length) LIFETIMEBOUND {return _write(nullptr, length);} /// Reserves space for \ref count values of type \ref T. template - T* reserveSpace(size_t count) {return (T*) reserveSpace(count * sizeof(T));} + T* reserveSpace(size_t count) LIFETIMEBOUND {return (T*) reserveSpace(count * sizeof(T));} /// Reserves `maxLength` bytes of space and passes a pointer to the callback. /// The callback must write _up to_ `maxLength` bytes, then return the byte count written. template - void* write(size_t maxLength, T callback) { + void* write(size_t maxLength, T callback) LIFETIMEBOUND { auto dst = (uint8_t*)reserveSpace(maxLength); size_t usedLength = callback(dst); _available.moveStart((ssize_t)usedLength - (ssize_t)maxLength); diff --git a/Fleece/Support/slice_stream.hh b/Fleece/Support/slice_stream.hh index a92d3f12..12b11fe6 100644 --- a/Fleece/Support/slice_stream.hh +++ b/Fleece/Support/slice_stream.hh @@ -135,7 +135,7 @@ namespace fleece { struct slice_istream : public slice { // slice_istream is constructed from a slice, or from the same parameters as a slice. constexpr slice_istream(const slice &s) noexcept :slice(s) { } - constexpr slice_istream(const alloc_slice &s) noexcept :slice(s) { } + slice_istream(const alloc_slice &s) noexcept :slice(s) { } constexpr slice_istream(const void* b, size_t s) noexcept STEPOVER :slice(b, s) {} constexpr slice_istream(const void* s NONNULL, const void* e NONNULL) noexcept STEPOVER :slice(s, e) { } diff --git a/Fleece/Support/varint.hh b/Fleece/Support/varint.hh index 15e10c3f..04dd1b83 100644 --- a/Fleece/Support/varint.hh +++ b/Fleece/Support/varint.hh @@ -79,7 +79,7 @@ static inline size_t GetUVarInt32(slice buf, uint32_t *n NONNULL) { /** Skips a pointer past a varint without decoding it. */ __hot -static inline const void* SkipVarInt(const void *buf NONNULL) { +static inline const void* SkipVarInt(const void *buf LIFETIMEBOUND NONNULL) { auto p = (const uint8_t*)buf; uint8_t byte; do { diff --git a/Tests/SupportTests.cc b/Tests/SupportTests.cc index 933b0a58..01e1e477 100644 --- a/Tests/SupportTests.cc +++ b/Tests/SupportTests.cc @@ -27,7 +27,28 @@ using namespace std; using namespace fleece; -// TESTS: +// Tests for the LIFETIMEBOUND attribute on slices. This code is commented out because it should fail to compile. +#if 0 +static slice bad(const char* str) { + alloc_slice strslice(str); + return strslice; // Clang should flag this as an error thanks to LIFETIMEBOUND +} + +static slice bad2(const char* str) { + string s(str); + return s; // Clang should flag this as an error thanks to LIFETIMEBOUND +} + + +TEST_CASE("Slice safety") { + // Both of these calls will result in use-after-free UB: + slice x = bad("long string will be heap allocated, right?"); + CHECK(string(x) == "foo"); + x = bad2("long string will be heap allocated, right?"); + CHECK(string(x) == "foo"); +} +#endif + #ifndef _MSC_VER diff --git a/Xcode/xcconfigs/XcodeWarnings.xcconfig b/Xcode/xcconfigs/XcodeWarnings.xcconfig index b7a3d707..3369d258 100644 --- a/Xcode/xcconfigs/XcodeWarnings.xcconfig +++ b/Xcode/xcconfigs/XcodeWarnings.xcconfig @@ -6,7 +6,7 @@ //BEGIN Added by Jens // Other Clang warnings that don't have Xcode names: -WARNING_CFLAGS = -Woverriding-method-mismatch -Weffc++ -Wcall-to-pure-virtual-from-ctor-dtor -Wmemset-transposed-args -Wreturn-std-move -Wsizeof-pointer-div -Wdefaulted-function-deleted -Wtautological-unsigned-zero-compare -Wtautological-unsigned-enum-zero-compare -Wmismatched-tags -Wformat -Wformat-nonliteral -Wformat-security +WARNING_CFLAGS = -Woverriding-method-mismatch -Weffc++ -Wcall-to-pure-virtual-from-ctor-dtor -Wmemset-transposed-args -Wreturn-std-move -Wsizeof-pointer-div -Wdefaulted-function-deleted -Wtautological-unsigned-zero-compare -Wtautological-unsigned-enum-zero-compare -Wmismatched-tags -Wformat -Wformat-nonliteral -Wformat-security -Wimplicit-fallthrough //END Added by Jens diff --git a/vendor/libb64/cdecode.c b/vendor/libb64/cdecode.c index bbbac564..ac51531e 100644 --- a/vendor/libb64/cdecode.c +++ b/vendor/libb64/cdecode.c @@ -7,6 +7,8 @@ For details, see http://sourceforge.net/projects/libb64 #include "cdecode.h" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" + char base64_decode_value(uint8_t value_in) { static const int8_t decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; diff --git a/vendor/libb64/cencode.c b/vendor/libb64/cencode.c index 5b488645..1bdbb18a 100644 --- a/vendor/libb64/cencode.c +++ b/vendor/libb64/cencode.c @@ -7,6 +7,8 @@ For details, see http://sourceforge.net/projects/libb64 #include "cencode.h" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" + const int DEFAULT_CHARS_PER_LINE = 72; void base64_init_encodestate(base64_encodestate* state_in)