aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-11-21 13:42:53 +0100
committerGitHub <[email protected]>2023-11-21 13:42:53 +0100
commit3369e345678aaa4199b76a099c750ed00754c548 (patch)
tree6d202aeb7d312acddd6c9da58b8d1993a3adfffc /src
parentfix bad merge (diff)
downloadzen-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.h6
-rw-r--r--src/zencore/compactbinary.cpp35
-rw-r--r--src/zencore/compactbinarybuilder.cpp75
-rw-r--r--src/zencore/include/zencore/assertfmt.h48
-rw-r--r--src/zencore/zencore.cpp27
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