2021-04-27 18:37:22 +00:00
|
|
|
|
Статический Анализ Go Кода
|
|
|
|
|
|
|
|
|
|
Короткий Фёдор
|
|
|
|
|
|
|
|
|
|
* Работа с Go кодом в stdlib
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"go/types"
|
|
|
|
|
"go/constant"
|
|
|
|
|
"go/parser"
|
|
|
|
|
"go/ast"
|
|
|
|
|
"go/scanner"
|
|
|
|
|
"go/token"
|
|
|
|
|
|
|
|
|
|
"golang.org/x/tools/go/loader"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
- `go/token` - определяет токены
|
|
|
|
|
- `go/scanner` - разбивает файл с исходным кодом на токены
|
|
|
|
|
- `go/ast` - определяет типы, из которых состоит AST
|
|
|
|
|
- `go/parser` - рекурсивный парсер, строящий AST
|
|
|
|
|
- `go/constant` - представление констант и операции над ними
|
|
|
|
|
- `go/types` - реализует type-checker.
|
|
|
|
|
|
2021-04-29 13:07:56 +00:00
|
|
|
|
* Example
|
|
|
|
|
|
|
|
|
|
.code pkginfo/main.go /const/,/END/
|
|
|
|
|
|
|
|
|
|
* Run Example
|
|
|
|
|
|
|
|
|
|
$ ./pkginfo
|
|
|
|
|
Package "cmd/hello"
|
|
|
|
|
Name: main
|
|
|
|
|
Imports: [package fmt ("fmt")]
|
|
|
|
|
Scope: package "cmd/hello" scope 0x820533590 {
|
|
|
|
|
. func cmd/hello.main()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* AST
|
|
|
|
|
|
|
|
|
|
type Node interface {
|
|
|
|
|
Pos() token.Pos // position of first character belonging to the node
|
|
|
|
|
End() token.Pos // position of first character immediately after the node
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All expression nodes implement the Expr interface.
|
|
|
|
|
type Expr interface {
|
|
|
|
|
Node
|
|
|
|
|
exprNode()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All statement nodes implement the Stmt interface.
|
|
|
|
|
type Stmt interface {
|
|
|
|
|
Node
|
|
|
|
|
stmtNode()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All declaration nodes implement the Decl interface.
|
|
|
|
|
type Decl interface {
|
|
|
|
|
Node
|
|
|
|
|
declNode()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* AST Expr
|
|
|
|
|
|
|
|
|
|
Expr = *BadExpr
|
|
|
|
|
| *Ident
|
|
|
|
|
| *Ellipsis
|
|
|
|
|
| *BasicLit
|
|
|
|
|
| *FuncLit
|
|
|
|
|
| *CompositeLit
|
|
|
|
|
| *ParenExpr
|
|
|
|
|
| *SelectorExpr
|
|
|
|
|
| *IndexExpr
|
|
|
|
|
| *SliceExpr
|
|
|
|
|
| *TypeAssertExpr
|
|
|
|
|
| *CallExpr
|
|
|
|
|
| *StarExpr
|
|
|
|
|
| *UnaryExpr
|
|
|
|
|
| *BinaryExpr
|
|
|
|
|
| *KeyValueExpr
|
|
|
|
|
|
|
|
|
|
| *ArrayType
|
|
|
|
|
| *StructType
|
|
|
|
|
| *FuncType
|
|
|
|
|
| *InterfaceType
|
|
|
|
|
| *MapType
|
|
|
|
|
| *ChanType
|
|
|
|
|
|
|
|
|
|
* AST Stmt
|
|
|
|
|
|
|
|
|
|
Stmt = *BadStmt
|
|
|
|
|
| *DeclStmt
|
|
|
|
|
| *EmptyStmt
|
|
|
|
|
| *LabeledStmt
|
|
|
|
|
| *ExprStmt
|
|
|
|
|
| *SendStmt
|
|
|
|
|
| *IncDecStmt
|
|
|
|
|
| *AssignStmt
|
|
|
|
|
| *GoStmt
|
|
|
|
|
| *DeferStmt
|
|
|
|
|
| *ReturnStmt
|
|
|
|
|
| *BranchStmt
|
|
|
|
|
| *BlockStmt
|
|
|
|
|
| *IfStmt
|
|
|
|
|
| *CaseClause
|
|
|
|
|
| *SwitchStmt
|
|
|
|
|
| *TypeSwitchStmt
|
|
|
|
|
| *CommClause
|
|
|
|
|
| *SelectStmt
|
|
|
|
|
| *ForStmt
|
|
|
|
|
| *RangeStmt
|
|
|
|
|
|
|
|
|
|
* AST Decl
|
|
|
|
|
|
|
|
|
|
Decl = *BadDecl
|
|
|
|
|
| *GenDecl
|
|
|
|
|
| *FuncDecl
|
|
|
|
|
|
|
|
|
|
* AST Example
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import "fmt"
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
fmt.Printf("hello %d", x)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.link https://astexplorer.net/#/gist/7c8cc64a979996984768aa4d7be6ceb9/203bc9b1588375699f2d733517d94d370e868d36 astexplorer.net
|
|
|
|
|
|
|
|
|
|
*ast.File
|
|
|
|
|
*GenDecl // import
|
|
|
|
|
*FuncDecl // func
|
|
|
|
|
*BlockStmt
|
|
|
|
|
*ExprStmt // fmt.Printf("hello %d", x)
|
|
|
|
|
*CallExpr
|
|
|
|
|
*SelectorExpr // fmt.Printf
|
|
|
|
|
*Ident
|
|
|
|
|
*Ident
|
|
|
|
|
*BasicLit // "hello %d"
|
|
|
|
|
*Ident // x
|
|
|
|
|
|
|
|
|
|
|
2021-04-27 18:37:22 +00:00
|
|
|
|
* Type Checker
|
|
|
|
|
|
|
|
|
|
Три процесса:
|
|
|
|
|
|
|
|
|
|
1. Для каждого *имени* определяет *объявление*. (identifier resolution)
|
|
|
|
|
|
|
|
|
|
var x int // declaration
|
|
|
|
|
|
|
|
|
|
func F() {
|
|
|
|
|
_ = x // name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
2. Для каждого *выражения* определяет *тип*. (type deduction)
|
|
|
|
|
|
|
|
|
|
time.Now().String()
|
|
|
|
|
|
|
|
|
|
// time.Now() -> *time.Time
|
|
|
|
|
// time.Now().String() -> string
|
|
|
|
|
|
|
|
|
|
3. Для каждого *константного*выражения* определяет *значение*. (constant evaluation)
|
|
|
|
|
|
|
|
|
|
const x = 1 + 2
|
|
|
|
|
|
|
|
|
|
* Type Checker Fun Facts
|
|
|
|
|
|
|
|
|
|
- Сonst evaluation depends on type deduction
|
|
|
|
|
|
|
|
|
|
const x = unsafe.Sizeof(int(0))
|
|
|
|
|
|
|
|
|
|
- Type deduction depends on const evalutation
|
|
|
|
|
|
|
|
|
|
var y = [2+3]int{}
|
|
|
|
|
|
|
|
|
|
- Identifier resolution depends on type deduction
|
|
|
|
|
|
|
|
|
|
type T struct{ k int }
|
|
|
|
|
var _ = T{k: 0}
|
|
|
|
|
|
|
|
|
|
type T [10]int
|
|
|
|
|
const k = 1
|
|
|
|
|
|
|
|
|
|
var _ = T{k: 0}
|
|
|
|
|
|
|
|
|
|
* Objects
|
|
|
|
|
|
|
|
|
|
*Identifier*resolution* строит отображение из *имени* (`*ast.Ident`) в `Object` (например `var`, `const` или `func`).
|
|
|
|
|
|
|
|
|
|
type Object interface {
|
|
|
|
|
Name() string // package-local object name
|
|
|
|
|
Exported() bool // reports whether the name starts with a capital letter
|
|
|
|
|
Type() Type // object type
|
|
|
|
|
Pos() token.Pos // position of object identifier in declaration
|
|
|
|
|
|
|
|
|
|
Parent() *Scope // scope in which this object is declared
|
|
|
|
|
Pkg() *Package // nil for objects in the Universe scope and labels
|
|
|
|
|
Id() string // object id (see Ids section below)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Objects
|
|
|
|
|
|
|
|
|
|
Object = *Func // function, concrete method, or abstract method
|
|
|
|
|
| *Var // variable, parameter, result, or struct field
|
|
|
|
|
| *Const // constant
|
|
|
|
|
| *TypeName // type name
|
|
|
|
|
| *Label // statement label
|
|
|
|
|
| *PkgName // package name, e.g. json after import "encoding/json"
|
|
|
|
|
| *Builtin // predeclared function such as append or len
|
|
|
|
|
| *Nil // predeclared nil
|
|
|
|
|
|
|
|
|
|
Некоторые типы имеют дополнительные методы
|
|
|
|
|
|
|
|
|
|
func (*Func) Scope() *Scope
|
|
|
|
|
func (*Var) Anonymous() bool
|
|
|
|
|
func (*Var) IsField() bool
|
|
|
|
|
func (*Const) Val() constant.Value
|
|
|
|
|
func (*TypeName) IsAlias() bool
|
|
|
|
|
func (*PkgName) Imported() *Package
|
|
|
|
|
|
|
|
|
|
* Identifier Resolution
|
|
|
|
|
|
|
|
|
|
type Info struct {
|
|
|
|
|
Defs map[*ast.Ident]Object
|
|
|
|
|
Uses map[*ast.Ident]Object
|
|
|
|
|
Implicits map[ast.Node]Object
|
|
|
|
|
Selections map[*ast.SelectorExpr]*Selection
|
|
|
|
|
Scopes map[ast.Node]*Scope
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Пример:
|
|
|
|
|
|
|
|
|
|
var x int // def of x, use of int
|
|
|
|
|
fmt.Println(x) // uses of fmt, Println, and x
|
|
|
|
|
type T struct{U} // def of T, use of U (type), def of U (field)
|
|
|
|
|
|
|
|
|
|
* Types
|
|
|
|
|
|
|
|
|
|
type Type interface {
|
|
|
|
|
Underlying() Type
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Реализации Type
|
|
|
|
|
|
|
|
|
|
Type = *Basic
|
|
|
|
|
| *Pointer
|
|
|
|
|
| *Array
|
|
|
|
|
| *Slice
|
|
|
|
|
| *Map
|
|
|
|
|
| *Chan
|
|
|
|
|
| *Struct
|
|
|
|
|
| *Tuple
|
|
|
|
|
| *Signature
|
|
|
|
|
| *Named
|
|
|
|
|
| *Interface
|
|
|
|
|
|
|
|
|
|
* Struct types
|
|
|
|
|
|
|
|
|
|
Описание структуры.
|
|
|
|
|
|
|
|
|
|
type Struct struct{ ... }
|
|
|
|
|
func (*Struct) NumFields() int
|
|
|
|
|
func (*Struct) Field(i int) *Var
|
|
|
|
|
func (*Struct) Tag(i int) string
|
|
|
|
|
|
|
|
|
|
* Named Types
|
|
|
|
|
|
|
|
|
|
type Celsius float64
|
|
|
|
|
|
|
|
|
|
Идентификатор `Celsius` определяет объект `*TypeName`.
|
|
|
|
|
|
|
|
|
|
`*TypeName` ссылается на `*Named` тип.
|
|
|
|
|
|
|
|
|
|
type Named struct{ ... }
|
|
|
|
|
func (*Named) NumMethods() int
|
|
|
|
|
func (*Named) Method(i int) *Func
|
|
|
|
|
func (*Named) Obj() *TypeName
|
|
|
|
|
func (*Named) Underlying() Type
|
|
|
|
|
|
|
|
|
|
`Underlying()` возвращает `int` в обоих случаях.
|
|
|
|
|
|
|
|
|
|
type T int
|
|
|
|
|
type U T
|
|
|
|
|
|
|
|
|
|
* TypeAndValue
|
|
|
|
|
|
|
|
|
|
Для каждого вырашения выводится тип. Он доступен в `Types`.
|
|
|
|
|
|
|
|
|
|
type Info struct {
|
|
|
|
|
...
|
|
|
|
|
Types map[ast.Expr]TypeAndValue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
`TypeAndValue` описывает тип, и опциональное значение (для констант).
|
|
|
|
|
|
|
|
|
|
type TypeAndValue struct {
|
|
|
|
|
Type Type
|
|
|
|
|
Value constant.Value // for constant expressions only
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (TypeAndValue) IsVoid() bool // e.g. "main()"
|
|
|
|
|
func (TypeAndValue) IsType() bool // e.g. "*os.File"
|
|
|
|
|
func (TypeAndValue) IsBuiltin() bool // e.g. "len(x)"
|
|
|
|
|
func (TypeAndValue) IsValue() bool // e.g. "*os.Stdout"
|
|
|
|
|
func (TypeAndValue) IsNil() bool // e.g. "nil"
|
|
|
|
|
func (TypeAndValue) Addressable() bool // e.g. "a[i]" but not "f()", "m[key]"
|
|
|
|
|
func (TypeAndValue) Assignable() bool // e.g. "a[i]", "m[key]"
|
|
|
|
|
func (TypeAndValue) HasOk() bool // e.g. "<-ch", "m[key]"
|
|
|
|
|
|
|
|
|
|
* Пример
|
|
|
|
|
|
|
|
|
|
// CheckNilFuncComparison reports unintended comparisons
|
|
|
|
|
// of functions against nil, e.g., "if x.Method == nil {".
|
|
|
|
|
func CheckNilFuncComparison(info *types.Info, n ast.Node) {
|
|
|
|
|
e, ok := n.(*ast.BinaryExpr)
|
|
|
|
|
if !ok {
|
|
|
|
|
return // not a binary operation
|
|
|
|
|
}
|
|
|
|
|
if e.Op != token.EQL && e.Op != token.NEQ {
|
|
|
|
|
return // not a comparison
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this is a comparison against nil, find the other operand.
|
|
|
|
|
var other ast.Expr
|
|
|
|
|
if info.Types[e.X].IsNil() {
|
|
|
|
|
other = e.Y
|
|
|
|
|
} else if info.Types[e.Y].IsNil() {
|
|
|
|
|
other = e.X
|
|
|
|
|
} else {
|
|
|
|
|
return // not a comparison against nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
* Пример
|
|
|
|
|
|
|
|
|
|
// CheckNilFuncComparison reports unintended comparisons
|
|
|
|
|
// of functions against nil, e.g., "if x.Method == nil {".
|
|
|
|
|
func CheckNilFuncComparison(info *types.Info, n ast.Node) {
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
// Find the object.
|
|
|
|
|
var obj types.Object
|
|
|
|
|
switch v := other.(type) {
|
|
|
|
|
case *ast.Ident:
|
|
|
|
|
obj = info.Uses[v]
|
|
|
|
|
case *ast.SelectorExpr:
|
|
|
|
|
obj = info.Uses[v.Sel]
|
|
|
|
|
default:
|
|
|
|
|
return // not an identifier or selection
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, ok := obj.(*types.Func); !ok {
|
|
|
|
|
return // not a function or method
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Printf("%s: comparison of function %v %v nil is always %v\n",
|
|
|
|
|
fset.Position(e.Pos()), obj.Name(), e.Op, e.Op == token.NEQ)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Analysis
|
|
|
|
|
|
|
|
|
|
package unusedresult
|
|
|
|
|
|
|
|
|
|
var Analyzer = &analysis.Analyzer{
|
|
|
|
|
Name: "unusedresult",
|
|
|
|
|
Doc: "check for unused results of calls to some functions",
|
|
|
|
|
Run: run,
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func run(pass *analysis.Pass) (interface{}, error) {
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Analysis реализует одну проверку.
|
|
|
|
|
|
|
|
|
|
* Pass
|
|
|
|
|
|
|
|
|
|
type Pass struct {
|
|
|
|
|
Fset *token.FileSet
|
|
|
|
|
Files []*ast.File
|
|
|
|
|
OtherFiles []string
|
|
|
|
|
IgnoredFiles []string
|
|
|
|
|
Pkg *types.Package
|
|
|
|
|
TypesInfo *types.Info
|
|
|
|
|
ResultOf map[*Analyzer]interface{}
|
|
|
|
|
Report func(Diagnostic)
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Pass - это запуск одного Analysis на одном пакете.
|
|
|
|
|
|
|
|
|
|
* Standalone
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"golang.org/x/tools/go/analysis/passes/findcall"
|
|
|
|
|
"golang.org/x/tools/go/analysis/singlechecker"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() { singlechecker.Main(findcall.Analyzer) }
|
|
|
|
|
|
|
|
|
|
* Unitchecker
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"golang.org/x/tools/go/analysis/passes/findcall"
|
|
|
|
|
"golang.org/x/tools/go/analysis/unitchecker"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() { unitchecker.Main(findcall.Analyzer) }
|
|
|
|
|
|
|
|
|
|
Запуск через govet.
|
|
|
|
|
|
|
|
|
|
$ go vet -vettool=$(which vet)
|
|
|
|
|
|
|
|
|
|
* analysistest
|
|
|
|
|
|
|
|
|
|
package jokelint
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"golang.org/x/tools/go/analysis/analysistest"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func Test(t *testing.T) {
|
|
|
|
|
testdata := analysistest.TestData()
|
|
|
|
|
analysistest.Run(t, testdata, Analyzer, "tests/...")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Тесты хранятся в testdata/.
|
|
|
|
|
|
|
|
|
|
package test
|
|
|
|
|
|
|
|
|
|
import "fmt"
|
|
|
|
|
|
|
|
|
|
func F() {
|
|
|
|
|
fmt.Println("lol no generics") // want `outdated joke`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Пример
|
|
|
|
|
|
|
|
|
|
.code jokelint/analysis.go /package/,/^}/
|
|
|
|
|
|
|
|
|
|
* Пример
|
|
|
|
|
|
|
|
|
|
.code jokelint/analysis.go /func run/,/^}/
|
|
|
|
|
|
|
|
|
|
* Пример
|
|
|
|
|
|
|
|
|
|
.code jokelint/analysis.go /func visitCall/,/^}/
|