# Resume warning-cleanup

## Status as of last session

Current state on `fix/wcast-qual-cleanup` after a full clean rebuild:

| Category                  | Was | Now |
|---------------------------|-----|-----|
| -Wignored-qualifiers      |   9 |   0 | DONE
| -Wunused-variable         |  15 |   0 | DONE
| -Wunused-but-set-variable |  29 |   0 | DONE (via -UNDEBUG, see below)
| -Wunused-function         |   1 |   0 | DONE
| -Wunused-parameter        |  44 |   0 | DONE
| -Wsign-compare            | 113 |   0 | DONE
| -Wpointer-sign            | 217 |   0 | DONE
| -Wformat-truncation=      |   7 |   7 | SKIP (verified safe last session)
| -Wdiscarded-qualifiers    |3776 |   0 | DONE
| -Wcast-qual               | 391 |  68 | DONE (all annotated, see below)
| -Wmissing-prototypes      | 111 |   0 | DONE
| -Wundef                   |  33 |   0 | DONE
| -Wshadow                  |  25 |   0 | DONE

### -Wpointer-sign (DONE)

All 217 -Wpointer-sign warnings were resolved across libopendkim/dkim.c,
libopendkim/dkim-mailparse.c, libopendkim/dkim-keys.c, opendkim/opendkim.c,
and opendkim/opendkim-testmsg.c using narrow casts at the
`u_char *` (libopendkim API — DKIM byte data) /
`char *` (daemon — printable strings) boundary, plus three local
declaration flips in opendkim.c (the Authentication-Results
`header` buffer in mlfi_eom, the `d`/`tmpdata` locals in the
KeyTable resolution path) where the variable's dominant use made
the natural type unambiguous.

Static helpers in dkim.c kept their existing `u_char *` parameter
style — do NOT retype them as `char *` or `const char *` for local
readability, even though it would be technically correct; it breaks
the file's visual consistency.  **Stop and report if a fix needs an
API signature change — that decision belongs to the user.**  This
session DID make two such changes after explicit approval:
the `dkim_sigkey_t` typedef became `const unsigned char *` (commit
296bc946), and `dkim_options` was split into const-correct
`dkim_setopt` / `dkim_getopt` (commit 4c403b31).

### Pattern: U_STR macro for static const tables

The three sign-recommendation tables at the top of dkim.c use a
file-scoped helper:

```c
#define U_STR(s) ((const u_char *) (s))

const u_char *dkim_should_signhdrs[] =
{
    U_STR("from"),
    U_STR("reply-to"),
    ...
};
...
#undef U_STR
```

Scope it to *just the table block* with `#undef U_STR` immediately
after.  Do NOT extend `U_STR` to non-table call sites — when passing
a string literal to a function taking non-const `u_char *` (e.g. the
static `dkim_get_header`), use an inline `(u_char *) "lit"` cast.
That matches the file's existing 61+ such casts.

### -Wcast-qual (DONE — 97 instances, all annotated)

97 unique sites carry explicit `/* libmilter's xxx_yyy is char *;
libmilter never writes through this pointer ... */` annotations
(or the equivalent for LMDB / dkim_set_dns_callback / etc.).  The
comment style is documented in
`~/.claude/projects/-home-edmund-devel-projects-opendkim-ng/memory/feedback_legacy_api_comments.md`
— name the external symbol, name the read-only-ness, name the cast
target.  When in doubt: fix the typedef (if you can change the API)
or annotate (if you can't).

The two big structural moves this session were:

- **dkim_sigkey_t** typedef widened to `const unsigned char *`.
  libopendkim never writes through secretkey; the previous typedef
  forced every test caller to either accept a discarded-qualifiers
  warning or copy KEY into a writable buffer.  The `conf_seckey`
  daemon field was *separately* retyped to plain `unsigned char *`
  because dkimf_zapkey owns and zeroes it (different lifetime, same
  bytes-on-disk shape).  Commit 296bc946 + fcfaffa9.
- **dkim_options** split into `dkim_setopt(const void *)` /
  `dkim_getopt(void *)`.  `dkim_options` remains as a legacy ABI
  wrapper with a "new code should prefer …" docstring; ~454 internal
  call sites were sed-migrated.  Commit 4c403b31.

### -Wdiscarded-qualifiers (1 deferred)

The 11 remaining stragglers from the last session were cleared in
three follow-up commits on this branch:

- `dkim_sig_hdrsigned`'s `hdr` parameter widened to `const u_char *`
  in the public header and definition (commit 2bf7d796).  The body
  only feeds `hdr` to `strcasecmp` / `strncasecmp`; the one internal
  callsite in `dkim.c:5643` and the four t-test99.c reports cleared
  in one move.
- `over_sign[]` / `skipheaders[]` / `mustbesigned[]` file-scope tables
  in t-test{47,44,102}.c widened to `const char *foo[]`, matching the
  existing `signheaders[]` in t-test54.c (commit 201caccf).
- t-test73.c's `q = REPORTRECORD` and opendkim-testmsg.c's
  `tmpdir = DEFTMPDIR` locals flipped to `const char *`
  (commit a5a2b57c).

The previously deferred `miltertest/miltertest.c:2046`
`host = DEFCLIENTHOST;` straggler was cleared as part of the
dedicated miltertest sweep below.

### What got done in the big -Wdiscarded-qualifiers sweep (20 commits, 6ae6ff80..d676e47d)

Discarded-qualifiers went 894 → 12 across these structural fixes
(see `git log 6ae6ff80..d676e47d` for the full list):

- `struct configdef` / `struct config` name fields → `const char *`,
  plus `config_check` return type and `config_error` return type.
- `struct lookup.str` const-ified in opendkim.c (7 file-scope tables)
  and a separate `struct lookup` in opendkim-ar.c.
- `struct dkimf_db_table.name` (opendkim-db.c file-scope dbtypes table)
  → `const char *`.
- `struct dkimf_lua_script_result.lrs_error` → `const char *`;
  tagged-union with `dofree` flag, so the six gated free() sites
  cast via `(void *)(uintptr_t)` (requires `<stdint.h>`).
- `dkimf_db_get` second arg `void *buf` → `const void *buf`.  The
  one site that can't be const (MDB `key.mv_data = buf`) goes
  through `(void *)(uintptr_t)` with a Legacy API constraint comment.
- `dkimf_db_open`'s `err` out-param → `const char **`.  All 21
  daemon-side `dberr` locals plus genzone/testkey/testmsg/lua/popdb
  callers updated.
- `dkimf_dns_init` and `dkimf_config_setlib`'s `err` out-params →
  `const char **`.  Reconciles 22 `*err = "literal"` sites in
  dkimf_config_setlib alone.
- `dkimf_libstatus` / `dkimf_setreply` / `dkimf_getsymval` /
  `dkimf_quarantine` / `dkimf_addheader` / `dkimf_insheader` /
  `dkimf_dstring_printf`: read-only inputs now `const char *`,
  smfi_* boundary calls carry annotated `(char *)` casts.
- `dkimf_test_*` companions in test.h / test.c follow the daemon
  wrappers above.
- `conffile` global + `configfile` / `conffile` locals in
  opendkim-genzone.c / opendkim-testkey.c → `const char *`;
  config_load / config_load_level signature widened to
  `const char *file`.
- t-conformance.c: 25 `dkim_sigkey_t key; ... key = KEY;` sites
  trivially clean now that `dkim_sigkey_t` is const.  The previous
  session's ed25519 mutable-buffer workaround (commit 96468ed6) was
  reverted: keys really are read-only.
- t-setup.c, t-verifyperf.c, t-signperf.c, t-test54.c, t-test92.c,
  t-test117.c, opendkim/util.c (optlist), opendkim/opendkim-arf.c
  (arf_type_string / arf_dkim_failure_string returns): various small
  const-widenings.
- 94 `dkim_options(..., DKIM_OP_SETOPT, KEYFILE, ...)` warnings in
  tests cleared by the dkim_options split above.

### Follow-up round: straggler cleanup (3 commits, 2bf7d796..a5a2b57c)

Discarded-qualifiers went 12 → 1 across:

- `dkim_sig_hdrsigned(DKIM_SIGINFO *, u_char *hdr)` API widened to
  `const u_char *hdr` in the public header (commit 2bf7d796).
- Three file-scope `char *foo[]` literal tables in t-test{47,44,102}.c
  flipped to `const char *foo[]` (commit 201caccf).
- Two local-var widens in t-test73.c and opendkim-testmsg.c
  (commit a5a2b57c).

Only `miltertest/miltertest.c:2046` remained after this round; it was
picked up by the dedicated miltertest sweep below.

### Dedicated miltertest sweep (6 commits, 9d12e214..fa0998d9)

miltertest had previously been carved out of the wider cleanup
because it held a long tail of -Wcast-qual sites.  Cleared in one
focused pass:

- 9 `-Wmissing-prototypes`: file-internal helpers (`mt_inet_ntoa`,
  `mt_lua_reader` / `_alloc`, `mt_flush_eomreqs`, `mt_eom_request`,
  `mt_milter_read` / `_write`, `mt_assert_state`, `usage`) marked
  `static`; `mt_inet_ntoa`'s definition gated by the same
  `!(HAVE_GETADDRINFO && HAVE_INET_NTOP)` condition as its call site
  to avoid a fresh `-Wunused-function` (commit 9d12e214).
- 1 `-Wcast-qual` at `mt_milter_read`: the `buf` out-parameter was
  spuriously `const char *` even though the function `read()`s into
  it; drop the const and the `(void *)` cast (commit cdb90767).
- 4 `-Wcast-qual` from `mt_lua_io.lua_io_script`: bogus `const char *`
  on the script buffer that miltertest allocates, fills via `read()`,
  and `free()`s.  Retype the struct member as `char *`; the four
  callsite casts (malloc, read, two frees) collapse away.  The
  `mt_lua_reader` return stays `const char *` for Lua's `lua_Reader`
  signature via implicit widening (commit 3e3c583a).
- 29 `-Wcast-qual` + the deferred `-Wdiscarded-qualifiers` at
  miltertest.c:2046: const-correct all `(char *) lua_tostring(...)`
  sites across `mt_echo`, `_chdir`, `_macro`, `_conninfo`, `_unknown`,
  `_helo`, `_mailfrom`, `_rcptto`, `_header`, `_bodystring`,
  `_bodyfile`, `_eom_check` (all eight cases) and `_getheader`
  (commit 14a09fb6).  The `mt_conninfo` `host` retype incidentally
  cleared the 2046 straggler.
- 1 `-Wcast-qual` at `mt_connect`: the function was casting away
  const on `lua_tostring`'s return and mutating Lua-owned memory in
  place to split the `proto:host:port` connection string.  Latent
  bug: Lua is free to reclaim or share that buffer after the
  `lua_pop` a few lines above.  Replace the pointer with a
  `char sockinfo[BUFRSZ]` stack buffer populated via `strlcpy`; the
  in-place ':'/'@' splits now operate on memory we own
  (commit b6eab51a).
- The two residual cast-qual sites (`writev`'s `iov_base`, `execv`'s
  `argv`) are true POSIX/legacy-API const-strips and got the
  project's standard block-comment annotation (commit fa0998d9).

Net delta: miltertest went 47 → 2 warnings.  The 2 remaining are
annotated legacy-API casts that contribute to the project-wide
-Wcast-qual figure above and follow the same documented intentional
pattern as the other 66.

### -Wmissing-prototypes (DONE)

Production-code sweep cleared 66 of 102 warnings (clean-rebuild
verified) across six commits, leaving 36 in `libopendkim/tests/`:

- **opendkim/opendkim.c** (47 → 0, two commits):
  - 43 cleared by defining `DKIMF_LUA_PROTOTYPES` (inside the
    existing `USE_LUA` guard) immediately before `#include
    "opendkim.h"`.  opendkim.h already declares all 42 `dkimf_xs_*`
    Lua extensions plus `dkimf_import_globals` under that guard;
    opendkim-lua.c set the guard, opendkim.c didn't.  Commit
    8fba6663.
  - 4 file-internal helpers (`dkimf_reptoken`, `dkimf_securefile`,
    `dkimf_miltercode`, `dkimf_ar_all_sigs`) marked `static`.
    Commit f659804c.
- **opendkim/opendkim-dns.c** (10 → 0, one commit): five
  `dkimf_ub_*` Unbound callbacks (`_trustanchor`, `_config`,
  `_init`, `_close`, `_nslist`) marked `static`, matching the
  existing storage class on the sibling `_query` / `_cancel` /
  `_waitreply` callbacks.  Library wires them in via
  `dkim_dns_set_*` inside `dkimf_unbound_setup` only.  Counts are
  doubled because the file is built into both the static and
  shared libopendkim.  Commit dea76f58.
- **opendkim/opendkim-genzone.c** (4 → 0): `strflen`, `loadkey`,
  `despace`, `usage` made static.  Commit 76f1e031.
- **opendkim/opendkim-ar.c / opendkim-testkey.c / opendkim-testmsg.c**
  (3 → 0, bundled): `ares_dedup`, `loadkey`, `decr` made static.
  Commit 4dd03e74.
- **libopendkim/dkim.c** (2 → 0): `dkim_get_key`'s prototype hoisted
  from a hand-rolled `extern` in dkim-test.c into dkim-keys.h next
  to the existing `dkim_get_key_dns` / `_file` siblings.  Both call
  sites already `#include "dkim-keys.h"`.  Commit e7a39391.
- Fixed all 36 internal functions within the libopendkim/tests test
  files by marking them static.

### -Wundef / -Wshadow

Smaller, mostly mechanical.  Address after the tests-only
-Wmissing-prototypes tail, or in parallel if a fresh session prefers.

## Workflow notes (carried over and updated)

- This is a CMake project, not autotools.  Build with all switches on
  (LUA, REDIS, UNBOUND, and what others there are). Build with
  `cmake --build build --parallel 2>&1 | tee build.log`.
  Do NOT invoke `make` directly — use `cmake --build` so configure
  changes are picked up automatically.
- After a CMakeLists.txt or cmake/*.cmake change, reconfigure with
  `cmake -B build` (or `cmake build` from the repo root) before
  building.  `cmake --build` will do this for you when needed.
- `ctest` (run from `build/`) is the test-suite invocation; expect
  "100% tests passed, 0 tests failed out of 174" with 5 disabled.
- One logical fix per commit, matching the existing "Fix: …" style.
- Group mechanically-identical fixes into one commit; keep different
  bug shapes separate.
- **Bug fixes get their own commits** — do not bundle a real
  behavioural fix with surrounding cleanup, even within one file.
  Example from a prior session: the `dkimf_xs_delheader` idx-arg fix
  (d8a9beaa) was committed alone, separate from the cleanup commit
  (e27bab95) that touched the same file.
- No `Co-Authored-By: Claude` lines on commits.
- Don't commit unrelated dirty files — `PROMPT_WARNINGS` (this file)
  is left dirty by the user and must not be folded into a fix commit.
  Commit PROMPT_WARNINGS updates on their own ("Docs: …").
- After each commit, rebuild and confirm the warning count for that
  category dropped by the expected amount.  Incremental builds only
  recompile changed files, so a clean rebuild
  (`cmake --build build --target clean && cmake --build build --parallel`)
  is required for a true delta against the table above.
- Skip the 7 -Wformat-truncation warnings — verified safe in a
  prior session (opendkim.c:9536/11286, opendkim-db.c:1689 are
  return-checked; opendkim.c:12371 has a provably-sufficient buffer).
  Line numbers may have shifted from edits since.
- Do NOT introduce new uses of the `__P((...))` K&R-compat macro on
  newly-added prototypes — write plain `(...)` argument lists.
  Existing `__P` usage on touched prototypes can stay.
- Legacy-API const-strip cast comments must be *descriptive*: say
  WHY we cast (which external symbol has the non-const signature)
  and HOW it is safe (the library never writes through the pointer).
  A one-liner like "Legacy API constraint: xxx is char *" is not
  enough — the next reader still has to go check the external library
  to know the cast is safe.  See
  `feedback_legacy_api_comments.md` in this project's memory directory
  for the standard shape and a worked example.  Don't go back and
  reformat old comments unless explicitly asked.
