all: apply modernisers
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 3m53s
Test / Hakurei (push) Successful in 4m0s
Test / Sandbox (race detector) (push) Successful in 5m37s
Test / Hakurei (race detector) (push) Successful in 6m40s
Test / Flake checks (push) Successful in 1m12s

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-06-08 14:19:11 +09:00
parent 725f2e0ef3
commit f869ff95a1
35 changed files with 146 additions and 141 deletions
+2 -2
View File
@@ -20,8 +20,8 @@ func (e AbsoluteError) Error() string {
} }
func (e AbsoluteError) Is(target error) bool { func (e AbsoluteError) Is(target error) bool {
var ce AbsoluteError ce, ok := errors.AsType[AbsoluteError](target)
if !errors.As(target, &ce) { if !ok {
return errors.Is(target, syscall.EINVAL) return errors.Is(target, syscall.EINVAL)
} }
return e == ce return e == ce
+2 -2
View File
@@ -57,8 +57,8 @@ func dispatchModprobe(
continue continue
} }
var exitError *exec.ExitError exitError, ok := errors.AsType[*exec.ExitError](err)
if !errors.As(err, &exitError) || exitError == nil { if !ok || exitError == nil {
r.Dispatch(report.Degraded, "invoke modprobe", err) r.Dispatch(report.Degraded, "invoke modprobe", err)
continue continue
} }
+6 -5
View File
@@ -7,7 +7,8 @@ import (
"strconv" "strconv"
) )
// decodeJSON decodes json from r and stores it in v. A non-nil error results in a call to fatal. // decodeJSON decodes json from r and stores it in v. A non-nil error results in
// a call to fatal.
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) { func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) {
err := json.NewDecoder(r).Decode(v) err := json.NewDecoder(r).Decode(v)
if err == nil { if err == nil {
@@ -47,14 +48,14 @@ func encodeJSON(fatal func(v ...any), output io.Writer, short bool, v any) {
} }
if err := encoder.Encode(v); err != nil { if err := encoder.Encode(v); err != nil {
var marshalerError *json.MarshalerError if e, ok := errors.AsType[*json.MarshalerError](err); ok && e != nil {
if errors.As(err, &marshalerError) && marshalerError != nil {
// this likely indicates an implementation error in hst // this likely indicates an implementation error in hst
fatal("cannot encode json for " + marshalerError.Type.String() + ": " + marshalerError.Err.Error()) fatal("cannot encode json for " + e.Type.String() + ": " + e.Err.Error())
return return
} }
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does not need to be handled // UnsupportedTypeError, UnsupportedValueError: incorrect usage, does
// not need to be handled
fatal("cannot write json: " + err.Error()) fatal("cannot write json: " + err.Error())
} }
} }
+1 -1
View File
@@ -74,7 +74,7 @@ func (s *searchCache) clean() {
} }
func indexsum(in [][]int) int { func indexsum(in [][]int) int {
sum := 0 sum := 0
for i := 0; i < len(in); i++ { for i := range in {
sum += in[i][1] - in[i][0] sum += in[i][1] - in[i][0]
} }
return sum return sum
+2 -2
View File
@@ -508,8 +508,8 @@ func _main(s ...string) (exitCode int) {
if !z.AllowOrphan { if !z.AllowOrphan {
if err := z.Wait(); err != nil { if err := z.Wait(); err != nil {
var exitError *exec.ExitError exitError, ok := errors.AsType[*exec.ExitError](err)
if !errors.As(err, &exitError) || exitError == nil { if !ok || exitError == nil {
log.Println(err) log.Println(err)
return 5 return 5
} }
+2 -2
View File
@@ -91,8 +91,8 @@ func (n *node) MustParse(arguments []string, handleError func(error)) {
case ErrEmptyTree: case ErrEmptyTree:
os.Exit(1) os.Exit(1)
default: default:
var flagError FlagError flagError, ok := errors.AsType[FlagError](err)
if !errors.As(err, &flagError) { // returned by HandlerFunc if !ok { // returned by HandlerFunc
handleError(err) handleError(err)
os.Exit(1) os.Exit(1)
} }
+2 -5
View File
@@ -154,11 +154,8 @@ func (e *StartError) Error() string {
return e.Step return e.Step
} }
{ if se, ok := errors.AsType[*os.SyscallError](e.Err); ok && se != nil {
var syscallError *os.SyscallError return e.Step + " " + se.Error()
if errors.As(e.Err, &syscallError) && syscallError != nil {
return e.Step + " " + syscallError.Error()
}
} }
return e.Step + ": " + e.Err.Error() return e.Step + ": " + e.Err.Error()
-2
View File
@@ -235,8 +235,6 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
}) })
} }
func sliceAddr[S any](s []S) *[]S { return &s }
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile { func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr} f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
// check happens in Close, and cleanup is not guaranteed to run, so relying // check happens in Close, and cleanup is not guaranteed to run, so relying
+6 -8
View File
@@ -46,9 +46,8 @@ func messageFromError(err error) (m string, ok bool) {
// While this is usable for pointer errors, such use should be avoided as nil // While this is usable for pointer errors, such use should be avoided as nil
// check is omitted. // check is omitted.
func messagePrefix[T error](prefix string, err error) (string, bool) { func messagePrefix[T error](prefix string, err error) (string, bool) {
var targetError T if e, ok := errors.AsType[T](err); ok {
if errors.As(err, &targetError) { return prefix + e.Error(), true
return prefix + targetError.Error(), true
} }
return zeroString, false return zeroString, false
} }
@@ -58,9 +57,8 @@ func messagePrefixP[V any, T interface {
*V *V
error error
}](prefix string, err error) (string, bool) { }](prefix string, err error) (string, bool) {
var targetError T if e, ok := errors.AsType[T](err); ok && e != nil {
if errors.As(err, &targetError) && targetError != nil { return prefix + e.Error(), true
return prefix + targetError.Error(), true
} }
return zeroString, false return zeroString, false
} }
@@ -109,8 +107,8 @@ func optionalErrorUnwrap(err error) error {
// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback. // errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) { func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
var errno syscall.Errno errno, ok := errors.AsType[syscall.Errno](err)
if !errors.As(err, &errno) { if !ok {
return 0, &os.PathError{Op: op, Path: path, Err: err} return 0, &os.PathError{Op: op, Path: path, Err: err}
} }
return errno, nil return errno, nil
+8 -8
View File
@@ -95,7 +95,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16, Uid: 1 << 16,
Gid: 1 << 15, Gid: 1 << 15,
Hostname: "hakurei-check", Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))), Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0), SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict, SeccompPresets: std.PresetStrict,
RetainSession: true, RetainSession: true,
@@ -123,7 +123,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16, Uid: 1 << 16,
Gid: 1 << 15, Gid: 1 << 15,
Hostname: "hakurei-check", Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))), Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0), SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict, SeccompPresets: std.PresetStrict,
RetainSession: true, RetainSession: true,
@@ -152,7 +152,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16, Uid: 1 << 16,
Gid: 1 << 15, Gid: 1 << 15,
Hostname: "hakurei-check", Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))), Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0), SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict, SeccompPresets: std.PresetStrict,
RetainSession: true, RetainSession: true,
@@ -182,7 +182,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16, Uid: 1 << 16,
Gid: 1 << 15, Gid: 1 << 15,
Hostname: "hakurei-check", Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))), Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0), SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict, SeccompPresets: std.PresetStrict,
RetainSession: true, RetainSession: true,
@@ -213,7 +213,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16, Uid: 1 << 16,
Gid: 1 << 15, Gid: 1 << 15,
Hostname: "hakurei-check", Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))), Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0), SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict, SeccompPresets: std.PresetStrict,
RetainSession: true, RetainSession: true,
@@ -245,7 +245,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16, Uid: 1 << 16,
Gid: 1 << 15, Gid: 1 << 15,
Hostname: "hakurei-check", Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))), Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0), SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict, SeccompPresets: std.PresetStrict,
RetainSession: true, RetainSession: true,
@@ -279,7 +279,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16, Uid: 1 << 16,
Gid: 1 << 15, Gid: 1 << 15,
Hostname: "hakurei-check", Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))), Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0), SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict, SeccompPresets: std.PresetStrict,
RetainSession: true, RetainSession: true,
@@ -315,7 +315,7 @@ func TestInitEntrypoint(t *testing.T) {
Uid: 1 << 16, Uid: 1 << 16,
Gid: 1 << 15, Gid: 1 << 15,
Hostname: "hakurei-check", Hostname: "hakurei-check",
Ops: (*Ops)(sliceAddr(make(Ops, 1))), Ops: new(make(Ops, 1)),
SeccompRules: make([]std.NativeRule, 0), SeccompRules: make([]std.NativeRule, 0),
SeccompPresets: std.PresetStrict, SeccompPresets: std.PresetStrict,
RetainSession: true, RetainSession: true,
+1 -1
View File
@@ -39,7 +39,7 @@ func TestSyscall(t *testing.T) {
t.Errorf("Unmarshal: %v, want %v", got, tc.want) t.Errorf("Unmarshal: %v, want %v", got, tc.want)
} }
}) })
if errors.As(tc.err, new(ext.SyscallNameError)) { if _, ok := errors.AsType[ext.SyscallNameError](tc.err); ok {
return return
} }
+7 -6
View File
@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"reflect" "reflect"
"strings"
"hakurei.app/check" "hakurei.app/check"
) )
@@ -78,17 +79,17 @@ type FSImplError struct{ Value FilesystemConfig }
func (f FSImplError) Error() string { func (f FSImplError) Error() string {
implType := reflect.TypeOf(f.Value) implType := reflect.TypeOf(f.Value)
var name string var buf strings.Builder
for implType != nil && implType.Kind() == reflect.Ptr { for implType != nil && implType.Kind() == reflect.Pointer {
name += "*" buf.WriteByte('*')
implType = implType.Elem() implType = implType.Elem()
} }
if implType != nil { if implType != nil {
name += implType.Name() buf.WriteString(implType.Name())
} else { } else {
name += "nil" buf.WriteString("nil")
} }
return fmt.Sprintf("implementation %s not supported", name) return "implementation " + buf.String() + " not supported"
} }
// FilesystemConfigJSON is the [json] adapter for [FilesystemConfig]. // FilesystemConfigJSON is the [json] adapter for [FilesystemConfig].
+2 -2
View File
@@ -103,7 +103,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
t.Run("marshal", func(t *testing.T) { t.Run("marshal", func(t *testing.T) {
t.Parallel() t.Parallel()
wantErr := tc.wantErr wantErr := tc.wantErr
if errors.As(wantErr, new(hst.FSTypeError)) { if _, ok := errors.AsType[hst.FSTypeError](wantErr); ok {
// for unsupported implementation tc // for unsupported implementation tc
wantErr = hst.FSImplError{Value: stubFS{"cat"}} wantErr = hst.FSImplError{Value: stubFS{"cat"}}
} }
@@ -139,7 +139,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
t.Run("unmarshal", func(t *testing.T) { t.Run("unmarshal", func(t *testing.T) {
t.Parallel() t.Parallel()
if tc.data == "\x00" && tc.sData == "\x00" { if tc.data == "\x00" && tc.sData == "\x00" {
if errors.As(tc.wantErr, new(hst.FSImplError)) { if _, ok := errors.AsType[hst.FSImplError](tc.wantErr); ok {
// this error is only returned on marshal // this error is only returned on marshal
return return
} }
+1 -6
View File
@@ -43,18 +43,13 @@ func (e *FSEphemeral) Apply(z *ApplyState) {
return return
} }
size := e.Size
if size < 0 {
size = 0
}
perm := e.Perm perm := e.Perm
if perm == 0 { if perm == 0 {
perm = fsEphemeralDefaultPerm perm = fsEphemeralDefaultPerm
} }
if e.Write { if e.Write {
z.Tmpfs(e.Target, size, perm) z.Tmpfs(e.Target, max(e.Size, 0), perm)
} else { } else {
z.Readonly(e.Target, perm) z.Readonly(e.Target, perm)
} }
+2 -2
View File
@@ -80,7 +80,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
continue continue
} }
if ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\* if found := bytes.Contains([]byte("-_/.\\*"), []byte{b}); found { // - // _/.\*
goto opt goto opt
} else if b >= '0' && b <= '9' { // 0-9 } else if b >= '0' && b <= '9' { // 0-9
goto opt goto opt
@@ -101,7 +101,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
break break
} }
if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil { if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
if errors.As(err, new(hex.InvalidByteError)) { if _, ok := errors.AsType[hex.InvalidByteError](err); ok {
errno = ErrBadValHexByte errno = ErrBadValHexByte
break break
} }
+1 -1
View File
@@ -40,7 +40,7 @@ func TestTransform(t *testing.T) {
const maxChunkWords = 8 << 10 const maxChunkWords = 8 << 10
buf := make([]byte, 2*maxChunkWords*8) buf := make([]byte, 2*maxChunkWords*8)
for i := uint64(0); i < 2*maxChunkWords; i++ { for i := range uint64(2 * maxChunkWords) {
binary.LittleEndian.PutUint64(buf[i*8:], i) binary.LittleEndian.PutUint64(buf[i*8:], i)
} }
if err := lockedfile.Write(path, bytes.NewReader(buf[:8]), 0666); err != nil { if err := lockedfile.Write(path, bytes.NewReader(buf[:8]), 0666); err != nil {
+1 -2
View File
@@ -58,8 +58,7 @@ func (k *outcome) finalise(
supp := make([]string, len(config.Groups)) supp := make([]string, len(config.Groups))
for i, name := range config.Groups { for i, name := range config.Groups {
if gid, err := k.lookupGroupId(name); err != nil { if gid, err := k.lookupGroupId(name); err != nil {
var unknownGroupError user.UnknownGroupError if unknownGroupError, ok := errors.AsType[user.UnknownGroupError](err); ok {
if errors.As(err, &unknownGroupError) {
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError) return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
} else { } else {
return &hst.AppError{Step: "look up group by name", Err: err, Msg: err.Error()} return &hst.AppError{Step: "look up group by name", Err: err, Msg: err.Error()}
+4 -6
View File
@@ -51,18 +51,16 @@ func (h *Hsu) ID() (int, error) {
cmd.Stderr = os.Stderr // pass through fatal messages cmd.Stderr = os.Stderr // pass through fatal messages
cmd.Env = make([]string, 0) cmd.Env = make([]string, 0)
cmd.Dir = fhs.Root cmd.Dir = fhs.Root
var ( var p []byte
p []byte
exitError *exec.ExitError
)
const step = "obtain uid from hsu" const step = "obtain uid from hsu"
if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil { if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
h.id, h.idErr = strconv.Atoi(string(p)) h.id, h.idErr = strconv.Atoi(string(p))
if h.idErr != nil { if h.idErr != nil {
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"} h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
} }
} else if errors.As(h.idErr, &exitError) && exitError != nil && exitError.ExitCode() == 1 { } else if exitError, ok := errors.AsType[*exec.ExitError](h.idErr); ok &&
exitError != nil &&
exitError.ExitCode() == 1 {
// hsu prints an error message in this case // hsu prints an error message in this case
h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess} h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
} else if errors.Is(h.idErr, os.ErrNotExist) { } else if errors.Is(h.idErr, os.ErrNotExist) {
+3 -3
View File
@@ -328,11 +328,11 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
} }
if err := k.sys.Revert((*system.Criteria)(&ec)); err != nil { if err := k.sys.Revert((*system.Criteria)(&ec)); err != nil {
var joinError interface { joinError, ok := errors.AsType[interface {
Unwrap() []error Unwrap() []error
error error
} }](err)
if !errors.As(err, &joinError) || joinError == nil { if !ok || joinError == nil {
perror(err, "revert system setup") perror(err, "revert system setup")
} else { } else {
for _, v := range joinError.Unwrap() { for _, v := range joinError.Unwrap() {
+2 -2
View File
@@ -390,8 +390,8 @@ func shimEntrypoint(k syscallDispatcher) {
if err := k.containerWait(z); err != nil { if err := k.containerWait(z); err != nil {
sp.destroy() sp.destroy()
var exitError *exec.ExitError exitError, ok := errors.AsType[*exec.ExitError](err)
if !errors.As(err, &exitError) { if !ok {
if errors.Is(err, context.Canceled) { if errors.Is(err, context.Canceled) {
k.exit(hst.ExitCancel) k.exit(hst.ExitCancel)
} }
+4 -4
View File
@@ -176,8 +176,8 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
case reflect.Struct: case reflect.Struct:
data = SPA_TYPE_Struct.append(data) data = SPA_TYPE_Struct.append(data)
var err error var err error
for i := 0; i < v.NumField(); i++ { for _, field := range v.Fields() {
data, err = marshalValueAppend(data, v.Field(i)) data, err = marshalValueAppend(data, field)
if err != nil { if err != nil {
return data, err return data, err
} }
@@ -370,8 +370,8 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
} }
var fieldWireSize Word var fieldWireSize Word
for i := 0; i < v.NumField(); i++ { for _, field := range v.Fields() {
if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil { if err := unmarshalValue(data, field, &fieldWireSize); err != nil {
return err return err
} }
// bounds check completed in successful call to unmarshalValue // bounds check completed in successful call to unmarshalValue
+21 -15
View File
@@ -238,8 +238,8 @@ func (t *TContext) destroy(errP *error) {
if chmodErr != nil || removeErr != nil { if chmodErr != nil || removeErr != nil {
*errP = errors.Join(*errP, chmodErr, removeErr) *errP = errors.Join(*errP, chmodErr, removeErr)
} else if errors.Is(*errP, os.ErrExist) { } else if errors.Is(*errP, os.ErrExist) {
var linkError *os.LinkError if linkError, ok := errors.AsType[*os.LinkError](*errP); ok &&
if errors.As(*errP, &linkError) && linkError != nil && linkError != nil &&
linkError.Op == "rename" { linkError.Op == "rename" {
// two artifacts may be backed by the same file // two artifacts may be backed by the same file
*errP = nil *errP = nil
@@ -974,36 +974,42 @@ func (e *ScrubError) Unwrap() []error {
// Error returns a multi-line representation of [ScrubError]. // Error returns a multi-line representation of [ScrubError].
func (e *ScrubError) Error() string { func (e *ScrubError) Error() string {
var segments []string var segments []string
var buf strings.Builder
if len(e.ChecksumMismatches) > 0 { if len(e.ChecksumMismatches) > 0 {
s := "checksum mismatches:\n" buf.Reset()
buf.WriteString("checksum mismatches:\n")
for _, m := range e.ChecksumMismatches { for _, m := range e.ChecksumMismatches {
s += m.Error() + "\n" buf.WriteString(m.Error() + "\n")
} }
segments = append(segments, s) segments = append(segments, buf.String())
} }
if len(e.DanglingIdentifiers) > 0 { if len(e.DanglingIdentifiers) > 0 {
s := "dangling identifiers:\n" buf.Reset()
buf.WriteString("dangling identifiers:\n")
for _, id := range e.DanglingIdentifiers { for _, id := range e.DanglingIdentifiers {
s += Encode(id) + "\n" buf.WriteString(Encode(id) + "\n")
} }
segments = append(segments, s) segments = append(segments, buf.String())
} }
if len(e.DanglingStatus) > 0 { if len(e.DanglingStatus) > 0 {
s := "dangling status:\n" buf.Reset()
buf.WriteString("dangling status:\n")
for _, id := range e.DanglingStatus { for _, id := range e.DanglingStatus {
s += Encode(id) + "\n" buf.WriteString(Encode(id) + "\n")
} }
segments = append(segments, s) segments = append(segments, buf.String())
} }
if len(e.Errs) > 0 { if len(e.Errs) > 0 {
s := "errors during scrub:\n" buf.Reset()
buf.WriteString("errors during scrub:\n")
for pathname, errs := range e.errs { for pathname, errs := range e.errs {
s += " " + pathname.Value() + ":\n" buf.WriteString(" " + pathname.Value() + ":\n")
for _, err := range errs { for _, err := range errs {
s += " " + err.Error() + "\n" buf.WriteString(" " + err.Error() + "\n")
} }
} }
segments = append(segments, s) segments = append(segments, buf.String())
} }
return strings.Join(segments, "\n") return strings.Join(segments, "\n")
} }
+1 -2
View File
@@ -102,8 +102,7 @@ func (e Error) MarshalJSON() (data []byte, err error) {
v.Severity = e.Severity v.Severity = e.Severity
} }
var re RepresentableError if re, ok := errors.AsType[RepresentableError](e.Err); ok {
if errors.As(e.Err, &re) {
v.Err, err = json.Marshal(re) v.Err, err = json.Marshal(re)
} else { } else {
v.Err, err = json.Marshal(e.Err.Error()) v.Err, err = json.Marshal(e.Err.Error())
+6 -5
View File
@@ -6,6 +6,7 @@ import (
"maps" "maps"
"reflect" "reflect"
"slices" "slices"
"strings"
"unique" "unique"
) )
@@ -107,8 +108,8 @@ func (e TypeError) Error() string {
} }
func (e TypeError) Is(err error) bool { func (e TypeError) Is(err error) bool {
var v TypeError v, ok := errors.AsType[TypeError](err)
return errors.As(err, &v) && return ok &&
e.Asserted == v.Asserted && e.Asserted == v.Asserted &&
e.Concrete == v.Concrete e.Concrete == v.Concrete
} }
@@ -233,14 +234,14 @@ func evaluateAny(d PF, s []Frame, expr, rp any) bool {
return evaluateAny(d, s, e[0], rp) return evaluateAny(d, s, e[0], rp)
} }
} }
var v string var v strings.Builder
for i := range e { for i := range e {
var _r string var _r string
if evaluate(d, s, e[i], &_r) { if evaluate(d, s, e[i], &_r) {
v += _r v.WriteString(_r)
} }
} }
store(rp, v) store(rp, v.String())
return true return true
case Array: case Array:
+1 -1
View File
@@ -214,7 +214,7 @@ func TestEvaluate(t *testing.T) {
} }
var errEquals bool var errEquals bool
if errors.As(err, new(TypeError)) { if _, ok := errors.AsType[TypeError](err); ok {
errEquals = errors.Is(err, tc.err) errEquals = errors.Is(err, tc.err)
} else { } else {
errEquals = reflect.DeepEqual(err, tc.err) errEquals = reflect.DeepEqual(err, tc.err)
+2 -3
View File
@@ -212,11 +212,10 @@ func (r *Report) HandleAccess(errP *error) func() {
*errP = err *errP = err
} }
var runtimeError interface { if runtimeError, ok := errors.AsType[interface {
Addr() uintptr Addr() uintptr
runtime.Error runtime.Error
} }](*errP); ok {
if errors.As(*errP, &runtimeError) {
offset := int(runtimeError.Addr() - uintptr(unsafe.Pointer(unsafe.SliceData(r.data)))) offset := int(runtimeError.Addr() - uintptr(unsafe.Pointer(unsafe.SliceData(r.data))))
// best effort for fragile uintptr // best effort for fragile uintptr
if offset >= 0 { if offset >= 0 {
+1 -1
View File
@@ -1063,7 +1063,7 @@ func (ctx *evalContext) pf(
checksum, ok := kc.(string) checksum, ok := kc.(string)
if !ok { if !ok {
err = azalea.TypeError{ err = azalea.TypeError{
Concrete: reflect.TypeOf(checksum), Concrete: reflect.TypeOf(kc),
Asserted: reflect.TypeFor[string](), Asserted: reflect.TypeFor[string](),
} }
return return
+2 -2
View File
@@ -29,7 +29,7 @@ func TestStoreBigLock(t *testing.T) {
{ {
s := store.New(check.MustAbs(t.TempDir()).Append("state")) s := store.New(check.MustAbs(t.TempDir()).Append("state"))
for i := 0; i < 2; i++ { // check once behaviour for range 2 { // check once behaviour
if unlock, err := bigLock(s); err != nil { if unlock, err := bigLock(s); err != nil {
t.Fatalf("bigLock: error = %v", err) t.Fatalf("bigLock: error = %v", err)
} else { } else {
@@ -43,7 +43,7 @@ func TestStoreBigLock(t *testing.T) {
wantErr := &hst.AppError{Step: "create state store directory", wantErr := &hst.AppError{Step: "create state store directory",
Err: &os.PathError{Op: "mkdir", Path: "/proc/nonexistent", Err: syscall.ENOENT}} Err: &os.PathError{Op: "mkdir", Path: "/proc/nonexistent", Err: syscall.ENOENT}}
for i := 0; i < 2; i++ { // check once behaviour for range 2 { // check once behaviour
if _, err := bigLock(store.New(check.MustAbs("/proc/nonexistent"))); !reflect.DeepEqual(err, wantErr) { if _, err := bigLock(store.New(check.MustAbs("/proc/nonexistent"))); !reflect.DeepEqual(err, wantErr) {
t.Errorf("bigLock: error = %#v, want %#v", err, wantErr) t.Errorf("bigLock: error = %#v, want %#v", err, wantErr)
} }
+2 -5
View File
@@ -20,9 +20,6 @@ func (e UniqueError) Error() string {
} }
func (e UniqueError) Is(target error) bool { func (e UniqueError) Is(target error) bool {
var u UniqueError u, ok := errors.AsType[UniqueError](target)
if !errors.As(target, &u) { return ok && e == u
return false
}
return e == u
} }
+3 -3
View File
@@ -172,12 +172,12 @@ func (s *linePrefixWriter) write(p []byte, a int) (int, error) {
return a, syscall.ENOMEM return a, syscall.ENOMEM
} }
if i := bytes.IndexByte(p, '\n'); i == -1 { if before, after, ok := bytes.Cut(p, []byte{'\n'}); !ok {
n, _ := s.buf.Write(p) n, _ := s.buf.Write(p)
s.n += n s.n += n
return a + n, nil return a + n, nil
} else { } else {
n, _ := s.buf.Write(p[:i]) n, _ := s.buf.Write(before)
s.n += n + 1 s.n += n + 1
v := s.buf.String() v := s.buf.String()
@@ -190,7 +190,7 @@ func (s *linePrefixWriter) write(p []byte, a int) (int, error) {
} }
s.buf.Reset() s.buf.Reset()
return s.write(p[i+1:], a+n+1) return s.write(after, a+n+1)
} }
} }
+2 -4
View File
@@ -130,13 +130,11 @@ func (e *Error) Error() string {
return e.withPrefix("cannot connect to " + e.Host) return e.withPrefix("cannot connect to " + e.Host)
case RCleanup: case RCleanup:
var pathError *os.PathError if pathError, ok := errors.AsType[*os.PathError](e.Errno); ok && pathError != nil {
if errors.As(e.Errno, &pathError) && pathError != nil {
return pathError.Error() return pathError.Error()
} }
var errno syscall.Errno if errno, ok := errors.AsType[syscall.Errno](e.Errno); ok && errno != 0 {
if errors.As(e.Errno, &errno) && errno != 0 {
return "cannot close wayland close_fd pipe: " + errno.Error() return "cannot close wayland close_fd pipe: " + errno.Error()
} }
+2 -2
View File
@@ -20,8 +20,8 @@ func TestExec(t *testing.T) {
_, err := ldd.Resolve(t.Context(), nil, check.MustAbs("/proc/nonexistent")) _, err := ldd.Resolve(t.Context(), nil, check.MustAbs("/proc/nonexistent"))
var exitError *exec.ExitError exitError, ok := errors.AsType[*exec.ExitError](err)
if !errors.As(err, &exitError) { if !ok {
t.Fatalf("Exec: error has incorrect concrete type: %#v", err) t.Fatalf("Exec: error has incorrect concrete type: %#v", err)
} }
+35 -17
View File
@@ -1,4 +1,5 @@
// Package message provides interfaces and a base implementation for extended reporting on top of [log.Logger] // Package message provides interfaces and a base implementation for extended
// reporting on top of [log.Logger]
package message package message
import ( import (
@@ -15,10 +16,11 @@ type Error interface {
error error
} }
// GetMessage returns whether an error implements [Error], and the message if it does. // GetMessage returns whether an error implements [Error], and the message if
// it does.
func GetMessage(err error) (string, bool) { func GetMessage(err error) (string, bool) {
var e Error e, ok := errors.AsType[Error](err)
if !errors.As(err, &e) || e == nil { if !ok || e == nil {
return "", false return "", false
} }
return e.Message(), true return e.Message(), true
@@ -29,30 +31,43 @@ type Msg interface {
// GetLogger returns the address of the underlying [log.Logger]. // GetLogger returns the address of the underlying [log.Logger].
GetLogger() *log.Logger GetLogger() *log.Logger
// IsVerbose atomically loads and returns whether [Msg] has verbose logging enabled. // IsVerbose atomically loads and returns whether [Msg] has verbose logging
// enabled.
IsVerbose() bool IsVerbose() bool
// SwapVerbose atomically stores a new verbose state and returns the previous value held by [Msg]. // SwapVerbose atomically stores a new verbose state and returns the
// previous value held by [Msg].
SwapVerbose(verbose bool) bool SwapVerbose(verbose bool) bool
// Verbose passes its argument to the Println method of the underlying [log.Logger] if IsVerbose returns true. // Verbose passes its argument to the Println method of the underlying
// [log.Logger] if IsVerbose returns true.
Verbose(v ...any) Verbose(v ...any)
// Verbosef passes its argument to the Printf method of the underlying [log.Logger] if IsVerbose returns true. // Verbosef passes its argument to the Printf method of the underlying
// [log.Logger] if IsVerbose returns true.
Verbosef(format string, v ...any) Verbosef(format string, v ...any)
// Suspend causes the embedded [Suspendable] to withhold writes to its downstream [io.Writer]. // Suspend causes the embedded [Suspendable] to withhold writes to its
// Suspend returns false and is a noop if called between calls to Suspend and Resume. // downstream [io.Writer].
//
// Suspend returns false and is a noop if called between calls to Suspend
// and Resume.
Suspend() bool Suspend() bool
// Resume dumps the entire buffer held by the embedded [Suspendable] and stops withholding future writes. // Resume dumps the entire buffer held by the embedded [Suspendable] and
// stops withholding future writes.
//
// Resume returns false and is a noop if a call to Suspend does not precede it. // Resume returns false and is a noop if a call to Suspend does not precede it.
Resume() bool Resume() bool
// BeforeExit runs implementation-specific cleanup code, and optionally prints warnings. // BeforeExit runs implementation-specific cleanup code, and optionally
// prints warnings.
//
// BeforeExit is called before [os.Exit]. // BeforeExit is called before [os.Exit].
BeforeExit() BeforeExit()
} }
// defaultMsg is the default implementation of the [Msg] interface. // defaultMsg is the default implementation of the [Msg] interface.
// The zero value is not safe for use. Callers should use the [New] function instead. //
// The zero value is not safe for use. Callers should use the [New] function
// instead.
type defaultMsg struct { type defaultMsg struct {
verbose atomic.Bool verbose atomic.Bool
@@ -61,8 +76,9 @@ type defaultMsg struct {
} }
// New initialises a downstream [log.Logger] for a new [Msg]. // New initialises a downstream [log.Logger] for a new [Msg].
// The [log.Logger] should no longer be configured after [New] returns. //
// If downstream is nil, a new logger is initialised in its place. // The [log.Logger] should no longer be configured after [New] returns. If
// downstream is nil, a new logger is initialised in its place.
func New(downstream *log.Logger) Msg { func New(downstream *log.Logger) Msg {
if downstream == nil { if downstream == nil {
downstream = log.New(log.Writer(), "container: ", 0) downstream = log.New(log.Writer(), "container: ", 0)
@@ -94,7 +110,8 @@ func (msg *defaultMsg) Verbosef(format string, v ...any) {
func (msg *defaultMsg) Resume() bool { func (msg *defaultMsg) Resume() bool {
resumed, dropped, _, err := msg.Suspendable.Resume() resumed, dropped, _, err := msg.Suspendable.Resume()
if err != nil { if err != nil {
// probably going to result in an error as well, so this message is as good as unreachable // probably going to result in an error as well, so this message is as
// good as unreachable
msg.logger.Printf("cannot dump buffer on resume: %v", err) msg.logger.Printf("cannot dump buffer on resume: %v", err)
} }
if resumed && dropped > 0 { if resumed && dropped > 0 {
@@ -103,7 +120,8 @@ func (msg *defaultMsg) Resume() bool {
return resumed return resumed
} }
// BeforeExit prints a message if called between calls to [Suspendable.Suspend] and Resume. // BeforeExit prints a message if called between calls to [Suspendable.Suspend]
// and Resume.
func (msg *defaultMsg) BeforeExit() { func (msg *defaultMsg) BeforeExit() {
if msg.Resume() { if msg.Resume() {
msg.logger.Printf("beforeExit reached on suspended output") msg.logger.Printf("beforeExit reached on suspended output")
+8 -8
View File
@@ -1,11 +1,11 @@
//go:build testtool //go:build testtool
/* // Package sandbox provides utilities for checking sandbox outcome.
Package sandbox provides utilities for checking sandbox outcome. //
// This package must never be used outside integration tests, there is a much
This package must never be used outside integration tests, there is a much better native implementation of mountinfo // better native implementation of mountinfo in the public sandbox/vfs package.
in the public sandbox/vfs package. Files in this package are excluded by the build system to prevent accidental misuse. // Files in this package are excluded by the build system to prevent accidental
*/ // misuse.
package sandbox package sandbox
import ( import (
@@ -204,8 +204,8 @@ func MustCheckFilter(pid int, want string) {
return return
} }
var perr *ptraceError perr, ok := errors.AsType[*ptraceError](err)
if !errors.As(err, &perr) { if !ok {
fatalf("%s", err) fatalf("%s", err)
} }
switch perr.op { switch perr.op {
+1 -1
View File
@@ -103,7 +103,7 @@ type (
// Flags interprets VfsOptstr and returns the resulting flags and unmatched options. // Flags interprets VfsOptstr and returns the resulting flags and unmatched options.
func (e *MountInfoEntry) Flags() (flags uintptr, unmatched []string) { func (e *MountInfoEntry) Flags() (flags uintptr, unmatched []string) {
for _, s := range strings.Split(e.VfsOptstr, ",") { for s := range strings.SplitSeq(e.VfsOptstr, ",") {
switch s { switch s {
case "rw": case "rw":
case "ro": case "ro":