5.9 KiB
Assertions, warnings, and halts: shared API and January matching
The owner is source/cseries/cseries.h.
All assertions express a condition that must be true. A false condition
reports the diagnostic with display_assert(..., TRUE) and then calls
system_exit(-1). Evaluate diagnostic expressions only on the failure path.
Warnings likewise report only when their condition is false, but use
display_assert(..., FALSE) and continue. Halts are unconditional: they
report with TRUE and call the existing halt_and_catch_fire() owner.
They do not take an assertion condition.
| Intent | Current supported API | Diagnostic |
|---|---|---|
| Plain assertion | assert(expression) |
Stringified expression |
| Diagnostic assertion | dassert(expression, diagnostic) |
Supplied string expression |
| Formatted assertion | vassert(expression, csprintf(temporary, format, ...)) |
Formatted string, produced only on failure |
| Preserve original source location | match_assert(file, line, expression) |
Stringified expression |
| Preserve location and diagnostic | match_dassert(file, line, expression, diagnostic) |
Supplied string expression |
| Preserve location and formatted diagnostic | match_vassert(file, line, expression, csprintf(temporary, format, ...)) |
Formatted string |
| Plain warning | warn(expression) |
Stringified expression; nonfatal |
| Diagnostic warning | dwarn(expression, diagnostic) |
Supplied string; nonfatal |
| Formatted warning | vwarn(expression, csprintf(temporary, format, ...)) |
Failure-only formatting; nonfatal |
| Plain halt | halt() |
No message; unconditional fatal report |
| Diagnostic halt | dhalt(diagnostic) |
Supplied string; unconditional |
| Formatted halt | vhalt(csprintf(temporary, format, ...)) |
Formatted string; unconditional |
Warnings and halts also have match_warn, match_dwarn, match_vwarn,
match_halt, match_dhalt, and match_vhalt counterparts. Their first two
arguments are the original file and line, followed by the ordinary API's
arguments.
vassert/match_vassert already accepted a message expression before this
cleanup. The dassert/dwarn/dhalt names and matching counterparts are
statement-safe diagnostic convenience wrappers for the existing behavior,
not a claim that these exact wrappers were in Bungie's January source.
Existing callers remain compatible.
The pinned XDK 3911 compiler is VC7 13.00.9254.1. Its preprocessor rejects
#define vassert(expression, format, ...) with C2010, and does not implement
__VA_ARGS__. Variadic functions such as csprintf are supported. Do not
change compiler versions, add a function-call wrapper, or force inline code
merely to obtain the newer comma-separated macro syntax.
The supplied CE reference files themselves use two-argument
vassert(expression, message) calls: random_math.c's random-seed lock
checks, server line 1248, and client line 521. These calls corroborate the
older API, but the attachments do not include its defining cseries.h.
See the source reconstruction map.
Units example: assert the invariant, not the error condition
The invariant is that an inventory weapon being dropped is not connected to the map:
match_vassert(
"c:\\halo\\SOURCE\\units\\units.c",
8505,
!TEST_FLAG(weapon->object.flags, _object_connected_to_map_bit),
csprintf(
temporary,
"a %s tried to drop a %s which was connected to the map.",
tag_get_name(unit->definition_index),
tag_get_name(weapon->definition_index)));
Omitting ! reverses the behavior: it would assert for a disconnected weapon
and accept the connected weapon. Keep the two tag_get_name calls and the
formatting inside the assertion message expression, so successful execution
does not evaluate them.
The matching form retains January's original filename and line number. In a
NON_MATCHING build, MATCH_FILE/MATCH_LINE deliberately use the actual
current source location instead. For new code with no original location to
preserve, use the ordinary assert/dassert/vassert form.
Limits and adoption
- Use braces around surrounding
if/elsebranches. Some inherited macros expand to anifstatement; an unbraced outerelsecan bind incorrectly. - Keep expressions free of unnecessary side effects. The condition is evaluated once, and the diagnostic only on failure, but argument evaluation order inside a formatted call remains the C compiler's responsibility.
temporaryis the existing shared 256-byte buffer andcsprintfuses unboundedvsprintf. Preserve authenticated legacy call sites; this is not a thread-safe or size-safe general logging facility. Do not pass arbitrary unbounded input or preformat into this buffer on the success path.- Do not add
NDEBUG-based removal: the current project macros do not have that contract. Both debug andNDEBUGfixture builds are tested. - Migrate one coherent owner at a time and run its strict gate plus consumer regression checks. Do not bulk-rewrite active lanes or lose original diagnostic text, argument types, failure polarity, or source locations.
The executable macro tests are
tools/test_cseries_assertions.py, using
C fixtures compiled by the actual VC7
toolchain. They check success/failure, lazy formatting, single evaluation,
exact matching/nonmatching locations, fatal flag and system_exit(-1) or
halt_and_catch_fire() dispatch, nonfatal warning continuation, and
connected-weapon polarity. They use bounded diagnostic API doubles; they do
not validate the termination implementation, vsprintf buffer safety, or the
game's complete error-reporting subsystem.
See also common constants and the Units cleanup ledger.