.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / go / analysis / passes / unusedwrite / unusedwrite.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/go/analysis/passes/unusedwrite/unusedwrite.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/go/analysis/passes/unusedwrite/unusedwrite.go
new file mode 100644 (file)
index 0000000..37a0e78
--- /dev/null
@@ -0,0 +1,184 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package unusedwrite checks for unused writes to the elements of a struct or array object.
+package unusedwrite
+
+import (
+       "fmt"
+       "go/types"
+
+       "golang.org/x/tools/go/analysis"
+       "golang.org/x/tools/go/analysis/passes/buildssa"
+       "golang.org/x/tools/go/ssa"
+)
+
+// Doc is a documentation string.
+const Doc = `checks for unused writes
+
+The analyzer reports instances of writes to struct fields and
+arrays that are never read. Specifically, when a struct object
+or an array is copied, its elements are copied implicitly by
+the compiler, and any element write to this copy does nothing
+with the original object.
+
+For example:
+
+       type T struct { x int }
+       func f(input []T) {
+               for i, v := range input {  // v is a copy
+                       v.x = i  // unused write to field x
+               }
+       }
+
+Another example is about non-pointer receiver:
+
+       type T struct { x int }
+       func (t T) f() {  // t is a copy
+               t.x = i  // unused write to field x
+       }
+`
+
+// Analyzer reports instances of writes to struct fields and arrays
+//that are never read.
+var Analyzer = &analysis.Analyzer{
+       Name:     "unusedwrite",
+       Doc:      Doc,
+       Requires: []*analysis.Analyzer{buildssa.Analyzer},
+       Run:      run,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+       // Check the writes to struct and array objects.
+       checkStore := func(store *ssa.Store) {
+               // Consider field/index writes to an object whose elements are copied and not shared.
+               // MapUpdate is excluded since only the reference of the map is copied.
+               switch addr := store.Addr.(type) {
+               case *ssa.FieldAddr:
+                       if isDeadStore(store, addr.X, addr) {
+                               // Report the bug.
+                               pass.Reportf(store.Pos(),
+                                       "unused write to field %s",
+                                       getFieldName(addr.X.Type(), addr.Field))
+                       }
+               case *ssa.IndexAddr:
+                       if isDeadStore(store, addr.X, addr) {
+                               // Report the bug.
+                               pass.Reportf(store.Pos(),
+                                       "unused write to array index %s", addr.Index)
+                       }
+               }
+       }
+
+       ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
+       for _, fn := range ssainput.SrcFuncs {
+               // Visit each block. No need to visit fn.Recover.
+               for _, blk := range fn.Blocks {
+                       for _, instr := range blk.Instrs {
+                               // Identify writes.
+                               if store, ok := instr.(*ssa.Store); ok {
+                                       checkStore(store)
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+// isDeadStore determines whether a field/index write to an object is dead.
+// Argument "obj" is the object, and "addr" is the instruction fetching the field/index.
+func isDeadStore(store *ssa.Store, obj ssa.Value, addr ssa.Instruction) bool {
+       // Consider only struct or array objects.
+       if !hasStructOrArrayType(obj) {
+               return false
+       }
+       // Check liveness: if the value is used later, then don't report the write.
+       for _, ref := range *obj.Referrers() {
+               if ref == store || ref == addr {
+                       continue
+               }
+               switch ins := ref.(type) {
+               case ssa.CallInstruction:
+                       return false
+               case *ssa.FieldAddr:
+                       // Check whether the same field is used.
+                       if ins.X == obj {
+                               if faddr, ok := addr.(*ssa.FieldAddr); ok {
+                                       if faddr.Field == ins.Field {
+                                               return false
+                                       }
+                               }
+                       }
+                       // Otherwise another field is used, and this usage doesn't count.
+                       continue
+               case *ssa.IndexAddr:
+                       if ins.X == obj {
+                               return false
+                       }
+                       continue // Otherwise another object is used
+               case *ssa.Lookup:
+                       if ins.X == obj {
+                               return false
+                       }
+                       continue // Otherwise another object is used
+               case *ssa.Store:
+                       if ins.Val == obj {
+                               return false
+                       }
+                       continue // Otherwise other object is stored
+               default: // consider live if the object is used in any other instruction
+                       return false
+               }
+       }
+       return true
+}
+
+// isStructOrArray returns whether the underlying type is struct or array.
+func isStructOrArray(tp types.Type) bool {
+       if named, ok := tp.(*types.Named); ok {
+               tp = named.Underlying()
+       }
+       switch tp.(type) {
+       case *types.Array:
+               return true
+       case *types.Struct:
+               return true
+       }
+       return false
+}
+
+// hasStructOrArrayType returns whether a value is of struct or array type.
+func hasStructOrArrayType(v ssa.Value) bool {
+       if instr, ok := v.(ssa.Instruction); ok {
+               if alloc, ok := instr.(*ssa.Alloc); ok {
+                       // Check the element type of an allocated register (which always has pointer type)
+                       // e.g., for
+                       //   func (t T) f() { ...}
+                       // the receiver object is of type *T:
+                       //   t0 = local T (t)   *T
+                       if tp, ok := alloc.Type().(*types.Pointer); ok {
+                               return isStructOrArray(tp.Elem())
+                       }
+                       return false
+               }
+       }
+       return isStructOrArray(v.Type())
+}
+
+// getFieldName returns the name of a field in a struct.
+// It the field is not found, then it returns the string format of the index.
+//
+// For example, for struct T {x int, y int), getFieldName(*T, 1) returns "y".
+func getFieldName(tp types.Type, index int) string {
+       if pt, ok := tp.(*types.Pointer); ok {
+               tp = pt.Elem()
+       }
+       if named, ok := tp.(*types.Named); ok {
+               tp = named.Underlying()
+       }
+       if stp, ok := tp.(*types.Struct); ok {
+               return stp.Field(index).Name()
+       }
+       return fmt.Sprintf("%d", index)
+}