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 |