File 0010-govis-modernise-errors.patch of Package go-mtree
From fbada2e08182412355ae32a0577c873057b5b305 Mon Sep 17 00:00:00 2001
From: Aleksa Sarai <cyphar@cyphar.com>
Date: Sun, 21 Sep 2025 01:38:26 +1000
Subject: [PATCH 10/25] govis: modernise errors
This code was written before %w was added to Go, and there were a fair
few mistakes in the copy-pasted error code.
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
---
pkg/govis/govis.go | 21 +++++++++++++
pkg/govis/govis_test.go | 18 +++++++++++
pkg/govis/unvis.go | 67 ++++++++++++++++++++---------------------
pkg/govis/vis.go | 41 +++++++++++--------------
4 files changed, 89 insertions(+), 58 deletions(-)
diff --git a/pkg/govis/govis.go b/pkg/govis/govis.go
index 003b182b7625..4200af2418ce 100644
--- a/pkg/govis/govis.go
+++ b/pkg/govis/govis.go
@@ -19,6 +19,8 @@
package govis
import (
+ "errors"
+ "fmt"
"strconv"
"strings"
)
@@ -44,6 +46,25 @@ const (
VisWhite VisFlag = (VisSpace | VisTab | VisNewline)
)
+// errUnknownVisFlagsError is a special value that lets you use [errors.Is]
+// with [unknownVisFlagsError]. Don't actually return this value, use
+// [unknownVisFlagsError] instead!
+var errUnknownVisFlagsError = errors.New("unknown or unsupported vis flags")
+
+// unknownVisFlagsError represents an error caused by unknown [VisFlag]s being
+// passed to [Vis] or [Unvis].
+type unknownVisFlagsError struct {
+ flags VisFlag
+}
+
+func (err unknownVisFlagsError) Is(target error) bool {
+ return target == errUnknownVisFlagsError
+}
+
+func (err unknownVisFlagsError) Error() string {
+ return fmt.Sprintf("%s contains unknown or unsupported flags %s", err.flags, err.flags&^visMask)
+}
+
// String pretty-prints VisFlag.
func (vflags VisFlag) String() string {
flagNames := []struct {
diff --git a/pkg/govis/govis_test.go b/pkg/govis/govis_test.go
index 50270308c38d..e3c9245ac1c9 100644
--- a/pkg/govis/govis_test.go
+++ b/pkg/govis/govis_test.go
@@ -215,3 +215,21 @@ func TestVisFlagsString(t *testing.T) {
})
}
}
+
+func TestUnknownVisFlagsError(t *testing.T) {
+ t.Run("Vis", func(t *testing.T) {
+ enc, err := Vis("dummy text", visMask+1)
+ require.Error(t, err, "Vis with invalid flags should fail")
+ assert.ErrorIs(t, err, errUnknownVisFlagsError, "Vis with invalid flags")
+ assert.ErrorContains(t, err, "contains unknown or unsupported flags", "Vis with invalid flags")
+ assert.Equal(t, "", enc, "error Vis should return empty string")
+ })
+
+ t.Run("Unvis", func(t *testing.T) {
+ dec, err := Unvis("dummy text", visMask+1)
+ require.Error(t, err, "Unvis with invalid flags should fail")
+ assert.ErrorIs(t, err, errUnknownVisFlagsError, "Unvis with invalid flags")
+ assert.ErrorContains(t, err, "contains unknown or unsupported flags", "Vis with invalid flags")
+ assert.Equal(t, "", dec, "error Unvis should return empty string")
+ })
+}
diff --git a/pkg/govis/unvis.go b/pkg/govis/unvis.go
index ddf77253a484..5dbf9356fcc7 100644
--- a/pkg/govis/unvis.go
+++ b/pkg/govis/unvis.go
@@ -19,11 +19,19 @@
package govis
import (
+ "errors"
"fmt"
"strconv"
"unicode"
)
+var (
+ errEndOfString = errors.New("unexpectedly reached end of string")
+ errUnknownEscapeChar = errors.New("unknown escape character")
+ errOutsideLatin1 = errors.New("outside latin-1 encoding")
+ errParseDigit = errors.New("could not parse digit")
+)
+
// unvisParser stores the current state of the token parser.
type unvisParser struct {
tokens []rune
@@ -39,7 +47,7 @@ func (p *unvisParser) Next() {
// Peek gets the current token.
func (p *unvisParser) Peek() (rune, error) {
if p.idx >= len(p.tokens) {
- return unicode.ReplacementChar, fmt.Errorf("tried to read past end of token list")
+ return unicode.ReplacementChar, errEndOfString
}
return p.tokens[p.idx], nil
}
@@ -75,23 +83,16 @@ func newParser(input string, flag VisFlag) *unvisParser {
func unvisPlainRune(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
- return nil, fmt.Errorf("plain rune: %c", ch)
+ return nil, fmt.Errorf("plain rune: %w", err)
}
p.Next()
-
- // XXX: Maybe we should not be converting to runes and then back to strings
- // here. Are we sure that the byte-for-byte representation is the
- // same? If the bytes change, then using these strings for paths will
- // break...
-
- str := string(ch)
- return []byte(str), nil
+ return []byte(string(ch)), nil
}
func unvisEscapeCStyle(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
- return nil, fmt.Errorf("escape hex: %s", err)
+ return nil, fmt.Errorf("escape cstyle: %w", err)
}
output := ""
@@ -120,7 +121,7 @@ func unvisEscapeCStyle(p *unvisParser) ([]byte, error) {
// Hidden marker.
default:
// XXX: We should probably allow falling through and return "\" here...
- return nil, fmt.Errorf("escape cstyle: unknown escape character: %q", ch)
+ return nil, fmt.Errorf("escape cstyle: %w %q", errUnknownEscapeChar, ch)
}
p.Next()
@@ -136,7 +137,7 @@ func unvisEscapeDigits(p *unvisParser, base int, force bool) ([]byte, error) {
if !force && i != 0xFF {
break
}
- return nil, fmt.Errorf("escape base %d: %s", base, err)
+ return nil, fmt.Errorf("escape base %d: %w", base, err)
}
digit, err := strconv.ParseInt(string(ch), base, 8)
@@ -144,7 +145,7 @@ func unvisEscapeDigits(p *unvisParser, base int, force bool) ([]byte, error) {
if !force && i != 0xFF {
break
}
- return nil, fmt.Errorf("escape base %d: could not parse digit: %s", base, err)
+ return nil, fmt.Errorf("escape base %d: %w %q: %w", base, errParseDigit, ch, err)
}
code = (code * base) + int(digit)
@@ -152,7 +153,7 @@ func unvisEscapeDigits(p *unvisParser, base int, force bool) ([]byte, error) {
}
if code > unicode.MaxLatin1 {
- return nil, fmt.Errorf("escape base %d: code %q outside latin-1 encoding", base, code)
+ return nil, fmt.Errorf("escape base %d: code %+.2x %w", base, code, errOutsideLatin1)
}
char := byte(code & 0xFF)
@@ -162,10 +163,10 @@ func unvisEscapeDigits(p *unvisParser, base int, force bool) ([]byte, error) {
func unvisEscapeCtrl(p *unvisParser, mask byte) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
- return nil, fmt.Errorf("escape ctrl: %s", err)
+ return nil, fmt.Errorf("escape ctrl: %w", err)
}
if ch > unicode.MaxLatin1 {
- return nil, fmt.Errorf("escape ctrl: code %q outside latin-1 encoding", ch)
+ return nil, fmt.Errorf("escape ctrl: code %q %w", ch, errOutsideLatin1)
}
char := byte(ch) & 0x1f
@@ -180,7 +181,7 @@ func unvisEscapeCtrl(p *unvisParser, mask byte) ([]byte, error) {
func unvisEscapeMeta(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
- return nil, fmt.Errorf("escape meta: %s", err)
+ return nil, fmt.Errorf("escape meta: %w", err)
}
mask := byte(0x80)
@@ -196,10 +197,10 @@ func unvisEscapeMeta(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
- return nil, fmt.Errorf("escape meta1: %s", err)
+ return nil, fmt.Errorf("escape meta1: %w", err)
}
if ch > unicode.MaxLatin1 {
- return nil, fmt.Errorf("escape meta1: code %q outside latin-1 encoding", ch)
+ return nil, fmt.Errorf("escape meta1: code %q %w", ch, errOutsideLatin1)
}
// Add mask to character.
@@ -207,13 +208,13 @@ func unvisEscapeMeta(p *unvisParser) ([]byte, error) {
return []byte{mask | byte(ch)}, nil
}
- return nil, fmt.Errorf("escape meta: unknown escape char: %s", err)
+ return nil, fmt.Errorf("escape meta: %w %q", errUnknownEscapeChar, ch)
}
func unvisEscapeSequence(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
- return nil, fmt.Errorf("escape sequence: %s", err)
+ return nil, fmt.Errorf("escape sequence: %w", err)
}
switch ch {
@@ -244,7 +245,7 @@ func unvisEscapeSequence(p *unvisParser) ([]byte, error) {
func unvisRune(p *unvisParser) ([]byte, error) {
ch, err := p.Peek()
if err != nil {
- return nil, fmt.Errorf("rune: %s", err)
+ return nil, err
}
switch ch {
@@ -258,11 +259,8 @@ func unvisRune(p *unvisParser) ([]byte, error) {
p.Next()
return unvisEscapeDigits(p, 16, true)
}
- fallthrough
-
- default:
- return unvisPlainRune(p)
}
+ return unvisPlainRune(p)
}
func unvis(p *unvisParser) (string, error) {
@@ -270,7 +268,7 @@ func unvis(p *unvisParser) (string, error) {
for !p.End() {
ch, err := unvisRune(p)
if err != nil {
- return "", fmt.Errorf("input: %s", err)
+ return "", err
}
output = append(output, ch...)
}
@@ -281,15 +279,14 @@ func unvis(p *unvisParser) (string, error) {
// VisHTTPStyle flag is checked) and output the un-encoded version of the
// encoded string. An error is returned if any escape sequences in the input
// string were invalid.
-func Unvis(input string, flag VisFlag) (string, error) {
- // TODO: Check all of the VisFlag bits.
- p := newParser(input, flag)
+func Unvis(input string, flags VisFlag) (string, error) {
+ if unknown := flags &^ visMask; unknown != 0 {
+ return "", unknownVisFlagsError{flags: flags}
+ }
+ p := newParser(input, flags)
output, err := unvis(p)
if err != nil {
- return "", fmt.Errorf("unvis: %s", err)
- }
- if !p.End() {
- return "", fmt.Errorf("unvis: trailing characters at end of input")
+ return "", fmt.Errorf("unvis '%s': %w", input, err)
}
return output, nil
}
diff --git a/pkg/govis/vis.go b/pkg/govis/vis.go
index 1af15eb0fa1a..620b49a1db5b 100644
--- a/pkg/govis/vis.go
+++ b/pkg/govis/vis.go
@@ -59,14 +59,14 @@ func isgraph(ch rune) bool {
// the plus side this is actually a benefit on the encoding side (it will
// always work with the simple unvis(3) implementation). It also means that we
// don't have to worry about different multi-byte encodings.
-func vis(b byte, flag VisFlag) (string, error) {
+func vis(b byte, flag VisFlag) string {
// Treat the single-byte character as a rune.
ch := rune(b)
// XXX: This is quite a horrible thing to support.
if flag&VisHTTPStyle == VisHTTPStyle {
if !ishttp(ch) {
- return "%" + fmt.Sprintf("%.2X", ch), nil
+ return "%" + fmt.Sprintf("%.2X", ch)
}
}
@@ -90,31 +90,31 @@ func vis(b byte, flag VisFlag) (string, error) {
if ch == '\\' && flag&VisNoSlash == 0 {
encoded += "\\"
}
- return encoded, nil
+ return encoded
}
// Try to use C-style escapes first.
if flag&VisCStyle == VisCStyle {
switch ch {
case ' ':
- return "\\s", nil
+ return "\\s"
case '\n':
- return "\\n", nil
+ return "\\n"
case '\r':
- return "\\r", nil
+ return "\\r"
case '\b':
- return "\\b", nil
+ return "\\b"
case '\a':
- return "\\a", nil
+ return "\\a"
case '\v':
- return "\\v", nil
+ return "\\v"
case '\t':
- return "\\t", nil
+ return "\\t"
case '\f':
- return "\\f", nil
+ return "\\f"
case '\x00':
// Output octal just to be safe.
- return "\\000", nil
+ return "\\000"
}
}
@@ -123,7 +123,7 @@ func vis(b byte, flag VisFlag) (string, error) {
// encoded as octal.
if flag&VisOctal == VisOctal || isgraph(ch) || ch&0x7f == ' ' {
// Always output three-character octal just to be safe.
- return fmt.Sprintf("\\%.3o", ch), nil
+ return fmt.Sprintf("\\%.3o", ch)
}
// Now we have to output meta or ctrl escapes. As far as I can tell, this
@@ -154,25 +154,20 @@ func vis(b byte, flag VisFlag) (string, error) {
encoded += fmt.Sprintf("-%c", b)
}
- return encoded, nil
+ return encoded
}
// Vis encodes the provided string to a BSD-compatible encoding using BSD's
// vis() flags. However, it will correctly handle multi-byte encoding (which is
// not done properly by BSD's vis implementation).
-func Vis(src string, flag VisFlag) (string, error) {
- if flag&visMask != flag {
- return "", fmt.Errorf("vis: flag %q contains unknown or unsupported flags", flag)
+func Vis(src string, flags VisFlag) (string, error) {
+ if unknown := flags &^ visMask; unknown != 0 {
+ return "", unknownVisFlagsError{flags: flags}
}
output := ""
for _, ch := range []byte(src) {
- encodedCh, err := vis(ch, flag)
- if err != nil {
- return "", err
- }
- output += encodedCh
+ output += vis(ch, flags)
}
-
return output, nil
}
--
2.51.0