Skip to content

Commit abb98b0

Browse files
committed
support fmt.Errorf
1 parent 42cbae8 commit abb98b0

File tree

4 files changed

+116
-12
lines changed

4 files changed

+116
-12
lines changed

.golangci.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ linters-settings:
1818
allow:
1919
- $gostd
2020
- github.com/alingse/nilnesserr
21+
cyclop:
22+
max-complexity: 12
23+
lll:
24+
line-length: 200
2125

2226
issues:
2327
exclude-rules:

linter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Powered by nilness and nilerr.`
1212

1313
linterReturnMessage = "return a nil value error after check error"
1414
linterCallMessage = "call function with a nil value error after check error"
15+
linterCall2Message = "call variadic function with a nil value error after check error"
1516
)
1617

1718
type LinterSetting struct{}

nilerr.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ func checkNilnesserr(pass *analysis.Pass, b *ssa.BasicBlock, errors []errFact, i
8080
})
8181
}
8282
}
83+
84+
// extra check for variadic arguments
85+
variadicArgs := checkVariadicCall(instr)
86+
for _, value := range variadicArgs {
87+
if checkSSAValue(value, errors, isNilnees) {
88+
pass.Report(analysis.Diagnostic{
89+
Pos: pos,
90+
Message: linterCall2Message,
91+
})
92+
}
93+
}
8394
}
8495
}
8596
}
@@ -93,3 +104,89 @@ func checkSSAValue(res ssa.Value, errors []errFact, isNilnees func(value ssa.Val
93104

94105
return lastValue != nil
95106
}
107+
108+
func checkVariadicCall(call *ssa.Call) []ssa.Value {
109+
alloc := validateVariadicCall(call)
110+
if alloc == nil {
111+
return nil
112+
}
113+
114+
return extractVariadicErrors(alloc)
115+
}
116+
117+
/*
118+
example: fmt.Errorf("call Do2 got err %w", err)
119+
120+
type *ssa.Alloc instr new [1]any (varargs)
121+
type *ssa.IndexAddr instr &t4[0:int]
122+
type *ssa.ChangeInterface instr change interface any <- error (t0)
123+
type *ssa.Store instr *t5 = t6
124+
...
125+
type *ssa.Slice instr slice t4[:]
126+
type *ssa.Call instr fmt.Errorf("call Do2 got err ...":string, t7...)
127+
*/
128+
func validateVariadicCall(call *ssa.Call) *ssa.Alloc {
129+
fn, ok := call.Call.Value.(*ssa.Function)
130+
if !ok {
131+
return nil
132+
}
133+
if !fn.Signature.Variadic() {
134+
return nil
135+
}
136+
137+
if len(call.Call.Args) == 0 {
138+
return nil
139+
}
140+
lastArg := call.Call.Args[len(call.Call.Args)-1]
141+
slice, ok := lastArg.(*ssa.Slice)
142+
if !ok {
143+
return nil
144+
}
145+
// check is t[:]
146+
if !(slice.Low == nil && slice.High == nil && slice.Max == nil) {
147+
return nil
148+
}
149+
alloc, ok := slice.X.(*ssa.Alloc)
150+
if !ok {
151+
return nil
152+
}
153+
valueType, ok := alloc.Type().(*types.Pointer)
154+
if !ok {
155+
return nil
156+
}
157+
158+
// check is array
159+
_, ok = valueType.Elem().(*types.Array)
160+
if !ok {
161+
return nil
162+
}
163+
164+
return alloc
165+
}
166+
167+
// the Referrer chain is like this.
168+
// Alloc --> IndexAddr --> ChangeInterface --> Store ---> Slice.
169+
// Alloc --> IndexAddr --> Store --> Slice.
170+
func extractVariadicErrors(alloc *ssa.Alloc) []ssa.Value {
171+
values := make([]ssa.Value, 0)
172+
173+
for _, instr := range *alloc.Referrers() {
174+
indexAddr, ok := instr.(*ssa.IndexAddr)
175+
if !ok {
176+
continue
177+
}
178+
for _, instr2 := range *indexAddr.Referrers() {
179+
store, ok := instr2.(*ssa.Store)
180+
if !ok {
181+
continue
182+
}
183+
value := store.Val
184+
if change, ok := value.(*ssa.ChangeInterface); ok {
185+
value = change.X
186+
}
187+
values = append(values, value)
188+
}
189+
}
190+
191+
return values
192+
}

testdata/src/nilnesserr/positive.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -118,18 +118,20 @@ func Call18() error {
118118
}
119119
err2 := Do2()
120120
if err2 != nil {
121-
_ = fmt.Errorf("call Do2 got err %w", err) // TODO
122-
_ = errors.Is(err, io.EOF) // want `call function with a nil value error after check error`
123-
_ = errors.Join(io.EOF, err) // TODO
124-
_ = errors.As(err, new(localError)) // want `call function with a nil value error after check error`
125-
_ = errors.Unwrap(err) // want `call function with a nil value error after check error`
126-
_ = isEOFErr(err) // want `call function with a nil value error after check error`
127-
_ = Append(err, err2) // want `call function with a nil value error after check error`
128-
_ = Errors(err) // want `call function with a nil value error after check error`
129-
_ = Combine(err) // TODO
130-
131-
_ = fmt.Sprintf("call Do2 got err %+v", err) // TODO
132-
return fmt.Errorf("call Do2 got err %w", err) // TODO
121+
122+
_ = fmt.Errorf("call Do2 got err %w", err) // want `call variadic function with a nil value error after check error`
123+
124+
_ = errors.Is(err, io.EOF) // want `call function with a nil value error after check error`
125+
_ = errors.Join(io.EOF, err) // want `call variadic function with a nil value error after check error`
126+
_ = errors.As(err, new(localError)) // want `call function with a nil value error after check error`
127+
_ = errors.Unwrap(err) // want `call function with a nil value error after check error`
128+
_ = isEOFErr(err) // want `call function with a nil value error after check error`
129+
_ = Append(err, err2) // want `call function with a nil value error after check error`
130+
_ = Errors(err) // want `call function with a nil value error after check error`
131+
_ = Combine(err) // want `call variadic function with a nil value error after check error`
132+
133+
_ = fmt.Sprintf("call Do2 got err %+v", err) // want `call variadic function with a nil value error after check error`
134+
return fmt.Errorf("call Do2 got err return %w", err) // want `call variadic function with a nil value error after check error`
133135
}
134136

135137
return nil

0 commit comments

Comments
 (0)