mirror of
				https://github.com/mgechev/revive.git
				synced 2025-10-30 23:37:49 +02:00 
			
		
		
		
	New rule: atomic (#77)
This commit is contained in:
		| @@ -273,6 +273,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a | ||||
| | `imports-blacklist`   | []string | Disallows importing the specified packages                     |    no    |  no   | | ||||
| | `range-val-in-closure`|  n/a   | Warns if range value is used in a closure dispatched as goroutine|    no    |  no   | | ||||
| | `waitgroup-by-value  `|  n/a   | Warns on functions taking sync.WaitGroup as a by-value parameter |    no    |  no   | | ||||
| | `atomic`              |  n/a   | Check for common mistaken usages of the `sync/atomic` package    |    no    |  no   | | ||||
|  | ||||
| ## Configurable rules | ||||
|  | ||||
|   | ||||
| @@ -69,6 +69,7 @@ var allRules = append([]lint.Rule{ | ||||
| 	&rule.MaxPublicStructsRule{}, | ||||
| 	&rule.RangeValInClosureRule{}, | ||||
| 	&rule.WaitGroupByCopyRule{}, | ||||
|   &rule.AtomicRule{}, | ||||
| }, defaultRules...) | ||||
|  | ||||
| var allFormatters = []lint.Formatter{ | ||||
|   | ||||
							
								
								
									
										45
									
								
								fixtures/atomic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								fixtures/atomic.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| package fixtures | ||||
|  | ||||
| import ( | ||||
| 	"sync/atomic" | ||||
| ) | ||||
|  | ||||
| type Counter uint64 | ||||
|  | ||||
| func AtomicTests() { | ||||
| 	x := uint64(1) | ||||
| 	x = atomic.AddUint64(&x, 1)        // MATCH /direct assignment to atomic value/ | ||||
| 	_, x = 10, atomic.AddUint64(&x, 1) // MATCH /direct assignment to atomic value/ | ||||
| 	x, _ = atomic.AddUint64(&x, 1), 10 // MATCH /direct assignment to atomic value/ | ||||
|  | ||||
| 	y := &x | ||||
| 	*y = atomic.AddUint64(y, 1) // MATCH /direct assignment to atomic value/ | ||||
|  | ||||
| 	var su struct{ Counter uint64 } | ||||
| 	su.Counter = atomic.AddUint64(&su.Counter, 1) // MATCH /direct assignment to atomic value/ | ||||
| 	z1 := atomic.AddUint64(&su.Counter, 1) | ||||
| 	_ = z1 // Avoid err "z declared and not used" | ||||
|  | ||||
| 	var sp struct{ Counter *uint64 } | ||||
| 	*sp.Counter = atomic.AddUint64(sp.Counter, 1) // MATCH /direct assignment to atomic value/ | ||||
| 	z2 := atomic.AddUint64(sp.Counter, 1) | ||||
| 	_ = z2 // Avoid err "z declared and not used" | ||||
|  | ||||
| 	au := []uint64{10, 20} | ||||
| 	au[0] = atomic.AddUint64(&au[0], 1) // MATCH /direct assignment to atomic value/ | ||||
| 	au[1] = atomic.AddUint64(&au[0], 1) | ||||
|  | ||||
| 	ap := []*uint64{&au[0], &au[1]} | ||||
| 	*ap[0] = atomic.AddUint64(ap[0], 1) // MATCH /direct assignment to atomic value/ | ||||
| 	*ap[1] = atomic.AddUint64(ap[0], 1) | ||||
| } | ||||
|  | ||||
| type T struct{} | ||||
|  | ||||
| func (T) AddUint64(addr *uint64, delta uint64) uint64 { return 0 } | ||||
|  | ||||
| func NonAtomic() { | ||||
| 	x := uint64(1) | ||||
| 	var atomic T | ||||
| 	x = atomic.AddUint64(&x, 1) // MATCH /direct assignment to atomic value/ | ||||
| } | ||||
							
								
								
									
										105
									
								
								rule/atomic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								rule/atomic.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| package rule | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"go/ast" | ||||
| 	"go/printer" | ||||
| 	"go/token" | ||||
| 	"go/types" | ||||
|  | ||||
| 	"github.com/mgechev/revive/lint" | ||||
| ) | ||||
|  | ||||
| // AtomicRule lints given else constructs. | ||||
| type AtomicRule struct{} | ||||
|  | ||||
| // Apply applies the rule to given file. | ||||
| func (r *AtomicRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { | ||||
| 	var failures []lint.Failure | ||||
| 	walker := atomic{ | ||||
| 		file: file, | ||||
| 		onFailure: func(failure lint.Failure) { | ||||
| 			failures = append(failures, failure) | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	ast.Walk(walker, file.AST) | ||||
|  | ||||
| 	return failures | ||||
| } | ||||
|  | ||||
| // Name returns the rule name. | ||||
| func (r *AtomicRule) Name() string { | ||||
| 	return "atomic" | ||||
| } | ||||
|  | ||||
| type atomic struct { | ||||
| 	file      *lint.File | ||||
| 	onFailure func(lint.Failure) | ||||
| } | ||||
|  | ||||
| func (w atomic) Visit(node ast.Node) ast.Visitor { | ||||
| 	n, ok := node.(*ast.AssignStmt) | ||||
| 	if !ok { | ||||
| 		return w | ||||
| 	} | ||||
|  | ||||
| 	if len(n.Lhs) != len(n.Rhs) { | ||||
| 		return w | ||||
| 	} | ||||
| 	if len(n.Lhs) == 1 && n.Tok == token.DEFINE { | ||||
| 		return w | ||||
| 	} | ||||
|  | ||||
| 	for i, right := range n.Rhs { | ||||
| 		call, ok := right.(*ast.CallExpr) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		sel, ok := call.Fun.(*ast.SelectorExpr) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		pkgIdent, _ := sel.X.(*ast.Ident) | ||||
| 		if w.file.Pkg.TypesInfo != nil { | ||||
| 			pkgName, ok := w.file.Pkg.TypesInfo.Uses[pkgIdent].(*types.PkgName) | ||||
| 			if !ok || pkgName.Imported().Path() != "sync/atomic" { | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		switch sel.Sel.Name { | ||||
| 		case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": | ||||
| 			left := n.Lhs[i] | ||||
| 			if len(call.Args) != 2 { | ||||
| 				continue | ||||
| 			} | ||||
| 			arg := call.Args[0] | ||||
| 			broken := false | ||||
|  | ||||
| 			if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND { | ||||
| 				broken = w.gofmt(left) == w.gofmt(uarg.X) | ||||
| 			} else if star, ok := left.(*ast.StarExpr); ok { | ||||
| 				broken = w.gofmt(star.X) == w.gofmt(arg) | ||||
| 			} | ||||
|  | ||||
| 			if broken { | ||||
| 				w.onFailure(lint.Failure{ | ||||
| 					Confidence: 1, | ||||
| 					Failure:    fmt.Sprintf("direct assignment to atomic value"), | ||||
| 					Node:       n, | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| // gofmt returns a string representation of the expression. | ||||
| func (w atomic) gofmt(x ast.Expr) string { | ||||
| 	buf := bytes.Buffer{} | ||||
| 	fs := token.NewFileSet() | ||||
| 	printer.Fprint(&buf, fs, x) | ||||
| 	return buf.String() | ||||
| } | ||||
							
								
								
									
										12
									
								
								test/atomic_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								test/atomic_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| package test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/mgechev/revive/rule" | ||||
| ) | ||||
|  | ||||
| // Atomic rule. | ||||
| func TestAtomic(t *testing.T) { | ||||
| 	testRule(t, "atomic", &rule.AtomicRule{}) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user