Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  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
  87.