diff options
| author | Stefan Boberg <[email protected]> | 2023-11-21 13:42:53 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-11-21 13:42:53 +0100 |
| commit | 3369e345678aaa4199b76a099c750ed00754c548 (patch) | |
| tree | 6d202aeb7d312acddd6c9da58b8d1993a3adfffc /src | |
| parent | fix bad merge (diff) | |
| download | zen-3369e345678aaa4199b76a099c750ed00754c548.tar.xz zen-3369e345678aaa4199b76a099c750ed00754c548.zip | |
basic ZEN_ASSERT_FORMAT implementation (#556)
includes porting some compact binary builder code to use it since it had vestiges of the UE-side asserts
Diffstat (limited to 'src')
| -rw-r--r-- | src/zenbase/include/zenbase/zenbase.h | 6 | ||||
| -rw-r--r-- | src/zencore/compactbinary.cpp | 35 | ||||
| -rw-r--r-- | src/zencore/compactbinarybuilder.cpp | 75 | ||||
| -rw-r--r-- | src/zencore/include/zencore/assertfmt.h | 48 | ||||
| -rw-r--r-- | src/zencore/zencore.cpp | 27 |
5 files changed, 131 insertions, 60 deletions
diff --git a/src/zenbase/include/zenbase/zenbase.h b/src/zenbase/include/zenbase/zenbase.h index 1df375b28..401bcd088 100644 --- a/src/zenbase/include/zenbase/zenbase.h +++ b/src/zenbase/include/zenbase/zenbase.h @@ -197,9 +197,11 @@ char (&ZenArrayCountHelper(const T (&)[N]))[N + 1]; ////////////////////////////////////////////////////////////////////////// #if ZEN_COMPILER_MSC -# define ZEN_NOINLINE __declspec(noinline) +# define ZEN_NOINLINE __declspec(noinline) +# define ZEN_FORCEINLINE [[msvc::forceinline]] #else -# define ZEN_NOINLINE __attribute__((noinline)) +# define ZEN_NOINLINE __attribute__((noinline)) +# define ZEN_FORCEINLINE __attribute__((always_inline)) #endif #if ZEN_PLATFORM_WINDOWS diff --git a/src/zencore/compactbinary.cpp b/src/zencore/compactbinary.cpp index 5e8ce22ed..9152a8bfc 100644 --- a/src/zencore/compactbinary.cpp +++ b/src/zencore/compactbinary.cpp @@ -2,6 +2,7 @@ #include "zencore/compactbinary.h" +#include <zencore/assertfmt.h> #include <zencore/base64.h> #include <zencore/compactbinarybuilder.h> #include <zencore/compactbinaryvalidation.h> @@ -855,10 +856,10 @@ void CbFieldView::CopyTo(MutableMemoryView Buffer) const { const MemoryView Source = GetViewNoType(); - ZEN_ASSERT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize()); - // TEXT("A buffer of %" UINT64_FMT " bytes was provided when %" UINT64_FMT " bytes are required"), - // Buffer.GetSize(), - // sizeof(CbFieldType) + Source.GetSize()); + ZEN_ASSERT_FORMAT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize(), + "A buffer of {} bytes was provided when {} bytes are required", + Buffer.GetSize(), + sizeof(CbFieldType) + Source.GetSize()); *static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetSerializedType(Type); Buffer.RightChopInline(sizeof(CbFieldType)); memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize()); @@ -963,10 +964,10 @@ void CbArrayView::CopyTo(MutableMemoryView Buffer) const { const MemoryView Source = GetPayloadView(); - ZEN_ASSERT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize()); - // TEXT("Buffer is %" UINT64_FMT " bytes but %" UINT64_FMT " is required."), - // Buffer.GetSize(), - // sizeof(CbFieldType) + Source.GetSize()); + ZEN_ASSERT_FORMAT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize(), + "Buffer is {} bytes but {} is required.", + Buffer.GetSize(), + sizeof(CbFieldType) + Source.GetSize()); *static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType()); Buffer.RightChopInline(sizeof(CbFieldType)); @@ -1077,10 +1078,10 @@ void CbObjectView::CopyTo(MutableMemoryView Buffer) const { const MemoryView Source = GetPayloadView(); - ZEN_ASSERT(Buffer.GetSize() == (sizeof(CbFieldType) + Source.GetSize())); - // TEXT("Buffer is %" UINT64_FMT " bytes but %" UINT64_FMT " is required."), - // Buffer.GetSize(), - // sizeof(CbFieldType) + Source.GetSize()); + ZEN_ASSERT_FORMAT(Buffer.GetSize() == (sizeof(CbFieldType) + Source.GetSize()), + "Buffer is {} bytes but {} is required.", + Buffer.GetSize(), + sizeof(CbFieldType) + Source.GetSize()); *static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType()); Buffer.RightChopInline(sizeof(CbFieldType)); memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize()); @@ -1151,10 +1152,10 @@ TCbFieldIterator<FieldType>::CopyRangeTo(MutableMemoryView InBuffer) const MemoryView Source; if (TryGetSerializedRangeView(Source)) { - ZEN_ASSERT(InBuffer.GetSize() == Source.GetSize()); - // TEXT("Buffer is %" UINT64_FMT " bytes but %" UINT64_FMT " is required."), - // InBuffer.GetSize(), - // Source.GetSize()); + ZEN_ASSERT_FORMAT(InBuffer.GetSize() == Source.GetSize(), + "Buffer is {} bytes but {} is required.", + InBuffer.GetSize(), + Source.GetSize()); memcpy(InBuffer.GetData(), Source.GetData(), Source.GetSize()); } else @@ -1654,7 +1655,7 @@ public: break; } default: - ZEN_ASSERT(false); + ZEN_ASSERT_FORMAT(false, "invalid field type: {}", uint8_t(Accessor.GetType())); break; } diff --git a/src/zencore/compactbinarybuilder.cpp b/src/zencore/compactbinarybuilder.cpp index d4ccd434d..5c08d2e6e 100644 --- a/src/zencore/compactbinarybuilder.cpp +++ b/src/zencore/compactbinarybuilder.cpp @@ -2,6 +2,7 @@ #include "zencore/compactbinarybuilder.h" +#include <zencore/assertfmt.h> #include <zencore/compactbinarypackage.h> #include <zencore/compactbinaryvalidation.h> #include <zencore/endian.h> @@ -128,13 +129,10 @@ CbWriter::Save() CbFieldViewIterator CbWriter::Save(const MutableMemoryView Buffer) { - ZEN_ASSERT(States.size() == 1 && States.back().Flags == StateFlags::None); - // TEXT("It is invalid to save while there are incomplete write operations.")); - ZEN_ASSERT(Data.size() > 0); // TEXT("It is invalid to save when nothing has been written.")); - ZEN_ASSERT(Buffer.GetSize() == Data.size()); - // TEXT("Buffer is %" UINT64_FMT " bytes but %" INT64_FMT " is required."), - // Buffer.GetSize(), - // Data.Num()); + ZEN_ASSERT_FORMAT(States.size() == 1 && States.back().Flags == StateFlags::None, + "It is invalid to save while there are incomplete write operations."); + ZEN_ASSERT_FORMAT(Data.size() > 0, "It is invalid to save when nothing has been written."); + ZEN_ASSERT_FORMAT(Buffer.GetSize() == Data.size(), "Buffer is {} bytes but {} is required.", Buffer.GetSize(), Data.size()); memcpy(Buffer.GetData(), Data.data(), Data.size()); return CbFieldViewIterator::MakeRange(Buffer); } @@ -142,9 +140,9 @@ CbWriter::Save(const MutableMemoryView Buffer) void CbWriter::Save(BinaryWriter& Writer) { - ZEN_ASSERT(States.size() == 1 && States.back().Flags == StateFlags::None); - // TEXT("It is invalid to save while there are incomplete write operations.")); - ZEN_ASSERT(Data.size() > 0); // TEXT("It is invalid to save when nothing has been written.")); + ZEN_ASSERT_FORMAT(States.size() == 1 && States.back().Flags == StateFlags::None, + "It is invalid to save while there are incomplete write operations."); + ZEN_ASSERT_FORMAT(Data.size() > 0, "It is invalid to save when nothing has been written."); Writer.Write(Data.data(), Data.size()); } @@ -166,10 +164,9 @@ CbWriter::BeginField() } else { - ZEN_ASSERT((State.Flags & StateFlags::Name) == StateFlags::Name); - // TEXT("A new field cannot be written until the previous field '%.*hs' is finished."), - // GetActiveName().Len(), - // GetActiveName().GetData()); + ZEN_ASSERT_FORMAT((State.Flags & StateFlags::Name) == StateFlags::Name, + "A new field cannot be written until the previous field '{}' is finished.", + GetActiveName()); } } @@ -184,8 +181,8 @@ CbWriter::EndField(CbFieldType Type) } else { - ZEN_ASSERT((State.Flags & StateFlags::Object) == StateFlags::None); - // TEXT("It is invalid to write an object field without a unique non-empty name.")); + ZEN_ASSERT((State.Flags & StateFlags::Object) == StateFlags::None, + "It is invalid to write an object field without a unique non-empty name."); } if (State.Count == 0) @@ -207,21 +204,18 @@ CbWriter& CbWriter::SetName(const std::string_view Name) { WriterState& State = States.back(); - ZEN_ASSERT((State.Flags & StateFlags::Array) != StateFlags::Array); - // TEXT("It is invalid to write a name for an array field. Name '%.*hs'"), - // Name.Len(), - // Name.GetData()); - ZEN_ASSERT(!Name.empty()); - // TEXT("%s"), - //(State.Flags & EStateFlags::Object) == EStateFlags::Object - // ? TEXT("It is invalid to write an empty name for an object field. Specify a unique non-empty name.") - // : TEXT("It is invalid to write an empty name for a top-level field. Specify a name or avoid this call.")); - ZEN_ASSERT((State.Flags & (StateFlags::Name | StateFlags::Field)) == StateFlags::None); - // TEXT("A new field '%.*hs' cannot be written until the previous field '%.*hs' is finished."), - // Name.Len(), - // Name.GetData(), - // GetActiveName().Len(), - // GetActiveName().GetData()); + ZEN_ASSERT_FORMAT((State.Flags & StateFlags::Array) != StateFlags::Array, + "It is invalid to write a name for an array field. Name '{}'", + Name); + ZEN_ASSERT_FORMAT(!Name.empty(), + "{}", + (State.Flags & StateFlags::Object) == StateFlags::Object + ? "It is invalid to write an empty name for an object field. Specify a unique non-empty name." + : "It is invalid to write an empty name for a top-level field. Specify a name or avoid this call."); + ZEN_ASSERT_FORMAT((State.Flags & (StateFlags::Name | StateFlags::Field)) == StateFlags::None, + "A new field '{}' cannot be written until the previous field '{}' is finished.", + Name, + GetActiveName()); BeginField(); State.Flags |= StateFlags::Name; @@ -296,7 +290,7 @@ CbWriter::MakeFieldsUniform(const int64_t FieldBeginOffset, const int64_t FieldE void CbWriter::AddField(const CbFieldView& Value) { - ZEN_ASSERT(Value.HasValue()); // , TEXT("It is invalid to write a field with no value.")); + ZEN_ASSERT_FORMAT(Value.HasValue(), "It is invalid to write a field with no value."); BeginField(); EndField(AppendCompactBinary(Value, Data)); } @@ -318,11 +312,10 @@ CbWriter::BeginObject() void CbWriter::EndObject() { - ZEN_ASSERT(States.size() > 1 && (States.back().Flags & StateFlags::Object) == StateFlags::Object); - - // TEXT("It is invalid to end an object when an object is not at the top of the stack.")); - ZEN_ASSERT((States.back().Flags & StateFlags::Field) == StateFlags::None); - // TEXT("It is invalid to end an object until the previous field is finished.")); + ZEN_ASSERT_FORMAT(States.size() > 1 && (States.back().Flags & StateFlags::Object) == StateFlags::Object, + "It is invalid to end an object when an object is not at the top of the stack."); + ZEN_ASSERT_FORMAT((States.back().Flags & StateFlags::Field) == StateFlags::None, + "It is invalid to end an object until the previous field is finished."); const bool bUniform = IsUniformType(States.back().UniformType); const uint64_t Count = States.back().Count; @@ -378,10 +371,10 @@ CbWriter::BeginArray() void CbWriter::EndArray() { - ZEN_ASSERT(States.size() > 1 && (States.back().Flags & StateFlags::Array) == StateFlags::Array); - // TEXT("Invalid attempt to end an array when an array is not at the top of the stack.")); - ZEN_ASSERT((States.back().Flags & StateFlags::Field) == StateFlags::None); - // TEXT("It is invalid to end an array until the previous field is finished.")); + ZEN_ASSERT_FORMAT(States.size() > 1 && (States.back().Flags & StateFlags::Array) == StateFlags::Array, + "Invalid attempt to end an array when an array is not at the top of the stack."); + ZEN_ASSERT_FORMAT((States.back().Flags & StateFlags::Field) == StateFlags::None, + "It is invalid to end an array until the previous field is finished."); const bool bUniform = IsUniformType(States.back().UniformType); const uint64_t Count = States.back().Count; States.pop_back(); diff --git a/src/zencore/include/zencore/assertfmt.h b/src/zencore/include/zencore/assertfmt.h new file mode 100644 index 000000000..56383ffd9 --- /dev/null +++ b/src/zencore/include/zencore/assertfmt.h @@ -0,0 +1,48 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/zencore.h> + +#include <fmt/args.h> +#include <string_view> + +namespace zen { + +namespace assert { + template<typename... T> + auto AssertCaptureArguments(T&&... Args) + { + return fmt::make_format_args(Args...); + } + + void ExecAssertFmt + [[noreturn]] (const char* Filename, int LineNumber, const char* FunctionName, std::string_view Format, fmt::format_args Args); + + // MSVC (v19.00.24215.1 at time of writing) ignores no-inline attributes on + // lambdas. This can be worked around by calling the lambda from inside this + // templated (and correctly non-inlined) function. + template<typename RetType = void, class InnerType, typename... ArgTypes> + RetType ZEN_FORCENOINLINE ZEN_DEBUG_SECTION CallColdNoInline(InnerType&& Inner, ArgTypes const&... Args) + { + return Inner(Args...); + } + +} // namespace assert + +#define ZEN_ASSERT_FORMAT(x, fmt, ...) \ + do \ + { \ + using namespace std::literals; \ + if (x) [[likely]] \ + break; \ + zen::assert::CallColdNoInline([&]() ZEN_FORCEINLINE { \ + zen::assert::ExecAssertFmt(__FILE__, \ + __LINE__, \ + __FUNCTION__, \ + "assert(" #x ") failed: " fmt ""sv, \ + zen::assert::AssertCaptureArguments(__VA_ARGS__)); \ + }); \ + } while (false) + +} // namespace zen diff --git a/src/zencore/zencore.cpp b/src/zencore/zencore.cpp index 5406af097..eed903f54 100644 --- a/src/zencore/zencore.cpp +++ b/src/zencore/zencore.cpp @@ -10,6 +10,7 @@ # include <pthread.h> #endif +#include <zencore/assertfmt.h> #include <zencore/blake3.h> #include <zencore/compactbinary.h> #include <zencore/compactbinarybuilder.h> @@ -34,6 +35,22 @@ #include <zencore/uid.h> #include <zencore/workthreadpool.h> +#include <fmt/format.h> + +namespace zen::assert { + +void +ExecAssertFmt(const char* Filename, int LineNumber, const char* FunctionName, std::string_view Format, fmt::format_args Args) +{ + fmt::basic_memory_buffer<char, 1024> Message; + fmt::vformat_to(fmt::appender(Message), Format, Args); + Message.push_back('\0'); + + AssertImpl::ExecAssert(Filename, LineNumber, FunctionName, Message.data()); +} + +} // namespace zen::assert + namespace zen { void refcount_forcelink(); @@ -144,6 +161,8 @@ zencore_forcelinktests() namespace zen { +TEST_SUITE_BEGIN("core.assert"); + TEST_CASE("Assert.Default") { bool A = true; @@ -151,6 +170,13 @@ TEST_CASE("Assert.Default") CHECK_THROWS_WITH(ZEN_ASSERT(A == B), "A == B"); } +TEST_CASE("Assert.Format") +{ + bool A = true; + bool B = false; + CHECK_THROWS_WITH(ZEN_ASSERT_FORMAT(A == B, "{} == {}", A, B), "assert(A == B) failed: true == false"); +} + TEST_CASE("Assert.Custom") { struct MyAssertImpl : AssertImpl @@ -185,6 +211,7 @@ TEST_CASE("Assert.Custom") CHECK(strcmp(MyAssert.Message, "A == B") == 0); } +TEST_SUITE_END(); #endif } // namespace zen |