Skip to content

Commit 6070731

Browse files
zstr: enforce single C-string spelling, drop char* / const char* arms
The codebase's `Zstr` typedef (`const char *`) is now the only spelling for C-strings -- bare `char *` and `const char *` are forbidden everywhere, including inside `_Generic` dispatch arms. The old exception that allowed `char *` / `const char *` arms (so string literals could match) is replaced by enabling `-Wwrite-strings` (gcc/clang/clang-cl) and `/Zc:strictStrings` (msvc/clang-cl) in `common_c_args`. Under those flags string literals carry type `const char *` (= `Zstr`), which lets `_Generic((literal), Zstr: ...)` match literals directly without a bare `char *` arm. Convention + header updates: * `CODING-CONVENTIONS.md` Naming + `_Generic` sections: forbid `char *` / `const char *` anywhere; rewrite the path-dispatch skeleton to `Str *` + `Zstr` arms only; add a "Compiler flags" section documenting the mandatory strict-string flags. * `Misra/Std/Zstr.h` header comment: drop the old exception clause; define `Cstr` as the `(Zstr, size)` naming-suffix (not a type). _Generic dispatch sweep: * Every existing string `_Generic` macro across `Misra/Parsers/*.h`, `Misra/Sys/*.h`, `Misra/Std/File.h`, etc. loses its `char *` arm, has its `const char *` arm renamed to `Zstr`, and uses `(Zstr)` casts inside arm bodies. The `((Str *)(p))->data` field reach is replaced with the public `StrBegin((Str *)(p))` accessor. * `const Str *` arms removed -- per the canonical "Path dispatch: `Str *`, `Zstr`" rule. Source-tree spelling cleanup: * Bulk-rename `const char *` -> `Zstr` and bare `char *` -> `Zstr` (where it's a string, not a byte buffer or kernel-boundary signature) across `Include/`, `Source/`, `Tests/`, `Bin/`. * Pre-existing byte-arithmetic `const char *` parameters (e.g. `insert_range_into_vec`'s `item_data`, `vec_const_ptr_at`'s return) converted to `const u8 *` per the existing "Raw byte buffers use `u8 *`" rule. Caller pointer-sign warnings fixed with `(const u8 *)` casts. * Cascading callsites where `path.data` (typed `char *`) was passed to macros that now dispatch on `Str *` / `Zstr` only -- callers pass `&path` instead so the `Str *` arm matches. Family refactors (per "Cstr / Zstr / unsuffixed-Str" canonical pattern): * `StrIndexOf`, `StrContains`, `StrStartsWith`, `StrEndsWith`, `StrReplace` (5 families in `Std/Container/Str/Ops.h`): the three public PascalCase functions per family are moved to snake_case backends in a new `Std/Container/Str/Private.h`. Replaced with a single PascalCase macro dispatching via `MISRA_OVERLOAD` (arg count for Cstr) + `_Generic` (Str * vs Zstr). * `StrInsert` / `StrMustInsert` (`Std/Container/Str/Insert.h`): unified into a single overloaded macro of the same shape. Compiler behaviour empirically verified on GCC and Clang. MSVC and clang-cl behaviour follows the documented `/Zc:strictStrings` flag; CI will confirm. Build clean (0 errors, 0 warnings); meson test 104/104 passing.
1 parent 2349e8d commit 6070731

81 files changed

Lines changed: 886 additions & 857 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Bin/Beam.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ static void log_request_summary(Allocator *alloc, Zstr client_addr, Zstr prefix_
252252
StrPushBackZstr(&raw, prefix_bytes);
253253

254254
HttpRequest req = HttpRequestInit(scope);
255-
Zstr end = HttpRequestParse(&req, StrBegin(&raw));
255+
Zstr end = HttpRequestParse(&req, (Zstr)StrBegin(&raw));
256256
if (end == StrBegin(&raw)) {
257257
LOG_INFO("[{}] (unparseable request, {} bytes)", client_addr, (u64)prefix_len);
258258
} else {

CODING-CONVENTIONS.md

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,18 @@ of the codebase to see them in action.
2626
- **C-strings everywhere use `Zstr`**, not `char *` / `const char *`.
2727
`Zstr` (`<Misra/Std/Zstr.h>`) is the project's name for a
2828
NUL-terminated C-string. This applies to internal helpers too — `Zstr`
29-
is the *only* C-string type in the codebase. The one exception is
30-
`_Generic` dispatch arms, where the underlying `char *` and
31-
`const char *` branches are still listed explicitly because string
32-
literals and `const`-returning callers each need their own match.
29+
is the *only* C-string type in the codebase, *including* in `_Generic`
30+
dispatch arms. There is no `char *` / `const char *` carve-out. The
31+
build sets `-Wwrite-strings` (gcc/clang/clang-cl) and
32+
`/Zc:strictStrings` (msvc/clang-cl) so string literals carry type
33+
`const char *` (= `Zstr`), which lets `_Generic((literal), Zstr: ...)`
34+
match literals directly without a bare-`char *` arm. See the
35+
*Compiler flags* section below.
36+
- **`Cstr` is a naming-suffix, not a type.** The `Cstr` form of an API
37+
takes `(Zstr, size)` — a non-NUL-terminated view of memory, or a
38+
NUL-terminated string truncated at an explicit length. See
39+
`StrStartsWith*` for the canonical Cstr / Zstr / unsuffixed-Str
40+
overload family.
3341
- **Raw byte buffers use `u8 *`**, not `char *`. When a pointer holds
3442
bytes you'll do byte-grain arithmetic on (allocator chunks, owned
3543
memory regions, packed-record cursors), the type is `u8 *`. `char *`
@@ -184,19 +192,25 @@ of the codebase to see them in action.
184192
`default:` arm that silently casts a wrong type to the function's
185193
expected one. Any other input type should trigger a compile-time
186194
`_Generic` mismatch.
187-
- Path dispatch: `Str *`, `char *`, `const char *` (note: not
188-
`const Str *` — see `<Misra/Std/File.h>`, `<Misra/Sys/Dir.h>`,
189-
`<Misra/Parsers/Elf.h>` for the canonical shape).
195+
- Path / string dispatch: `Str *` and `Zstr` only — no bare `char *`
196+
or `const char *` arms. The mandatory `-Wwrite-strings` /
197+
`/Zc:strictStrings` build flags make string literals match the
198+
`Zstr` arm directly.
190199
- Container-out dispatch: `Buf *`, `Str *`.
191200

192201
Skeleton:
193202
```c
194-
#define FileGetSize(path) \
195-
_Generic((path), \
196-
Str *: file_get_size(((Str *)(path))->data), \
197-
char *: file_get_size((const char *)(path)), \
198-
const char *: file_get_size((const char *)(path)))
203+
#define FileGetSize(path) \
204+
_Generic((path), \
205+
Str *: file_get_size(StrBegin((Str *)(path))), \
206+
Zstr: file_get_size((Zstr)(path)))
199207
```
208+
209+
For functions that already have an arg-count variant of the API
210+
(`*Cstr` form taking `(Zstr, size)`), combine the type dispatch
211+
above with `MISRA_OVERLOAD` for arg count — see the
212+
*StrStartsWith family* example next to the `Cstr` / `Zstr` /
213+
unsuffixed-Str description in the *API shape* section.
200214
- **Don't reach into a wrapped type's fields from outside its `.c`
201215
file.** Go through the public accessor macros: `BufLength` / `BufData`
202216
/ `BufAllocator` (`<Misra/Std/Container/Buf.h>`), `StrLen` / `StrBegin`
@@ -316,6 +330,30 @@ of the codebase to see them in action.
316330
allocations are managed *outside* the container. A comment in the
317331
fixture explains the intent.
318332
333+
## Compiler flags (mandatory)
334+
335+
These are baked into `meson.build`'s `common_c_args`; downstream consumers
336+
that bypass the meson build must enable equivalents themselves or the
337+
`_Generic` dispatch the convention relies on will silently mis-match.
338+
339+
- **`-Wwrite-strings`** (gcc, clang, clang-cl) and
340+
**`/Zc:strictStrings`** (msvc, clang-cl) — gives string literals the
341+
type `const char *` (= `Zstr`) instead of bare `char *`. Required so
342+
`_Generic` dispatch on string types can list `Zstr` as a match arm
343+
and still accept literal callers. Without these flags, `"foo"` has
344+
type `char *` and the `Zstr`-only dispatch misses.
345+
- **`-Wuninitialized` / `-Wmaybe-uninitialized` / `-Werror=` on both** —
346+
enforces the "initialise at declaration" convention. Listed for
347+
completeness; not new in this section.
348+
349+
Verified compiler behaviour at the time these flags were locked in:
350+
351+
- GCC and Clang: empirically tested. `_Generic((literal), Zstr: ...)`
352+
matches under `-Wwrite-strings` and does not match without it.
353+
- MSVC: docs-confirmed (`/Zc:strictStrings`) — Windows-CI smoke test
354+
is what locks this in for production. clang-cl honours both `-W`
355+
and `/Zc` spellings; we pass both in the MSVC branch.
356+
319357
## Commits and pre-commit
320358
321359
- **Tests must pass** before every commit.

Include/Misra/Parsers/Dns.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ extern "C" {
119119
bool dns_build_query_zstr(DnsWireBuf *out, u16 id, Zstr name, DnsType type);
120120
bool dns_build_query_str(DnsWireBuf *out, u16 id, const Str *name, DnsType type);
121121
#define DnsBuildQuery(out, id, name, type) \
122-
_Generic((name), Str *: dns_build_query_str, const Str *: dns_build_query_str, char *: dns_build_query_zstr, const char *: dns_build_query_zstr)( \
122+
_Generic((name), Str *: dns_build_query_str, Zstr: dns_build_query_zstr)( \
123123
(out), \
124124
(id), \
125125
(name), \

Include/Misra/Parsers/Elf.h

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ typedef struct ElfSection {
122122
/// `.strtab` (for `symbols`) or `.dynstr` (for `dynamic_symbols`).
123123
///
124124
typedef struct ElfSymbol {
125-
Zstr name;
125+
Zstr name;
126126
ElfSymbolBind bind;
127127
ElfSymbolType type;
128128
u16 section_index;
@@ -183,15 +183,15 @@ typedef struct Elf {
183183
ElfSymbols dynamic_symbols;
184184
const u8 *build_id;
185185
u32 build_id_size;
186-
Zstr debuglink_name;
186+
Zstr debuglink_name;
187187
u32 debuglink_crc;
188188
} Elf;
189189

190190
///
191191
/// Open and parse an ELF file from disk.
192192
///
193193
/// out[out] : Populated on success.
194-
/// path[in] : Filesystem path. Prefer `Str *`; `const char *` accepted.
194+
/// path[in] : Filesystem path. Prefer `Str *`; `Zstr` (NUL-terminated) accepted.
195195
/// alloc[in] : Allocator for the read-in byte buffer and the section /
196196
/// symbol vectors. Must outlive the `Elf`.
197197
///
@@ -207,16 +207,14 @@ bool elf_open(Elf *out, Zstr path, Allocator *alloc);
207207
#define ElfOpen_2(out, path) \
208208
_Generic( \
209209
(path), \
210-
Str *: elf_open((out), ((Str *)(path))->data, MisraScope), \
211-
char *: elf_open((out), (const char *)(path), MisraScope), \
212-
const char *: elf_open((out), (const char *)(path), MisraScope) \
210+
Str *: elf_open((out), (Zstr)StrBegin((Str *)(path)), MisraScope), \
211+
Zstr: elf_open((out), (Zstr)(path), MisraScope) \
213212
)
214213
#define ElfOpen_3(out, path, alloc) \
215214
_Generic( \
216215
(path), \
217-
Str *: elf_open((out), ((Str *)(path))->data, ALLOCATOR_OF(alloc)), \
218-
char *: elf_open((out), (const char *)(path), ALLOCATOR_OF(alloc)), \
219-
const char *: elf_open((out), (const char *)(path), ALLOCATOR_OF(alloc)) \
216+
Str *: elf_open((out), (Zstr)StrBegin((Str *)(path)), ALLOCATOR_OF(alloc)), \
217+
Zstr: elf_open((out), (Zstr)(path), ALLOCATOR_OF(alloc)) \
220218
)
221219

222220
///

Include/Misra/Parsers/Http.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ bool http_header_init_copy(void *dst, const void *src, const Allocator *alloc);
9393
HttpHeader *http_headers_find_zstr(HttpHeaders *headers, Zstr key);
9494
HttpHeader *http_headers_find_str(HttpHeaders *headers, const Str *key);
9595
#define HttpHeadersFind(headers, key) \
96-
_Generic((key), Str *: http_headers_find_str, const Str *: http_headers_find_str, char *: http_headers_find_zstr, const char *: http_headers_find_zstr)( \
96+
_Generic((key), Str *: http_headers_find_str, Zstr: http_headers_find_zstr)( \
9797
(headers), \
9898
(key) \
9999
)
@@ -248,7 +248,7 @@ typedef struct HttpRequest {
248248
Zstr http_request_parse_zstr(HttpRequest *req, Zstr in);
249249
Zstr http_request_parse_str(HttpRequest *req, const Str *in);
250250
#define HttpRequestParse(req, in) \
251-
_Generic((in), Str *: http_request_parse_str, const Str *: http_request_parse_str, char *: http_request_parse_zstr, const char *: http_request_parse_zstr)( \
251+
_Generic((in), Str *: http_request_parse_str, Zstr: http_request_parse_zstr)( \
252252
(req), \
253253
(in) \
254254
)

Include/Misra/Parsers/JSON.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,7 @@ StrIter JSkipValue(StrIter si);
711711
} else { \
712712
StrPushBack(&(j), ','); \
713713
} \
714-
StrAppendFmt(&(j), "\"{}\":", (const char *)(k)); \
714+
StrAppendFmt(&(j), "\"{}\":", (Zstr)(k)); \
715715
JW_OBJ(j, writer); \
716716
} while (0)
717717

@@ -777,7 +777,7 @@ StrIter JSkipValue(StrIter si);
777777
} else { \
778778
StrPushBack(&(j), ','); \
779779
} \
780-
StrAppendFmt(&(j), "\"{}\":", (const char *)(k)); \
780+
StrAppendFmt(&(j), "\"{}\":", (Zstr)(k)); \
781781
JW_ARR(j, arr, item, writer); \
782782
} while (0)
783783

@@ -823,7 +823,7 @@ StrIter JSkipValue(StrIter si);
823823
} else { \
824824
StrPushBack(&(j), ','); \
825825
} \
826-
StrAppendFmt(&(j), "\"{}\":", (const char *)(k)); \
826+
StrAppendFmt(&(j), "\"{}\":", (Zstr)(k)); \
827827
JW_INT(j, i); \
828828
} while (0)
829829

@@ -869,7 +869,7 @@ StrIter JSkipValue(StrIter si);
869869
} else { \
870870
StrPushBack(&(j), ','); \
871871
} \
872-
StrAppendFmt(&(j), "\"{}\":", (const char *)(k)); \
872+
StrAppendFmt(&(j), "\"{}\":", (Zstr)(k)); \
873873
JW_FLT(j, f); \
874874
} while (0)
875875

@@ -889,7 +889,7 @@ StrIter JSkipValue(StrIter si);
889889
///
890890
#define JW_STR(j, s) \
891891
do { \
892-
StrAppendFmt(&(j), "\"{}\"", (const char *)((s).length ? (s).data : "")); \
892+
StrAppendFmt(&(j), "\"{}\"", (Zstr)((s).length ? (s).data : "")); \
893893
} while (0)
894894

895895
///
@@ -914,7 +914,7 @@ StrIter JSkipValue(StrIter si);
914914
} else { \
915915
StrPushBack(&(j), ','); \
916916
} \
917-
StrAppendFmt(&(j), "\"{}\":", (const char *)(k)); \
917+
StrAppendFmt(&(j), "\"{}\":", (Zstr)(k)); \
918918
JW_STR(j, s); \
919919
} while (0)
920920

@@ -934,7 +934,7 @@ StrIter JSkipValue(StrIter si);
934934
///
935935
#define JW_BOOL(j, b) \
936936
do { \
937-
StrAppendFmt(&(j), "{}", (const char *)((b) ? "true" : "false")); \
937+
StrAppendFmt(&(j), "{}", (Zstr)((b) ? "true" : "false")); \
938938
} while (0)
939939

940940
///
@@ -959,7 +959,7 @@ StrIter JSkipValue(StrIter si);
959959
} else { \
960960
StrPushBack(&(j), ','); \
961961
} \
962-
StrAppendFmt(&(j), "\"{}\":", (const char *)(k)); \
962+
StrAppendFmt(&(j), "\"{}\":", (Zstr)(k)); \
963963
JW_BOOL(j, b); \
964964
} while (0)
965965

0 commit comments

Comments
 (0)