Skip to content
This repository has been archived by the owner on Jan 24, 2025. It is now read-only.

Commit

Permalink
Adds checks for validating assert, improves assertion behaviour based…
Browse files Browse the repository at this point in the history
… on code size optimization, adds configuration for assertion macro behaviour
  • Loading branch information
Felix "xq" Queißner committed Jan 23, 2024
1 parent 0c228a7 commit 843d8bf
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 16 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ Right now, the following configurations exist:

- `foundation_libc_panic_handler`, which allows users to catch detectable undefined behaviour.

You can also configure the libc by chosing the build mode:

- `Debug`: Implements additional safety checks and adds breakpoints in panics.
- `ReleaseSafe`: Keeps the safety checks, but removes breakpoints.
- `ReleaseSmall`: Still keeps a certain amount of safety, but drops long internal strings to reduce code and ram size.
- `ReleaseFast`: Gotta go fast. Drops all safety and assumes all code behaves well.

There are also certain "usage" configurations that can be chosen to affect behaviour when *using* the headers. Those are implemented as C macros/defines:

- `FOUNDATION_LIBC_ASSERT` is a global macro that defines how `assert()` should behave:
- `FOUNDATION_LIBC_ASSERT_DEFAULT=0`: Behaves like a regular assert that can print file name, assertion message and line.
- `FOUNDATION_LIBC_ASSERT_NOFILE=1`: Drops the filename from the assertion to reduce code size.
- `FOUNDATION_LIBC_ASSERT_NOMSG=2`: Additionally drops the assertion message from the assertion to reduce code size.
- `FOUNDATION_LIBC_ASSERT_EXPECTED=3`: Replaces `assert(…)` with a construct that tells the compiler the assertion is always met. Makes code very fast. Assertions aren't checked.

## Development

Zig Version: 0.11
Expand Down Expand Up @@ -75,7 +90,7 @@ Which functions belong into which header can be figured out by taking a look at

| Header File | Header Status | Implementation Status | Description |
| --------------- | ------------- | --------------------- | ------------------------------------------------------------------------------------------------------- |
| `assert.h` | | | Conditionally compiled macro that compares its argument to zero |
| `assert.h` | | | Conditionally compiled macro that compares its argument to zero |
| `complex.h` || | (since C99) Complex number arithmetic |
| `ctype.h` ||| Functions to determine the type contained in character data |
| `errno.h` ||| Macros reporting error conditions |
Expand Down
26 changes: 26 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,32 @@ pub fn build(b: *std.Build) void {

validation_step.dependOn(&ext_compiler.step);
}

// Validate all modes of assertion:
for ([_][]const u8{
"FOUNDATION_LIBC_ASSERT_DEFAULT",
"FOUNDATION_LIBC_ASSERT_NOFILE",
"FOUNDATION_LIBC_ASSERT_NOMSG",
"FOUNDATION_LIBC_ASSERT_EXPECTED",
}) |assert_mode| {
// Check if the syntax of all of our header files is valid:
const assert_validator = b.addStaticLibrary(.{
.name = "assert-validator",
.target = .{}, // just build for the host
.optimize = .Debug,
});
assert_validator.addCSourceFile(.{
.file = .{ .path = "tests/assert-validator.c" },
.flags = &common_c_flags,
});
assert_validator.addIncludePath(include_path);
_ = assert_validator.getEmittedBin();

assert_validator.defineCMacro("FOUNDATION_LIBC_ASSERT", assert_mode);

// Just compile, do not install:
validation_step.dependOn(&assert_validator.step);
}
}
}

Expand Down
43 changes: 35 additions & 8 deletions include/assert.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
#ifndef _FOUNDATION_LIBC_ASSERT_H_
#define _FOUNDATION_LIBC_ASSERT_H_

#ifndef NDEBUG
#define assert(expr)
#else
extern void __assert(char const * assertion, char const * file, unsigned line) __attribute__((__noreturn__));
#include "foundation/builtins.h"

extern FOUNDATION_LIBC_NORETURN void foundation_libc_assert(char const * assertion, char const * file, unsigned line);

#if FOUNDATION_LIBC_ASSERT == FOUNDATION_LIBC_ASSERT_DEFAULT

#define assert(expr) \
do { \
if ((expr) == 0) { \
foundation_libc_assert(#expr, __FILE__, __LINE__); \
} \
} while (0)

#elif FOUNDATION_LIBC_ASSERT == FOUNDATION_LIBC_ASSERT_NOFILE

#define assert(expr) \
((expr) \
? void(0) \
: __assert(#expr, __FILE__, __LINE__))
#define assert(expr) \
do { \
if ((expr) == 0) { \
foundation_libc_assert(#expr, "", __LINE__); \
} \
} while (0)

#elif FOUNDATION_LIBC_ASSERT == FOUNDATION_LIBC_ASSERT_NOMSG

#define assert(expr) \
do { \
if ((expr) == 0) { \
foundation_libc_assert("", "", __LINE__); \
} \
} while (0)

#elif FOUNDATION_LIBC_ASSERT == FOUNDATION_LIBC_ASSERT_EXPECTED

#define assert(expr) FOUNDATION_LIBC_EXPECT(expr)

#else
#error "bad definition of FOUNDATION_LIBC_ASSERT_DEFAULT!"
#endif

#endif
37 changes: 37 additions & 0 deletions include/foundation/builtins.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#ifndef _FOUNDATION_LIBC_BUILTINS_H_
#define _FOUNDATION_LIBC_BUILTINS_H_

#include <stddef.h>

#define FOUNDATION_LIBC_ASSERT_DEFAULT 0
#define FOUNDATION_LIBC_ASSERT_NOFILE 1
#define FOUNDATION_LIBC_ASSERT_NOMSG 2
#define FOUNDATION_LIBC_ASSERT_EXPECTED 3

#ifndef FOUNDATION_LIBC_ASSERT
#define FOUNDATION_LIBC_ASSERT FOUNDATION_LIBC_ASSERT_DEFAULT
#endif

#if defined(__clang__)

#define FOUNDATION_LIBC_NORETURN __attribute__((__noreturn__))
#define FOUNDATION_LIBC_EXPECT(expr) (__builtin_expect(!(expr), 0))

#elif defined(__GNUC__) || defined(__GNUG__)

#define FOUNDATION_LIBC_NORETURN __attribute__((__noreturn__))
#define FOUNDATION_LIBC_EXPECT(expr) (__builtin_expect(!(expr), 0))

#elif defined(_MSC_VER)

#define FOUNDATION_LIBC_NORETURN __declspec(noreturn)
#define FOUNDATION_LIBC_EXPECT(expr) (__assume((expr)))

#else

#define FOUNDATION_LIBC_NORETURN
#define FOUNDATION_LIBC_EXPECT(expr)

#endif

#endif
20 changes: 13 additions & 7 deletions src/modules/assert.zig
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
const std = @import("std");
const builtin = @import("builtin");

export fn __assert(
export fn foundation_libc_assert(
assertion: ?[*:0]const u8,
file: ?[*:0]const u8,
line: c_uint,
) noreturn {
var buf: [256]u8 = undefined;
const str = std.fmt.bufPrint(&buf, "assertion failed: '{?s}' in file {?s} line {}", .{ assertion, file, line }) catch {
@panic("assertion failed");
};

@panic(str);
switch (builtin.mode) {
.Debug, .ReleaseSafe => {
var buf: [256]u8 = undefined;
const str = std.fmt.bufPrint(&buf, "assertion failed: '{?s}' in file {?s} line {}", .{ assertion, file, line }) catch {
@panic("assertion failed");
};
@panic(str);
},
.ReleaseSmall => @panic("assertion failed"),
.ReleaseFast => unreachable,
}
}
25 changes: 25 additions & 0 deletions tests/assert-validator.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <assert.h>

#ifndef FOUNDATION_LIBC_ASSERT
#error "FOUNDATION_LIBC_ASSERT wasn't implicitly or explicitly defined by assert.h"
#endif

#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
//

void assert_dynamic(int ok) {
assert(ok);
}

void assert_ok(void) {
assert(1);
}

// suppress noreturn diagnostic for missing "noreturn"
#pragma GCC diagnostic ignored "-Wmissing-noreturn"
//

void assert_bad(void) {
assert(0);
}
1 change: 1 addition & 0 deletions tests/syntactic-validation.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

// our own files must be included as non-system includes to trigger warnings:
#include "assert.h"
#include "ctype.h"
#include "errno.h"
#include "foundation/libc.h"
Expand Down

0 comments on commit 843d8bf

Please sign in to comment.