Skip to content

Commit 64e1511

Browse files
committed
tools/syz-linter: define context.Context usage rules
1. It is the first parameter everywhere except tests. 2. It is the second param in the tests. 3. It is always named ctx. 4. For the cases with multiple contexts recommend to opt-out syz-linter.
1 parent 8fc3779 commit 64e1511

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

tools/syz-linter/linter.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func run(p *analysis.Pass) (any, error) {
6868
pass.checkStringLenCompare(n)
6969
case *ast.FuncDecl:
7070
pass.checkFuncArgs(n)
71+
pass.checkContextArgs(n)
7172
case *ast.CallExpr:
7273
pass.checkFlagDefinition(n)
7374
pass.checkLogErrorFormat(n)
@@ -229,6 +230,43 @@ func (pass *Pass) reportFuncArgs(fields []*ast.Field, first, last int) {
229230
pass.report(fields[first], "Use '%v %v'", names[2:], pass.typ(fields[first].Type))
230231
}
231232

233+
func (pass *Pass) checkContextArgs(n *ast.FuncDecl) {
234+
if n.Type.Params == nil {
235+
return
236+
}
237+
expectedCtxPos := 0
238+
if len(n.Type.Params.List) > 0 {
239+
firstField := n.Type.Params.List[0]
240+
if strings.HasSuffix(pass.typ(firstField.Type), "*testing.T") {
241+
expectedCtxPos = 1
242+
}
243+
}
244+
for fieldPos, field := range n.Type.Params.List {
245+
isContext := pass.typ(field.Type) == "context.Context"
246+
if isContext {
247+
if fieldPos != expectedCtxPos {
248+
if expectedCtxPos == 0 {
249+
pass.report(field, "Context must be the first argument")
250+
} else {
251+
pass.report(field, "Context must be the second argument")
252+
}
253+
}
254+
// Every type group may have a few variables.
255+
if len(field.Names) > 1 {
256+
// A few contexts are passed to the function
257+
// It is very rare. Let's use nolint:syz-linter to opt-out.
258+
pass.report(field, "multiple Contexts are passed, use nolint:syz-linter")
259+
}
260+
if len(field.Names) == 1 {
261+
name := field.Names[0]
262+
if name.Name != "ctx" && name.Name != "_" {
263+
pass.report(name, "Context variable must be named 'ctx' or '_'")
264+
}
265+
}
266+
}
267+
}
268+
}
269+
232270
func (pass *Pass) checkFlagDefinition(n *ast.CallExpr) {
233271
fun, ok := n.Fun.(*ast.SelectorExpr)
234272
if !ok {

tools/syz-linter/testdata/src/lintertest/lintertest.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package lintertest
55

66
import (
7+
"context"
78
"flag"
89
"fmt"
910
"log"
@@ -152,3 +153,24 @@ func anyInterface() interface{} { // want "Use any instead of interface{}"
152153
func(any) {} (y)
153154
return v
154155
}
156+
157+
func contextArgsGood1(ctx context.Context) {
158+
}
159+
160+
func contextArgsBad1(c context.Context) { // want "Context variable must be named 'ctx'"
161+
}
162+
163+
func contextArgsBad2(a int, ctx context.Context) { // want "Context must be the first argument"
164+
}
165+
166+
func contextArgsGood2(ctx context.Context, a int) {
167+
}
168+
169+
func TestContextArgsGood(t *testing.T, ctx context.Context) {
170+
}
171+
172+
func TestContextArgsBad1(t *testing.T, c context.Context) { // want "Context variable must be named 'ctx'"
173+
}
174+
175+
func TestContextArgsBad2(t *testing.T, a int, ctx context.Context) { // want "Context must be the second argument"
176+
}

0 commit comments

Comments
 (0)