From 3b07975a7805c00c6f5e41bcd690bd7de1606a63 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 11:16:28 +0000 Subject: [PATCH] php: the config.h template has an epilogue too, and it carries Past ZEND_API, and stopped at zend_operators.h:308 -- `incompatible types: "IND VOID" and "INT" for op "RETURN"' on `return mempcpy(dest, src, n);'. mempcpy is declared unguarded at sys/include/ape/string.h:62, and was not reachable in that translation unit at all. zend_operators.h does not include it, and the only thing that does is main/php.h:208, which a Zend file never reaches. memcpy, memcmp and strncasecmp were undeclared in the same unit and said nothing, because an implicit int converts to size_t in silence: the pointer return was the only one loud enough to notice. php_config.h.in:2225-2236 ends with four #includes -- stdlib.h, sys/types.h, sys/select.h, string.h -- and that is how every unit in php sees them. The same category as the prologue, missed for the same reason, and copied verbatim now with its guards. cfghdr-sweep could not see it either: its first version compared macro NAMES, which is the prologue's shape and not the epilogue's. The category is verbatim template text, and an #include is as verbatim as a #define. It compares both now and its control carries both shapes, so a run reports 2 where it reported 1. The host reproduced the whole thing -- gcc -fsyntax-only on block_pass.c with the real flags listed the same implicit declarations -- which is how it was diagnosed without a second round trip. One residual host-only artifact is recorded so nobody fixes it: memrchr still warns there, because glibc guards it behind _GNU_SOURCE while APE declares it unguarded. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs --- docs/notes/php.md | 31 ++++++++++++++ sys/lib/tests/cfghdr-sweep.py | 51 ++++++++++++++++++----- sys/src/ape/cmd/php/cfg/main/php_config.h | 31 ++++++++++++++ 3 files changed, 103 insertions(+), 10 deletions(-) diff --git a/docs/notes/php.md b/docs/notes/php.md index 92f383133..53ca59fa6 100644 --- a/docs/notes/php.md +++ b/docs/notes/php.md @@ -507,6 +507,37 @@ two things on its first run beyond the case it was built for: uses them; *recorded, not acted on*, which is why clisp is not in the sweep's gating list. +### And the same mistake had a second half: the EPILOGUE + +The next build got past `ZEND_API` and stopped at +`zend_operators.h:308`: +`incompatible types: "IND VOID" and "INT" for op "RETURN"`, on +`return mempcpy(dest, src, n);`. + +`mempcpy` is declared, unguarded, at `sys/include/ape/string.h:62` -- +and **`` was not reachable in that translation unit at +all**. `Zend/zend_operators.h` does not include it; the only thing +that does is `main/php.h:208`, which a Zend file never reaches. +***`memcpy`, `memcmp` and `strncasecmp` were undeclared in the same +unit and said nothing***, because an implicit `int` converts to +`size_t` in silence: *the pointer return was the only one loud +enough to notice.* + +`main/php_config.h.in:2225-2236` ends with four `#include`s -- +``, ``, ``, `` -- and +that is how every unit in php sees them. **The same category as the +prologue, missed for the same reason**, and `cfghdr-sweep` could not +see it either, because its first version compared macro NAMES and an +`#include` has none. It compares both now, and its control carries +both shapes. + +*The host reproduced the whole thing* -- `gcc -fsyntax-only` on +`block_pass.c` with the real flags listed the same implicit +declarations -- which is how it was diagnosed without a second round +trip. One residual host-only artifact: `memrchr` still warns there, +because glibc guards it behind `_GNU_SOURCE` while APE declares it +unguarded at `string.h:54`. *Not a finding; the host's limit.* + The other 18 are `_GNU_SOURCE` and its family, `__STDC_WANT_*`, and two Linux headers. **Left out deliberately**: APE's feature macros are `_POSIX_SOURCE`, `_BSD_EXTENSION` and `_PLAN9_SOURCE`, which the diff --git a/sys/lib/tests/cfghdr-sweep.py b/sys/lib/tests/cfghdr-sweep.py index e136996a7..7b339150b 100644 --- a/sys/lib/tests/cfghdr-sweep.py +++ b/sys/lib/tests/cfghdr-sweep.py @@ -89,12 +89,13 @@ DEFINE = re.compile(r'^\s*#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)') IFNDEF = re.compile(r'^\s*#\s*ifndef\s+([A-Za-z_][A-Za-z0-9_]*)') +INCLUDE = re.compile(r'^\s*#\s*include\s+([<"][^>"]+[>"])') UNDEF_C = re.compile(r'^\s*/\*\s*#\s*undef\s+([A-Za-z_][A-Za-z0-9_]*)\s*\*/') UNDEF_H = re.compile(r'^\s*#\s*undef\s+([A-Za-z_][A-Za-z0-9_]*)') def scan(path): - """(defines, undefs) as name sets. + """(defines, undefs, includes) as sets. ***THE INCLUDE GUARD IS EXCLUDED, AND THE CONTROL IS WHAT SAID SO.*** A guard is a `#define' like any other, so the first @@ -105,7 +106,7 @@ def scan(path): have shown it. *A fixture is allowed to differ from the real case in exactly the way that exposes the bug.* """ - d, u = set(), set() + d, u, inc = set(), set(), set() guarded = set() with open(path, encoding='utf-8', errors='replace') as f: for line in f: @@ -124,15 +125,41 @@ def scan(path): m = UNDEF_H.match(line) if m: u.add(m.group(1)) - return d, u + continue + m = INCLUDE.match(line) + if m: + inc.add(m.group(1)) + return d, u, inc def check(tmpl, ans): """Returns the number of GATING findings.""" - tdef, tundef = scan(tmpl) - adef, aundef = scan(ans) + tdef, tundef, tinc = scan(tmpl) + adef, aundef, ainc = scan(ans) gating = 0 + # 0. ***THE TEMPLATE'S OWN `#include's, WHICH ARE WHY THIS CHECK + # EXISTS A SECOND TIME.*** php's template ends with four, and + # they are how every translation unit comes to see : + # `zend_operators.h' does not include it and neither does + # anything a Zend file reaches. Leaving them out gave + # `incompatible types: "IND VOID" and "INT" for op "RETURN"' on + # `return mempcpy(...)' -- and memcpy, memcmp and strncasecmp + # were undeclared in the same unit and said nothing, since `int' + # converts to `size_t' in silence. + # + # *The first version of this sweep compared macro NAMES only*, + # which is the prologue's shape and not the epilogue's. The + # category is "verbatim template text", and an `#include' is as + # verbatim as a `#define'. + lostinc = sorted(tinc - ainc) + if lostinc: + print(' MISSING template #includes -- verbatim text, and the') + print(' reason every unit sees the headers it does:') + for n in lostinc: + print(' %s' % n) + gating += len(lostinc) + # 1. the copied-through text. This is the gate. # # ***A NAME THE TEMPLATE BOTH `#define's AND `#undef's IS A @@ -185,6 +212,8 @@ def check(tmpl, ans): /* another */ #undef HAVE_SILENT + +#include #endif """ @@ -208,9 +237,9 @@ def control(): a = os.path.join(d, 'a.h'); open(a, 'w').write(CONTROL_ANS) print('control:') n = check(t, a) - ok = (n == 1) - print('control: %d gating finding(s), want 1 (COPIED_THROUGH) -- %s' - % (n, 'ok' if ok else 'FAILED')) + ok = (n == 2) + print('control: %d gating finding(s), want 2 (COPIED_THROUGH and' + ' the #include) -- %s' % (n, 'ok' if ok else 'FAILED')) return 0 if ok else 1 @@ -230,10 +259,12 @@ def main(): print('%s' % os.path.relpath(ans, ROOT)) gating += check(tmpl, ans) print() - print('---- %d template #define(s) not carried through' % gating) + print('---- %d piece(s) of verbatim template text not carried ' + 'through' % gating) if gating: print('Copy them VERBATIM from the template: they are not') - print('capability answers and autoheader never generates them.') + print('capability answers, and autoheader generates only') + print('`#undef NAME\' lines, so nothing else in a .h.in is.') return gating diff --git a/sys/src/ape/cmd/php/cfg/main/php_config.h b/sys/src/ape/cmd/php/cfg/main/php_config.h index 06e46d72b..1dc42e87e 100644 --- a/sys/src/ape/cmd/php/cfg/main/php_config.h +++ b/sys/src/ape/cmd/php/cfg/main/php_config.h @@ -438,4 +438,35 @@ /* #undef HAVE_DTRACE */ /* #undef HAVE_HUGE_CODE_PAGES */ +/* + * ***THE EPILOGUE IS UPSTREAM'S TOO, AND LEAVING IT OUT WAS THE SAME + * MISTAKE TWICE.*** `main/php_config.h.in:2225-2236' ends with four + * `#include's, and they are how EVERY translation unit in php comes + * to see `': `Zend/zend_operators.h' does not include it, + * and neither does anything else `Zend/Optimizer/block_pass.c' + * reaches -- only `main/php.h:208', which a Zend file never gets to. + * + * Without them `zend_operators.h:308' answered + * `incompatible types: "IND VOID" and "INT" for op "RETURN"' on + * `return mempcpy(dest, src, n);' -- an undeclared function returning + * implicit `int' where a `void *' belongs. **`memcpy', `memcmp' and + * `strncasecmp' were undeclared in the same translation unit** and + * said nothing, because `int' converts to `size_t' without + * complaint: *the pointer was the only one loud enough to notice.* + * + * Verbatim, guards included. + */ + +#include + +#ifdef HAVE_SYS_TYPES_H +# include +#endif + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#include + #endif /* PHP_CONFIG_H */