Skip to content

tr: handle trailing-backslash warnings for both sets#13488

Open
EffortlessSteven wants to merge 1 commit into
uutils:mainfrom
EffortlessSteven:fix/tr-trailing-backslash-warning
Open

tr: handle trailing-backslash warnings for both sets#13488
EffortlessSteven wants to merge 1 commit into
uutils:mainfrom
EffortlessSteven:fix/tr-trailing-backslash-warning

Conversation

@EffortlessSteven

@EffortlessSteven EffortlessSteven commented Jul 21, 2026

Copy link
Copy Markdown

What this does

tr checks only SET1 for an unescaped trailing backslash, so tr . '\' does not emit GNU's portability warning for SET2. Those warning paths discard stderr write errors, so tr . '\' 2>/dev/full exits 0 where GNU exits 1.

Fix: check each operand at parse completion, so both sets warn in operand order. Parse both operands before semantic validation so SET2 diagnostics precede a SET1 semantic error. A checked writer records exit status 1 when a warning cannot be written and keeps translating, preserving stdout. That writer remains local to tr; shared uucore diagnostic macros are unchanged, and warning text is localized through Fluent.

Fixes #12961.

Verification

Check Result
SET2 trailing backslash unpatched silent, patched warns
warning write to /dev/full unpatched exit 0, patched exit 1, stdout preserved
operand coverage both trailing-backslash operands warn twice in order; even run '\\' stays silent
SET1 syntax error plus SET2 trailing backslash SET1 error only, SET2 never parsed
tr '[x*]' 'z-a' ordering unpatched reports the SET1 [c*] error, patched reports GNU's SET2 range error
GNU 9.11 black box, 20 cases exit status, stdout bytes, diagnostic order match
GNU tr.pl, tr-case-class.sh, BusyBox tr pass, pass, 10 passed
test_tr passes on Linux, Windows, and WASI
gates (clippy -D warnings, fmt, cspell, MSRV 1.88) clean

Review map

  • Sequence::from_str: warns after scanning the whole operand, so the warning follows parser warnings and precedes a syntax error from that operand.
  • Sequence::solve_set_characters: parses SET1 then SET2 before any semantic check, preserving SET1 syntax precedence.
  • emit_diagnostic: writes each localized diagnostic once, recording status 1 on write failure so translation can continue.
  • tr.rs uumain: drops the SET1-only pre-scan and its show!(USimpleError::new(0, ...)).
  • tests/by-util/test_tr.rs: operand coverage, ordering, and /dev/full failures with stdout preserved.

Comment thread src/uu/tr/src/operation.rs Outdated
Comment on lines +42 to +44
fn emit_warning(message: impl Display) {
emit_diagnostic(format_args!("warning: {message}"));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think this function is useful

just call
emit_diagnostic(format_args!("warning: {message}"));
directly instead

(please update your agents.md configuration to avoid this in the future btw ;)

also, it needs to use translate!() to allow translation of warnings

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the forwarding helper and moved the complete warning text into Fluent, so the call sites now use emit_diagnostic(translate!(...)) directly.

This changes two existing Fluent source messages from warning bodies into complete warnings, so their external translations will need refreshing. I followed the complete-warning pattern used by sync.

Comment thread src/uu/tr/src/operation.rs Outdated
);
} else {
show_warning!("{}", translate!("tr-warning-invalid-utf8"));
emit_warning(translate!("tr-warning-invalid-utf8"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, clearly not a useful function :)

Comment thread tests/by-util/test_tr.rs Outdated

#[cfg(target_os = "linux")]
#[test]
fn test_ambiguous_octal_warning_write_failure() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge this one with the test above
(probably with a loop with the values/expectations)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Merged the two warning-write cases into one table-driven test.

tr checked only SET1 for an unescaped trailing backslash, so SET2 never
produced GNU's portability warning. Warning writes also went through
helpers that discard stderr errors, so redirecting a warning to /dev/full
still exited successfully.

Move terminal-backslash detection from the SET1-only pre-scan in tr.rs to
operand parse completion, so SET1 and SET2 are checked in parse order and
the warning follows any warning emitted while scanning the same operand.
Parse both operands before applying semantic validation, so SET2 warnings
and syntax errors surface ahead of a semantic rejection of a
syntactically valid SET1.

Add a tr-local checked diagnostic writer that records exit status 1 when
a warning cannot be written, without returning early, so stdin processing
and stdout output continue. Route tr's existing ambiguous-octal and
invalid-UTF-8 warnings through the same writer. The shared uucore
diagnostic macros are unchanged.

Move the localized "warning: " label into the tr-warning-* Fluent
messages so the label and its locale-specific punctuation are translated
rather than hardcoded in Rust. This changes two existing message keys
from warning bodies into complete warnings.
@EffortlessSteven
EffortlessSteven force-pushed the fix/tr-trailing-backslash-warning branch from ba373e5 to f4c2699 Compare July 21, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tr: echo 1|tr . \\ lacks warning

2 participants