Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/notes/php.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 **`<string.h>` 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 --
`<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 `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
Expand Down
51 changes: 41 additions & 10 deletions sys/lib/tests/cfghdr-sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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 <string.h>:
# `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
Expand Down Expand Up @@ -185,6 +212,8 @@ def check(tmpl, ans):

/* another */
#undef HAVE_SILENT

#include <stdlib.h>
#endif
"""

Expand All @@ -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


Expand All @@ -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


Expand Down
31 changes: 31 additions & 0 deletions sys/src/ape/cmd/php/cfg/main/php_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<string.h>': `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 <stdlib.h>

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include <string.h>

#endif /* PHP_CONFIG_H */
Loading