Я использую https://github.com/gin-gonic/gin, и я заметил, что мне нужно использовать c.Request.Context()
вместо c *gin.Context
в моих обработчиках, чтобы обеспечить распространение и отмену контекста.
Я написал следующий анализатор для поиска этих случаев:
package main
import (
"bytes"
"fmt"
"go/ast"
"go/printer"
"go/token"
"go/types"
"io/ioutil"
"os"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/singlechecker"
)
var Analyzer = &analysis.Analyzer{
Name: "gincontext",
Doc: "This analyzer detects usage of *gin.Context as context.Context function " +
" parameter, which doesn't properly communicate cancellation downstream.",
Run: run,
}
func main() {
singlechecker.Main(Analyzer)
}
func run(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
ast.Inspect(file, func(n ast.Node) bool {
be, ok := n.(*ast.CallExpr)
if !ok {
return true
}
if !funcHasContextContextAsFirstParam(pass, be.Fun) {
return true
}
if len(be.Args) < 1 {
return true
}
arg := be.Args[0]
if !exprArgIsGinContext(pass, arg) {
return true
}
// TODO: how to update file contents?
pass.Reportf(
be.Pos(),
"calling function that takes context.Context with *gin.Context which might not work as expected %q",
render(pass.Fset, be))
return true
})
}
return nil, nil
}
// exprArgIsGinContext checks if provided expression is a pointer of type *gin.Context.
func exprArgIsGinContext(pass *analysis.Pass, expr ast.Expr) bool {
t := pass.TypesInfo.TypeOf(expr)
if t == nil {
return false
}
p, ok := t.Underlying().(*types.Pointer)
if !ok {
return false
}
named, ok := p.Elem().(*types.Named)
if !ok {
return false
}
namedObj := named.Obj()
if namedObj.Name() != "Context" || namedObj.Pkg().Name() != "gin" || namedObj.Pkg().Path() != "github.com/gin-gonic/gin" {
return false
}
return true
}
// funcHasContextContextAsFirstParam checks if provided expression is a function
// call site with context.Context as first parameter.
func funcHasContextContextAsFirstParam(pass *analysis.Pass, expr ast.Expr) bool {
t := pass.TypesInfo.TypeOf(expr)
if t == nil {
return false
}
bt, ok := t.Underlying().(*types.Signature)
if !ok {
return false
}
params := bt.Params()
if params.Len() < 1 {
return false
}
param := params.At(0)
named, ok := param.Type().(*types.Named)
if !ok {
return false
}
namedObj := named.Obj()
if namedObj.Name() != "Context" || namedObj.Pkg().Name() != "context" {
return false
}
return true
}
// render returns the pretty-print of the given node
func render(fset *token.FileSet, x interface{}) string {
var buf bytes.Buffer
if err := printer.Fprint(&buf, fset, x); err != nil {
panic(err)
}
return buf.String()
}
В разделе TODO
я пытаюсь обновить содержимое файла, чтобы изменить:
func goo(ctx context.Context) {
// ...
}
// this ...
func handler(c *gin.Context) {
fmt.Println("handler")
foo(c, 123, "dummy")
}
func handler2(c *gin.Context) {
fmt.Println("handler2")
foo(c, 10, "xxx")
}
// ... to this ...
func handler(c *gin.Context) {
fmt.Println("handler")
foo(c.Request.Context(), 123, "dummy")
}
func handler2(c *gin.Context) {
fmt.Println("handler2")
foo(c.Request.Context(), 10, "xxx")
}
Мне удалось написать что-то вроде этого:
position := pass.Fset.Position(arg.Pos())
filename := position.Filename
f, err := os.OpenFile(filename, os.O_RDWR, 0600)
if err != nil {
panic(err)
}
defer func() {
if err := f.Close(); err != nil {
panic(err)
}
}()
// TODO: change +1 to length of *gin.Context variable name
if _, err := f.Seek(int64(position.Offset)+1, 0); err != nil {
panic(err)
}
remainder, err := ioutil.ReadAll(f)
if err != nil {
panic(err)
}
if _, err := f.Seek(int64(position.Offset)+1, 0); err != nil {
panic(err)
}
const requestContextStr = ".Request.Context()"
if _, err := f.Write([]byte(requestContextStr)); err != nil {
panic(err)
}
if _, err := f.Write(remainder); err != nil {
panic(err)
}
, но это только правильно обновит первое вхождение, и будут зашифрованы следующие:
func handler(c *gin.Context) {
fmt.Println("handler")
// OK
foo(c.Request.Context(), 123, "dummy")
}
func handler2(c *gin.Context) {
// Scrambled...
fmt.Println(.Request.Context()"handler2")
foo(c, 10, "xxx")
}
Как разрешить анализатору знаете, что мы изменили файл и как обновить текущие смещения файла?