Skip to content

Instantly share code, notes, and snippets.

@jimmyfrasche
Created February 28, 2019 22:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jimmyfrasche/51b83022f83ccbe240092dcd884605cd to your computer and use it in GitHub Desktop.
Save jimmyfrasche/51b83022f83ccbe240092dcd884605cd to your computer and use it in GitHub Desktop.
stdlib error handling
// Copyright 2019 jimmyfrasche
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
)
func chk(err error) {
if err != nil {
log.Fatal(err)
}
}
func main() {
log.SetFlags(0)
root := filepath.Join(runtime.GOROOT(), "src")
chk(os.Chdir(root))
// Collect directories to examine.
var dirs []string
err := filepath.Walk(".", func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
if nm := fi.Name(); nm == "errors" || nm == "testdata" || nm == "cmd" {
return filepath.SkipDir
}
if path == "." {
return nil
}
dirs = append(dirs, filepath.ToSlash(path))
}
return nil
})
chk(err)
// Collect and parse files.
pkgs := map[string][]*ast.File{}
fset := token.NewFileSet()
for _, dir := range dirs {
goFiles, err := filepath.Glob(filepath.Join(dir, "*.go"))
chk(err)
if len(goFiles) == 0 {
continue
}
for _, file := range goFiles {
if !strings.HasSuffix(file, "_test.go") {
f, err := parser.ParseFile(fset, file, nil, 0)
chk(err)
pkgs[dir] = append(pkgs[dir], f)
}
}
}
// Inspect parsed files for custom error types and fmt.Errorfs.
type result struct {
pkg string
custom []string
fmts []string
calls []string
}
var results []result
for pkg, files := range pkgs {
errTypes := map[string]bool{}
var fmts, calls []token.Pos
for _, file := range files {
ast.Inspect(file, func(n ast.Node) bool {
switch n := n.(type) {
case *ast.FuncDecl:
// func (X) Error() string
if n.Recv != nil && n.Name.Name == "Error" && retsErr(n.Type) {
errTypes[rcvName(n.Recv.List[0].Type)] = true
}
case *ast.CallExpr:
// fmt.Errorf("... %X", ..., X)
if calling(n.Fun) == "fmt.Errorf" && len(n.Args) > 1 && fmtString(n.Args[0]) {
fmts = append(fmts, n.Pos())
}
// X.Error()
if rawError(n) {
calls = append(calls, n.Pos())
}
}
return true
})
}
if len(errTypes)+len(fmts)+len(calls) > 0 {
results = append(results, result{
pkg: pkg,
custom: sortMap(errTypes),
fmts: sortPos(fset, fmts),
calls: sortPos(fset, calls),
})
}
}
sort.Slice(results, func(i, j int) bool {
return results[i].pkg < results[j].pkg
})
// Display.
var nc, nf, ne int
for _, res := range results {
fmt.Println(res.pkg)
if len(res.custom) > 0 {
fmt.Println(" custom error types:")
for _, c := range res.custom {
nc++
fmt.Printf("\t%s\n", c)
}
}
if len(res.fmts) > 0 {
fmt.Println(" possibly wrapping fmt.Errorf:")
for _, c := range res.fmts {
nf++
fmt.Printf("\t%s\n", c)
}
}
if len(res.calls) > 0 {
fmt.Println(" naked Error() calls")
for _, c := range res.calls {
ne++
fmt.Printf("\t%s\n", c)
}
}
fmt.Println()
}
fmt.Println("custom error types:", nc)
fmt.Println("potential wrapping fmt.Errorf:", nf)
fmt.Println("naked Error calls:", ne)
}
func sortMap(m map[string]bool) (ret []string) {
for k := range m {
ret = append(ret, k)
}
sort.Strings(ret)
return ret
}
func sortPos(fset *token.FileSet, fmts []token.Pos) (ret []string) {
type pos struct {
name string
line int
}
var poses []pos
for _, fmt := range fmts {
p := fset.Position(fmt)
poses = append(poses, pos{
name: filepath.Base(p.Filename),
line: p.Line,
})
}
sort.Slice(poses, func(i, j int) bool {
pi, pj := poses[i], poses[j]
return pi.name < pj.name && pi.line < pj.line
})
for _, p := range poses {
ret = append(ret, fmt.Sprintf("%s:%d", p.name, p.line))
}
return ret
}
func rcvName(t ast.Expr) string {
if s, ok := t.(*ast.StarExpr); ok {
return rcvName(s.X)
}
nm, ok := t.(*ast.Ident)
if !ok {
return ""
}
return nm.Name
}
func retsErr(f *ast.FuncType) bool {
if len(f.Params.List) != 0 || f.Results == nil || len(f.Results.List) != 1 {
return false
}
ret := f.Results.List[0]
if ret.Names != nil && len(ret.Names) != 1 {
return false
}
nm, ok := ret.Type.(*ast.Ident)
if !ok {
return false
}
return nm.Name == "string"
}
func rawError(n *ast.CallExpr) bool {
sel, ok := n.Fun.(*ast.SelectorExpr)
if !ok {
return false
}
if sel.Sel.Name != "Error" {
return false
}
if len(n.Args) != 0 {
return false
}
return true
}
func calling(n ast.Expr) string {
sel, ok := n.(*ast.SelectorExpr)
if !ok {
return ""
}
pkg, ok := sel.X.(*ast.Ident)
if !ok {
return ""
}
return pkg.Name + "." + sel.Sel.Name
}
var fmtStringRe = regexp.MustCompile("%[sv]$")
func fmtString(s ast.Expr) bool {
l, ok := s.(*ast.BasicLit)
if !ok || l.Kind != token.STRING {
return false
}
str, err := strconv.Unquote(l.Value)
if err != nil {
return false
}
return fmtStringRe.MatchString(str)
}
archive/tar
custom error types:
headerError
possibly wrapping fmt.Errorf:
common.go:660
compress/bzip2
custom error types:
StructuralError
compress/flate
custom error types:
CorruptInputError
InternalError
ReadError
WriteError
naked Error() calls
inflate.go:53
inflate.go:65
context
custom error types:
deadlineExceededError
crypto/aes
custom error types:
KeySizeError
crypto/des
custom error types:
KeySizeError
crypto/rc4
custom error types:
KeySizeError
crypto/tls
custom error types:
RecordHeaderError
alert
timeoutError
possibly wrapping fmt.Errorf:
prf.go:361
tls.go:228
tls.go:242
naked Error() calls
handshake_client.go:106
handshake_client.go:113
handshake_client.go:609
handshake_client.go:818
handshake_client.go:959
handshake_client_tls13.go:593
handshake_server.go:574
handshake_server.go:715
handshake_server.go:739
handshake_server_tls13.go:645
key_agreement.go:199
ticket.go:163
crypto/x509
custom error types:
CertificateInvalidError
ConstraintViolationError
HostnameError
InsecureAlgorithmError
SystemRootsError
UnhandledCriticalExtension
UnknownAuthorityError
possibly wrapping fmt.Errorf:
pkcs8.go:54
root_darwin.go:282
root_darwin.go:285
x509.go:1127
naked Error() calls
x509_test_import.go:25
pkcs8.go:37
pkcs8.go:49
pkcs8.go:82
pkcs8.go:93
sec1.go:68
verify.go:179
pem_decrypt.go:190
verify.go:546
verify.go:525
x509.go:1272
x509.go:1296
x509.go:2515
x509.go:1230
x509_test_import.go:40
database/sql
possibly wrapping fmt.Errorf:
convert.go:192
convert.go:427
convert.go:436
convert.go:445
sql.go:2957
database/sql/driver
possibly wrapping fmt.Errorf:
types.go:277
types.go:281
debug/dwarf
custom error types:
DecodeError
possibly wrapping fmt.Errorf:
typeunit.go:91
debug/elf
custom error types:
FormatError
possibly wrapping fmt.Errorf:
file.go:1369
debug/gosym
custom error types:
DecodingError
UnknownFileError
UnknownLineError
debug/macho
custom error types:
FormatError
debug/pe
custom error types:
FormatError
possibly wrapping fmt.Errorf:
section.go:58
section.go:63
string.go:35
string.go:40
string.go:50
symbol.go:34
symbol.go:39
debug/plan9obj
custom error types:
formatError
encoding/ascii85
custom error types:
CorruptInputError
encoding/asn1
custom error types:
StructuralError
SyntaxError
encoding/base32
custom error types:
CorruptInputError
encoding/base64
custom error types:
CorruptInputError
encoding/csv
custom error types:
ParseError
encoding/gob
naked Error() calls
type.go:768
encoding/hex
custom error types:
InvalidByteError
encoding/json
custom error types:
InvalidUTF8Error
InvalidUnmarshalError
MarshalerError
SyntaxError
UnmarshalFieldError
UnmarshalTypeError
UnsupportedTypeError
UnsupportedValueError
possibly wrapping fmt.Errorf:
decode.go:720
decode.go:760
decode.go:849
decode.go:860
decode.go:876
decode.go:890
decode.go:903
decode.go:909
decode.go:927
decode.go:959
decode.go:974
naked Error() calls
encode.go:269
encoding/xml
custom error types:
SyntaxError
TagPathError
UnmarshalError
UnsupportedTypeError
possibly wrapping fmt.Errorf:
marshal.go:735
marshal.go:880
typeinfo.go:204
xml.go:641
flag
possibly wrapping fmt.Errorf:
flag.go:426
fmt
naked Error() calls
print.go:610
go/build
custom error types:
MultiplePackageError
NoGoError
possibly wrapping fmt.Errorf:
build.go:701
build.go:727
build.go:864
build.go:1252
build.go:1398
build.go:1405
build.go:1424
build.go:1429
build.go:1454
go/format
naked Error() calls
internal.go:34
internal.go:56
go/internal/gccgoimporter
custom error types:
importError
possibly wrapping fmt.Errorf:
ar.go:91
go/internal/gcimporter
possibly wrapping fmt.Errorf:
gcimporter.go:131
go/scanner
custom error types:
Error
ErrorList
naked Error() calls
errors.go:98
go/types
custom error types:
Error
possibly wrapping fmt.Errorf:
eval.go:59
html/template
custom error types:
Error
naked Error() calls
js.go:175
image/gif
possibly wrapping fmt.Errorf:
reader.go:240
reader.go:268
reader.go:291
reader.go:304
reader.go:317
reader.go:326
reader.go:335
reader.go:347
reader.go:357
reader.go:414
reader.go:425
reader.go:442
reader.go:452
reader.go:484
image/jpeg
custom error types:
FormatError
UnsupportedError
image/png
custom error types:
FormatError
UnsupportedError
naked Error() calls
reader.go:401
internal/poll
custom error types:
TimeoutError
naked Error() calls
fd_plan9.go:189
fd_plan9.go:193
fd_windows.go:247
internal/testenv
naked Error() calls
testenv.go:108
testenv_windows.go:21
internal/trace
possibly wrapping fmt.Errorf:
parser.go:145
parser.go:158
parser.go:174
parser.go:201
parser.go:214
parser.go:221
parser.go:258
parser.go:285
parser.go:327
parser.go:332
parser.go:360
parser.go:366
parser.go:375
parser.go:864
parser.go:869
parser.go:873
parser.go:884
parser.go:893
parser.go:897
parser.go:931
internal/x/crypto/cryptobyte
possibly wrapping fmt.Errorf:
asn1.go:171
internal/x/net/dns/dnsmessage
custom error types:
nestedError
naked Error() calls
message.go:128
internal/x/net/http/httpproxy
possibly wrapping fmt.Errorf:
proxy.go:167
internal/x/net/http2/hpack
custom error types:
DecodingError
InvalidIndexError
internal/x/net/idna
custom error types:
labelError
runeError
internal/x/net/internal/nettest
naked Error() calls
helper_windows.go:30
internal/xcoff
possibly wrapping fmt.Errorf:
ar.go:135
ar.go:145
ar.go:168
ar.go:175
ar.go:208
math/big
custom error types:
ErrNaN
mime/multipart
possibly wrapping fmt.Errorf:
multipart.go:323
net
custom error types:
AddrError
DNSConfigError
DNSError
InvalidAddrError
OpError
ParseError
UnknownNetworkError
addrinfoErrno
timeoutError
naked Error() calls
addrselect.go:297
cgo_unix.go:112
cgo_unix.go:173
lookup_windows.go:290
dial.go:258
dnsclient_unix.go:247
dnsclient_unix.go:265
dnsclient_unix.go:290
lookup_windows.go:271
lookup_windows.go:251
ipsock.go:132
lookup.go:180
lookup.go:241
lookup_plan9.go:63
lookup_plan9.go:150
lookup_plan9.go:226
lookup_windows.go:209
lookup_windows.go:59
lookup_windows.go:101
lookup_windows.go:116
lookup_windows.go:147
lookup_windows.go:179
lookup_windows.go:183
lookup_windows.go:194
lookup_plan9.go:226
lookup_windows.go:231
cgo_unix.go:302
dnsclient_unix.go:566
dnsclient_unix.go:389
lookup_windows.go:317
net.go:470
net.go:566
net/http
custom error types:
ProtocolError
badRequestError
badStringError
http2ConnectionError
http2GoAwayError
http2StreamError
http2badStringError
http2connError
http2duplicatePseudoHeaderError
http2goAwayFlowError
http2headerFieldNameError
http2headerFieldValueError
http2httpError
http2noCachedConnError
http2pseudoHeaderError
httpError
tlsHandshakeTimeoutError
transportReadFromServerError
possibly wrapping fmt.Errorf:
client.go:566
h2_bundle.go:8945
roundtrip_js.go:145
server.go:323
transport.go:421
transport.go:1632
transport.go:1835
transport.go:2180
naked Error() calls
client.go:647
client.go:874
fs.go:213
fs.go:226
fs.go:251
h2_bundle.go:4202
h2_bundle.go:6442
triv.go:110
net/http/fcgi
naked Error() calls
child.go:285
net/http/httptest
naked Error() calls
httptest.go:47
net/http/httputil
possibly wrapping fmt.Errorf:
reverseproxy.go:529
reverseproxy.go:535
reverseproxy.go:539
net/mail
custom error types:
charsetError
possibly wrapping fmt.Errorf:
message.go:488
net/rpc
custom error types:
ServerError
naked Error() calls
client.go:128
client.go:137
client.go:143
debug.go:88
server.go:389
server.go:475
server.go:500
server.go:596
server.go:634
server.go:707
net/rpc/jsonrpc
possibly wrapping fmt.Errorf:
client.go:90
net/textproto
custom error types:
Error
ProtocolError
net/url
custom error types:
Error
EscapeError
InvalidHostError
naked Error() calls
url.go:28
os
custom error types:
LinkError
PathError
SyscallError
naked Error() calls
error.go:33
error.go:47
error_plan9.go:27
file.go:98
os/exec
custom error types:
Error
ExitError
naked Error() calls
exec.go:47
os/signal/internal/pty
custom error types:
PtyError
naked Error() calls
pty.go:34
os/user
custom error types:
UnknownGroupError
UnknownGroupIdError
UnknownUserError
UnknownUserIdError
possibly wrapping fmt.Errorf:
cgo_lookup_unix.go:73
cgo_lookup_unix.go:106
cgo_lookup_unix.go:153
cgo_lookup_unix.go:186
lookup_plan9.go:28
lookup_stubs.go:64
lookup_windows.go:166
reflect
custom error types:
ValueError
regexp
naked Error() calls
regexp.go:272
regexp.go:283
regexp/syntax
custom error types:
Error
runtime
custom error types:
TypeAssertionError
errorString
plainError
naked Error() calls
panic.go:430
runtime/pprof
naked Error() calls
pprof.go:791
runtime/pprof/internal/profile
possibly wrapping fmt.Errorf:
legacy_profile.go:610
legacy_profile.go:613
legacy_profile.go:844
legacy_profile.go:848
profile.go:137
profile.go:141
profile.go:147
profile.go:152
profile.go:487
profile.go:491
profile.go:496
prune.go:87
prune.go:91
strconv
custom error types:
NumError
naked Error() calls
atoi.go:23
syscall
custom error types:
DLLError
Errno
ErrorString
naked Error() calls
dll_windows.go:64
dll_windows.go:95
exec_plan9.go:187
mksyscall_windows.go:772
syscall_windows.go:881
unzip_nacl.go:641
syscall/js
custom error types:
Error
ValueError
testing/quick
custom error types:
CheckEqualError
CheckError
SetupError
text/scanner
naked Error() calls
scanner.go:242
text/template
custom error types:
ExecError
possibly wrapping fmt.Errorf:
funcs.go:138
funcs.go:149
funcs.go:189
funcs.go:209
funcs.go:231
funcs.go:245
funcs.go:275
funcs.go:289
naked Error() calls
exec.go:121
text/template/parse
possibly wrapping fmt.Errorf:
node.go:544
time
custom error types:
ParseError
fileSizeError
custom error types: 126
potential wrapping fmt.Errorf: 136
naked Error calls: 116
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment