Stop probing "link" as if it were the MSVC linker

The tool table probed the bare name, and coreutils' hard-link utility owns that name on
every host that has it -- including the Git Bash and MSYS installs on Windows, where the
row would be trusted most. The probe therefore reported the MSVC header toolchain present
on a host with no MSVC at all, and the present-tool count was one too high.

The row now probes editbin, whose name has no Unix collision, and its note says that
link.exe is not probed by name. strings stays in the table: binutils strings reads PE
images, so that row is true on a Unix host, and the test carries the reason the
distinction is per-name rather than per-existence.

tests/unit/test_hwprobe_tool_table.py pins three things: no row probes a name from the
ambiguity list; editbin is the row that carries header edits; and, as the general form,
every probed tool that resolves on this host is run with --version and must not identify
as GNU coreutils. FINDINGS F17 records the defect and how it surfaced -- by reading the
probe's own output while collecting host facts for a protected-sample run, where a row
claiming the MSVC linker sat beside gdb, a MinGW gcc and no cl.
This commit is contained in:
root
2026-10-27 16:50:07 +08:00
parent 5d7c41bdd5
commit 69a89abf0c
4 changed files with 86 additions and 1 deletions
+1
View File
@@ -28,6 +28,7 @@ and "the script was measured doing X on this host".
| `cli/test_cli_registry_surface.py` | The registry and doctor subprocess contracts: `--list` / `--json` / single-capability exit codes and `RESULT=` tokens, the doctor's three blocks and its 0-versus-3 verdict, and the view flags that print one block only. |
| `unit/test_iat_callsite_restore.py` | The import-layer repair's decisions on a generated image: the six-byte window on either side of a call, the refusal when the sixth byte is the next instruction, the refusal when two slots hold one address, the pairing rule, the search that recovers the table from the call targets and the control that suppresses it, two candidate tables refused, an empty search reported as empty, and the verdict token that follows from the evidence rather than from a count. |
| `cli/test_cli_iat_callsite.py` | The repair's published contract as a subprocess: a report-only run writes nothing into the working directory, `--out` leaves the input byte-identical and the written sites re-decode to indirect calls, the PE32 displacement is an absolute slot address where the PE32+ one is relative, and a refusal is exit 1 rather than a quiet success. |
| `unit/test_hwprobe_tool_table.py` | The probe's tool names are a claim about the host: no row may probe a name that a Unix utility also owns (`link` was reported as the MSVC linker on a host whose `link` is coreutils), and every row that resolves here must not identify as a coreutils binary. |
| `cli/test_cli_dart_snapshot.py` | The snapshot reader's CLI: the refusals and their exit codes, `--find` case-insensitivity and the exit code a miss produces, `--at` by file offset and by virtual address, and the `--json` document. |
| `integration/test_pipeline_end_to_end.py` | Build, locate, patch, re-read: the patched bytes, the untouched control site, the recomputed checksum, and a control run in which nothing changes. |
| `integration/test_across_builds.py` | Pattern hit counts compared between two builds, including the case where a patch removes the very byte the pattern was located with. |
+60
View File
@@ -0,0 +1,60 @@
"""The tool table's names are a claim about what is on the machine.
A name in this table is probed with shutil.which, so a name that also belongs to a common Unix
utility produces a false capability claim: the probe says the MSVC toolchain is present because
/usr/bin/link exists, and /usr/bin/link is coreutils' hard-link utility. That is the exact failure
shape this repository is built against -- a well-formed row that is confidently wrong -- and it was
found by reading the probe's own output on a Linux host, where link was reported present alongside a
note about the MSVC linker.
"""
import shutil
import subprocess
import pytest
pytestmark = pytest.mark.unit
# Names that exist as ordinary Unix utilities, so probing them says nothing about the Windows tool
# they are paired with. Each entry is (name, the utility it collides with).
# binutils strings is deliberately not in this list: it reads PE images, so probing it on a Unix
# host is a true statement about a tool that does the job. link is different -- coreutils' link
# creates a hard link and has nothing to do with the MSVC linker it was paired with.
AMBIGUOUS = [
("link", "GNU coreutils hard-link utility"),
("install", "GNU coreutils file installer"),
]
def test_the_table_does_not_probe_an_ambiguous_name(load_script):
names = dict(load_script("hwprobe.py").TOOL_NAMES)
for name, collision in AMBIGUOUS:
assert name not in names, (
"%r is probed, but it is also the %s on every Unix host, so a hit is not evidence of "
"the Windows tool being available" % (name, collision))
def test_editbin_replaces_link_for_header_edits(load_script):
names = dict(load_script("hwprobe.py").TOOL_NAMES)
assert "editbin" in names
assert "header" in names["editbin"]
def test_no_probed_name_resolves_to_a_coreutils_binary(load_script):
"""The general form of the same defect, checked against this host rather than a list."""
names = [name for name, _ in load_script("hwprobe.py").TOOL_NAMES]
checked = 0
for name in names:
found = shutil.which(name)
if not found:
continue
try:
proc = subprocess.run([found, "--version"], capture_output=True, text=True,
timeout=10, encoding="utf-8", errors="replace")
except (OSError, subprocess.SubprocessError):
continue
checked += 1
assert "GNU coreutils" not in (proc.stdout or ""), (
"%r resolves to %s, which is a coreutils binary -- the row would claim a Windows tool"
% (name, found))
if not checked:
pytest.skip("no probed tool on this host answers --version, so there is nothing to check")