shad-go/lectures/11-analysis/lecture.slide
2021-04-29 16:07:56 +03:00

459 lines
11 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Статический Анализ 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.
* 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
* 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/,/^}/