Skip to content

selftest 070 requires LC_ALL=C on a piped sort but not on <(sort ...), so three comm helpers went unchecked #1112

Description

@jdatcmd

test/selftest/070-and-comm-s-two-inputs-must.sh requires every sort feeding a comm to pin LC_ALL=C. Its pattern only matches the pipeline form, so the process-substitution form passes unchecked.

The gap

test/selftest/070:24   _cm_sorts="$(grep -E '\|[[:space:]]*sort' "$_f" || true)"

That matches ... | sort. It cannot match <(sort "$1").

The guard's own comment already notices the distinction — "The first reads a FILE and is not a pipeline at all" — but the regex only covers the pipeline half.

It was live, and the guard was green on it

Found by @OffgridwithJD reviewing #1110. test/run_all_versions.sh used comm in three helpers, every one through process substitution:

matches for '| sort'        : 0
occurrences of '<(sort ...)': 3
selftest 070 verdict        : run_all_versions.sh passes the guard

So a file using comm three times read as compliant because it never piped into sort. One of the three, pgc_own_mechanism_suites, had been there since #928.

The reordering is real, not theoretical

Measured on real suite names, two collations:

LC_ALL=C         ... native_repack  pg_dump_roundtrip  pgc_setup  ... projection_update  projections
LC_ALL=en_US     ... native_repack  pgc_setup  pg_dump_roundtrip  ... projections  projection_update

Two pairs swap, and comm says so when fed the mismatch:

comm: file 1 is not in sorted order
comm: input is not in sorted order

It is not live on CI: the container is C.UTF-8, which orders these as C does, and the workflows set no locale. It bites a developer on en_US.UTF-8, which is the default on plenty of boxes — and it bites them as a wrong set, not an error, because comm's complaint goes to stderr while it still prints output.

A second trap worth encoding

LC_ALL=C comm <(sort a) <(sort b) pins only comm's own comparison. The substitutions run in subshells of the parent and inherit the parent's locale, so that form still sorts on the caller's locale. Both halves need pinning:

LC_ALL=C comm -23 <(LC_ALL=C sort "$1") <(LC_ALL=C sort "$2")

A widened guard should assert both, or it will bless the half-pinned form.

Current state

#1110 pins all three helpers in run_all_versions.sh and adds arms over that file specifically, so nothing is unpinned in the tree today:

process-substitution sorts feeding comm, unpinned, tree-wide: 0

This issue is about the guard, not the tree. The next file to reach for comm with process substitution gets the same free pass, and the arms in #1110 only cover run_all_versions.sh.

What to do

Widen 070's pattern to reach <(sort as well as | sort, and assert the substitution is pinned rather than only the comm. It needs its own removal proof: plant a file using each form, unpinned, and require the guard to name each one. The pipeline half already has coverage, so the new arms are the substitution form and the half-pinned form.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions