Two genre sweeps in one pass, since they hit the same paragraphs.
Implementation detail standing in for contract: internal names
(NotRecognized, "the regex layer", gcc.yaml as a criterion actor,
"Bear's per-compiler interpreters", "inner writer", collector-address
and per-wrapper TCP plumbing) are restated as the observable outcomes
they gestured at; performance and buffering speculation (syscall lists,
O(r) costs, memory proportionality, patterns-compiled-once, streaming
machinery) is deleted or reduced to its observable half. Bear's own
flags and config keys (--append, --input, duplicates, compilers/as,
the four path-format values) leave Intent and criteria for behavioral
descriptions - the man page names them (verified), Testing command
lines keep them. The duplicates-key criterion turned out to be a
verbatim duplicate once de-literalized and is dropped. The events
format's `-` token is ruled CLI surface, not format convention: the
stream contract survives with no literal.
Accepted gaps move to Known limitations sections (nine files gain
one): static binaries, the execvp environment gap, the single -o copy,
symlink non-resolution, the straddling single-TU case, native-Windows
CC/CXX, nvcc/XL response-file syntaxes, the fixed wrapper-name set and
symlink-only detection - each keeping the one-line reasoning that
keeps it out. The non-template Scope section dissolves into NFC plus
Known limitations. Narratives squatting in requirements move to their
rationale entries: the masquerade-loop story, the parse-sh boundary
calls, the make/ninja handler reliance.
No contract was lost; every rephrasing was checked against the code or
the man page before landing.
Found by the /requirements skill audit (dogfooding 1038d916).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3.3 KiB
Wrapper recursion: why filter Bear's lookup PATH, not the alternatives
Context
In wrapper mode, a compiler env var or PATH discovery can resolve to a masquerade wrapper (ccache, distcc, icecc, ...) rather than the real compiler. If Bear records that path, the build loops the wrapper into itself. Several fixes were considered before settling on "resolve past the masquerade dir and record the real compiler".
How the loop arises: masquerade wrappers (ccache, distcc, icecream,
colorgcc, buildcache) install a directory of symlinks named after real
compilers and prepend it to PATH; a bare gcc then resolves to the
wrapper, which looks up the real compiler further along PATH (skipping
its own symlinks) and forwards the call. Bear's wrapper mode prepends
its own directory of compiler-named links to PATH as well. A wrapper
that recognises itself only by symlink comparison cannot tell Bear's
link from a real compiler, selects it as the "real" gcc, and the two
forward to each other without end. distcc avoids that specific loop by
stripping every PATH entry up to and including its own directory, but
that also drops Bear's directory, silently breaking nested
interception.
Decision
Filter Bear's own lookup PATH so resolution lands on the real compiler past any masquerade directory, and record that path. Do not touch the child's PATH or set ccache-specific variables.
Consequences
Detecting a masquerade wrapper by resolving the symlink of the discovered path, rather than matching a list of known directory locations, covers distribution-local masquerade setups automatically: any installer that symlinks compiler names to a wrapper binary is caught, with no per-distro path table to maintain. The cost is missing a masquerade installed as a shell script or hard copy, which no major distro ships.
The rejected alternatives were each narrower or more fragile:
- Set
CCACHE_COMPILERin the wrapper's child environment (the original proposal). The path the wrapper knows IS the ccache symlink (that is whatwhich gccreturned at setup), andCCACHE_COMPILERpointing at a symlink-to-ccache makes ccache recurse into itself. Empirically verified on Fedora:CCACHE_COMPILER=/usr/lib64/ccache/gcc ccache gcc -c foo.changs and must be killed;CCACHE_COMPILER=/usr/bin/gccworks. Fixing it would require resolving past ccache to get the real path anyway - which is what this requirement does, makingCCACHE_COMPILERredundant. It is also ccache-specific and would not help icecc or distcc. CCACHE_PATHset to PATH minus.bear/. ccache-specific (no equivalent for other wrappers), still requires enumerating a safe PATH, and does not address Bear's config pointing at the wrong executable.- Removing masquerade directories from the child's PATH. Heavy-
handed: a masquerade dir might also hold binaries that do not loop
(some installs put
distccitself there); stripping it globally would break those. Filtering Bear's own lookup PATH is the narrower intervention.
References
- Requirement:
interception-wrapper-recursion - Related requirement:
interception-wrapper-mechanism - Issue #445 - original PATH-ordering report
- Issue #686 - bare-name CC resolution
- ccache 4.x manual: https://ccache.dev/manual/4.10.2.html
- icecream masquerade setup: https://github.com/icecc/icecream