Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: CMake

on:
push:
branches: [ "master" ]
branches: [ "master", "dev" ]
pull_request:
branches: [ "master" ]
branches: [ "master", "dev" ]

jobs:
build:
Expand All @@ -20,6 +20,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Check Clang warning hygiene
if: runner.os != 'Windows'
run: python3 scripts/check-warning-hygiene.py --compiler clang++

- name: Configure CMake
# Configure CMake in a 'build' subdirectory.
run: bash ${{github.workspace}}/scripts/build-all.sh config ${{github.workspace}}
Expand Down
8 changes: 4 additions & 4 deletions include/zeroerr/fuzztest.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH

#define ZEROERR_CREATE_FUZZ_TEST_FUNC(function, name, ...) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH \
static void function(zeroerr::TestContext*); \
static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \
{name, __FILE__, __LINE__, function, {__VA_ARGS__}}, zeroerr::TestType::fuzz_test); \
ZEROERR_SUPPRESS_COMMON_WARNINGS_POP \
static void function(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT))

#define FUZZ_TEST_CASE(...) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH \
ZEROERR_CREATE_FUZZ_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), __VA_ARGS__) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_POP
ZEROERR_CREATE_FUZZ_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), __VA_ARGS__)

#define FUZZ_FUNC(func) zeroerr::FuzzFunction(func, _ZEROERR_TEST_CONTEXT)

Expand Down Expand Up @@ -234,4 +234,4 @@ std::vector<T> ReadCorpusFromDir(std::string dir);
} // namespace zeroerr


ZEROERR_SUPPRESS_COMMON_WARNINGS_POP
ZEROERR_SUPPRESS_COMMON_WARNINGS_POP
15 changes: 13 additions & 2 deletions include/zeroerr/internal/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,17 @@
// == COMPILER WARNINGS ============================================================================
// =================================================================================================

// Clang warning groups vary across upstream, Apple and Android releases.
// Test support instead of emitting an unknown diagnostic from the suppression itself.
#define ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED
#if ZEROERR_CLANG && !ZEROERR_ICC
#if __has_warning("-Wvariadic-macro-arguments-omitted")
#undef ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED
#define ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED \
ZEROERR_CLANG_SUPPRESS_WARNING("-Wvariadic-macro-arguments-omitted")
#endif
#endif

// both the header and the implementation suppress all of these,
// so it only makes sense to aggregate them like so
#define ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH \
Expand All @@ -233,7 +244,7 @@
ZEROERR_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \
ZEROERR_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \
ZEROERR_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \
ZEROERR_CLANG_SUPPRESS_WARNING("-Wvariadic-macro-arguments-omitted") \
ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED \
\
ZEROERR_GCC_SUPPRESS_WARNING_PUSH \
ZEROERR_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \
Expand Down Expand Up @@ -304,7 +315,7 @@

#define ZEROERR_SUPPRESS_VARIADIC_MACRO \
ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wgnu-zero-variadic-macro-arguments") \
ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wvariadic-macro-arguments-omitted")
ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED

#define ZEROERR_SUPPRESS_VARIADIC_MACRO_POP ZEROERR_CLANG_SUPPRESS_WARNING_POP

Expand Down
10 changes: 5 additions & 5 deletions include/zeroerr/log.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,11 @@ extern int _ZEROERR_G_VERBOSE;
#undef ZEROERR_G_CONTEXT_SCOPE
#endif

#define ZEROERR_G_CONTEXT_SCOPE(x) \
if (x) { \
for (auto* i : zeroerr::_ZEROERR_G_CONTEXT_SCOPE_VECTOR) { \
i->str(std::cerr); \
} \
#define ZEROERR_G_CONTEXT_SCOPE(x) \
if (x) { \
for (auto* zeroerr_context_scope : zeroerr::_ZEROERR_G_CONTEXT_SCOPE_VECTOR) { \
zeroerr_context_scope->str(std::cerr); \
} \
}

#ifdef ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER
Expand Down
7 changes: 3 additions & 4 deletions include/zeroerr/unittest.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH
zeroerr::SubCase(name, __FILE__, __LINE__, _ZEROERR_TEST_CONTEXT, {__VA_ARGS__}) \
<< [=](ZEROERR_UNUSED(zeroerr::TestContext * _ZEROERR_TEST_CONTEXT)) mutable

#define SUB_CASE(...) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH \
ZEROERR_EXPAND(ZEROERR_CREATE_SUB_CASE(__VA_ARGS__)) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_POP
// The caller supplies the lambda body, so no diagnostic pragma may follow
// the lambda declarator (GCC rejects it before the opening brace).
#define SUB_CASE(...) ZEROERR_EXPAND(ZEROERR_CREATE_SUB_CASE(__VA_ARGS__))

#define ZEROERR_CREATE_TEST_CLASS(fixture, classname, funcname, name, ...) \
class classname : public fixture { \
Expand Down
72 changes: 72 additions & 0 deletions scripts/check-warning-hygiene.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Compile warning regressions with --compiler clang++, g++, or cl (developer shell)."""
import argparse
from pathlib import Path
import subprocess
import tempfile


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--compiler", required=True)
args = parser.parse_args()
root = Path(__file__).resolve().parents[1]
msvc = Path(args.compiler).stem.lower() == "cl"
version = subprocess.run([args.compiler, "--version"], capture_output=True, text=True,
errors="replace")
clang = "clang" in version.stdout.lower()
with tempfile.TemporaryDirectory() as tmp:
source = Path(tmp) / "probe.cpp"
def compile_probe(code, flags, diagnostic=None):
source.write_text(code, encoding="utf-8")
if msvc:
command = [args.compiler, "/nologo", "/std:c++17", "/EHsc", "/utf-8", "/c",
f"/I{root / 'include'}", f"/I{root}", f"/Fo{tmp}/probe.obj"]
else:
command = [args.compiler, "-std=c++17", "-fsyntax-only",
f"-I{root / 'include'}", f"-I{root}"]
result = subprocess.run(command + flags + [str(source)], capture_output=True,
text=True, errors="replace")
output = result.stdout + result.stderr
if diagnostic is None:
assert result.returncode == 0, output
else:
assert result.returncode != 0 and diagnostic in output, output

# A consumer's loop variable must not collide with assertion/logging internals.
for header in ("zeroerr.h", "zeroerr.hpp"):
compile_probe(f'#include "{header}"\n'
+ ('' if msvc else '#pragma GCC diagnostic error "-Wshadow"\n') +
'void probe() { for (int i = 0; i < 2; ++i) { REQUIRE(i >= 0); } }\n',
["/W4", "/we4456", "/we4458"] if msvc else [])
# Pragmas cannot separate a function/lambda declarator from its body.
for header in ("zeroerr/fuzztest.h", "zeroerr.hpp"):
compile_probe(f'#include "{header}"\n'
'FUZZ_TEST_CASE("fuzz declaration") {}\n'
'TEST_CASE("subcase declaration") { SUB_CASE("child") {}; }\n', [])
implementation = '#define ZEROERR_IMPLEMENTATION\n#include "zeroerr.hpp"\n'
deprecated_flags = ["/we4996"] if msvc else ["-Werror=deprecated-declarations"]
compile_probe(implementation, deprecated_flags)
compile_probe(implementation +
'[[deprecated("user diagnostic")]] void old_api();\n'
'void consumer() { old_api(); }\n',
deprecated_flags, "4996" if msvc else "deprecated")
if clang:
prefix = '#include "zeroerr/internal/config.h"\n'
scoped = ('ZEROERR_SUPPRESS_VARIADIC_MACRO\n'
'#define ZEROERR_PROBE(x, ...) x\n'
'int value = ZEROERR_PROBE(1);\n'
'ZEROERR_SUPPRESS_VARIADIC_MACRO_POP\n')
flags = ["-Werror=unknown-warning-option", "-Werror=gnu-zero-variadic-macro-arguments"]
compile_probe(prefix + scoped, flags)
# Leaving the scope must restore the caller's diagnostic state.
compile_probe(prefix + scoped +
'#define USER_PROBE(x, ...) x\nint user = USER_PROBE(2);\n',
flags, "variadic")
compile_probe(prefix + scoped +
'#pragma clang diagnostic ignored "-Wzeroerr-nonexistent-warning"\n',
flags, "unknown warning group")
print("Warning hygiene probes passed" + (" (including Clang diagnostic restoration)" if clang else ""))


if __name__ == "__main__":
main()
6 changes: 6 additions & 0 deletions src/log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
#include <iomanip>
#include <unordered_set>

// The implementation uses the portable CRT API. Keep this diagnostic local;
// callers must retain their own deprecation warnings.
ZEROERR_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996)

#ifdef _WIN32
#include <windows.h>
#else
Expand Down Expand Up @@ -470,3 +474,5 @@ static std::string DefaultLogCallback(const LogMessage& msg, bool colorful) {
#undef zeroerr_color

} // namespace zeroerr

ZEROERR_MSVC_SUPPRESS_WARNING_POP
2 changes: 1 addition & 1 deletion src/unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,7 @@ class FailureDecorator : public Decorator {
enum FailureType { may_fail, should_fail };
FailureDecorator(FailureType type) : type(type) {}

bool onFinish(const TestCase& tc, TestContext& ctx) override {
bool onFinish(const TestCase&, TestContext& ctx) override {
if (type == FailureType::may_fail) {
// Treat failures as warnings so the suite can continue cleanly.
ctx.warning_as += ctx.failed_as;
Expand Down
55 changes: 37 additions & 18 deletions zeroerr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,17 @@
// == COMPILER WARNINGS ============================================================================
// =================================================================================================

// Clang warning groups vary across upstream, Apple and Android releases.
// Test support instead of emitting an unknown diagnostic from the suppression itself.
#define ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED
#if ZEROERR_CLANG && !ZEROERR_ICC
#if __has_warning("-Wvariadic-macro-arguments-omitted")
#undef ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED
#define ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED \
ZEROERR_CLANG_SUPPRESS_WARNING("-Wvariadic-macro-arguments-omitted")
#endif
#endif

// both the header and the implementation suppress all of these,
// so it only makes sense to aggregate them like so
#define ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH \
Expand All @@ -236,7 +247,7 @@
ZEROERR_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \
ZEROERR_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \
ZEROERR_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \
ZEROERR_CLANG_SUPPRESS_WARNING("-Wvariadic-macro-arguments-omitted") \
ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED \
\
ZEROERR_GCC_SUPPRESS_WARNING_PUSH \
ZEROERR_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \
Expand Down Expand Up @@ -307,7 +318,7 @@

#define ZEROERR_SUPPRESS_VARIADIC_MACRO \
ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wgnu-zero-variadic-macro-arguments") \
ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wvariadic-macro-arguments-omitted")
ZEROERR_CLANG_SUPPRESS_VARIADIC_OMITTED

#define ZEROERR_SUPPRESS_VARIADIC_MACRO_POP ZEROERR_CLANG_SUPPRESS_WARNING_POP

Expand Down Expand Up @@ -1441,9 +1452,11 @@ struct Printer {
#else
ZEROERR_ENABLE_IF(ZEROERR_IS_ENUM)
print(T value, unsigned level, const char* lb, rank<0>) {
// enum class has no operator<< and no implicit conversion, so fall back
// to its underlying numeric value instead of failing to compile.
os << tab(level) << static_cast<typename std::underlying_type<T>::type>(value) << lb;
if constexpr (detail::is_streamable<std::ostream, T>::value) {
os << tab(level) << value << lb;
} else {
os << tab(level) << "<unprintable enum " << type(value) << ">" << lb;
}
}
#endif

Expand Down Expand Up @@ -3623,11 +3636,11 @@ extern int _ZEROERR_G_VERBOSE;
#undef ZEROERR_G_CONTEXT_SCOPE
#endif

#define ZEROERR_G_CONTEXT_SCOPE(x) \
if (x) { \
for (auto* i : zeroerr::_ZEROERR_G_CONTEXT_SCOPE_VECTOR) { \
i->str(std::cerr); \
} \
#define ZEROERR_G_CONTEXT_SCOPE(x) \
if (x) { \
for (auto* zeroerr_context_scope : zeroerr::_ZEROERR_G_CONTEXT_SCOPE_VECTOR) { \
zeroerr_context_scope->str(std::cerr); \
} \
}

#ifdef ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER
Expand Down Expand Up @@ -4306,10 +4319,9 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH
zeroerr::SubCase(name, __FILE__, __LINE__, _ZEROERR_TEST_CONTEXT, {__VA_ARGS__}) \
<< [=](ZEROERR_UNUSED(zeroerr::TestContext * _ZEROERR_TEST_CONTEXT)) mutable

#define SUB_CASE(...) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH \
ZEROERR_EXPAND(ZEROERR_CREATE_SUB_CASE(__VA_ARGS__)) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_POP
// The caller supplies the lambda body, so no diagnostic pragma may follow
// the lambda declarator (GCC rejects it before the opening brace).
#define SUB_CASE(...) ZEROERR_EXPAND(ZEROERR_CREATE_SUB_CASE(__VA_ARGS__))

#define ZEROERR_CREATE_TEST_CLASS(fixture, classname, funcname, name, ...) \
class classname : public fixture { \
Expand Down Expand Up @@ -4689,15 +4701,15 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_POP
ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH

#define ZEROERR_CREATE_FUZZ_TEST_FUNC(function, name, ...) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH \
static void function(zeroerr::TestContext*); \
static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \
{name, __FILE__, __LINE__, function, {__VA_ARGS__}}, zeroerr::TestType::fuzz_test); \
ZEROERR_SUPPRESS_COMMON_WARNINGS_POP \
static void function(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT))

#define FUZZ_TEST_CASE(...) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH \
ZEROERR_CREATE_FUZZ_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), __VA_ARGS__) \
ZEROERR_SUPPRESS_COMMON_WARNINGS_POP
ZEROERR_CREATE_FUZZ_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), __VA_ARGS__)

#define FUZZ_FUNC(func) zeroerr::FuzzFunction(func, _ZEROERR_TEST_CONTEXT)

Expand Down Expand Up @@ -4903,6 +4915,7 @@ std::vector<T> ReadCorpusFromDir(std::string dir);


ZEROERR_SUPPRESS_COMMON_WARNINGS_POP

#ifdef ZEROERR_IMPLEMENTATION


Expand Down Expand Up @@ -5179,6 +5192,10 @@ TerminalSize getTerminalWidth() {
#include <iomanip>
#include <unordered_set>

// The implementation uses the portable CRT API. Keep this diagnostic local;
// callers must retain their own deprecation warnings.
ZEROERR_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996)

#ifdef _WIN32
#include <windows.h>
#else
Expand Down Expand Up @@ -5646,6 +5663,8 @@ static std::string DefaultLogCallback(const LogMessage& msg, bool colorful) {

} // namespace zeroerr

ZEROERR_MSVC_SUPPRESS_WARNING_POP




Expand Down Expand Up @@ -6935,7 +6954,7 @@ class FailureDecorator : public Decorator {
enum FailureType { may_fail, should_fail };
FailureDecorator(FailureType type) : type(type) {}

bool onFinish(const TestCase& tc, TestContext& ctx) override {
bool onFinish(const TestCase&, TestContext& ctx) override {
if (type == FailureType::may_fail) {
// Treat failures as warnings so the suite can continue cleanly.
ctx.warning_as += ctx.failed_as;
Expand Down
Loading