Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3
 * It is copyright by its individual contributors, as recorded in the
4
 * project's Git history.  See COPYING.txt at the top level for license
5
 * terms and a link to the Git history.
6
 */
7
#pragma once
8
 
9
/* Define a utility macro `cf_assert` that, like `assert`, is meant to
10
 * check invariants.  Unlike `assert`, it has a small but configurable
11
 * effect on NDEBUG builds.
12
 *
13
 * This macro is mainly used to try to hint to gcc that it is
14
 * excessively cautious in determining whether a
15
 * -Wformat-truncation warning is appropriate.
16
 *
17
 * Unless proved otherwise by control flow analysis, gcc will assume
18
 * that a variable to be formatted could have any value that fits in the
19
 * underlying type, and then gcc will warn if any of those values does
20
 * not fit.  In most cases, program logic constrains the value to a
21
 * smaller range than the underlying type supports.  Correct placement
22
 * of this macro can inform the compiler that a variable's actual range
23
 * is less than the full supported range of the underlying type.  For
24
 * example, a player counter should never exceed MAX_PLAYERS, but
25
 * MAX_PLAYERS is far less than MAX_UCHAR.
26
 *
27
 * Unfortunately, in tested versions of gcc-8, the compiler's range
28
 * propagation pass is hampered by strange rules in the flow control
29
 * logic.  Consider:
30
 
31
        unsigned var = unconstrained_expression();
32
        // var can now be anything in [0, UINT_MAX]
33
        cf_assert(var <= 8);
34
        // If execution gets here, `var <= 8` is true.
35
        snprintf(..., var);
36
 
37
 * Suppose cf_assert(X) is defined as `((X) || (assert(X),
38
 * __builtin_unreachable()))`, so the above becomes:
39
 
40
        unsigned var = unconstrained_expression();
41
        if (var <= 8) {
42
        }
43
        else {
44
                assert_fail(...);
45
                __builtin_unreachable();
46
        }
47
        snprintf(..., var);
48
 
49
 * In testing, gcc deleted __builtin_unreachable (probably because
50
 * assert_fail is noreturn), then warned because, without the
51
 * __builtin_unreachable, flow control decides the snprintf is reachable
52
 * (even though assert_fail is noreturn) on the else path and would
53
 * misbehave if reached.  Remove the call to `assert` so that you have
54
 * `else { __builtin_unreachable(); }` and gcc will retain the
55
 * `__builtin_unreachable` and not warn.
56
 *
57
 * For an even more bizarre result:
58
 
59
        if (var <= 8) {
60
                snprintf(..., var);
61
        } else {
62
                assert(var <= 8);       // always fails
63
                __builtin_unreachable();
64
        }
65
 
66
 * This block warns that `var` is out of range.  Comment out the
67
 * `assert`, which cannot influence whether `snprintf` is reached, and
68
 * the warning goes away.
69
 *
70
 * --
71
 *
72
 * Leave cf_assert set as an alias for (X || __builtin_unreachable())
73
 * unless you know what you are doing and are prepared for the warnings
74
 * that will arise.
75
 */
76
#include <cassert>
77
#if defined(DXX_CF_ASSERT_ASSERT)
78
#define cf_assert       assert
79
#else
80
#ifdef DXX_CF_ASSERT_TRAP
81
#define cf_assert_fail  __builtin_trap
82
#else
83
#define cf_assert_fail  __builtin_unreachable
84
#endif
85
#define cf_assert(X)    ((X) ? static_cast<void>(0) : (cf_assert_fail()))
86
#endif