mirror of
				https://github.com/mgechev/revive.git
				synced 2025-10-30 23:37:49 +02:00 
			
		
		
		
	add rule datarace (#683)
This commit is contained in:
		| @@ -482,6 +482,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a | |||||||
| | [`banned-characters`](./RULES_DESCRIPTIONS.md#banned-characters)          |  n/a   |  Checks banned characters in identifiers |    no    |  no   | | | [`banned-characters`](./RULES_DESCRIPTIONS.md#banned-characters)          |  n/a   |  Checks banned characters in identifiers |    no    |  no   | | ||||||
| | [`optimize-operands-order`](./RULES_DESCRIPTIONS.md#optimize-operands-order)          |  n/a   |  Checks inefficient conditional expressions |    no    |  no   | | | [`optimize-operands-order`](./RULES_DESCRIPTIONS.md#optimize-operands-order)          |  n/a   |  Checks inefficient conditional expressions |    no    |  no   | | ||||||
| | [`use-any`](./RULES_DESCRIPTIONS.md#use-any)          |  n/a   |  Proposes to replace `interface{}` with its alias `any` |    no    |  no   | | | [`use-any`](./RULES_DESCRIPTIONS.md#use-any)          |  n/a   |  Proposes to replace `interface{}` with its alias `any` |    no    |  no   | | ||||||
|  | | [`datarace`](./RULES_DESCRIPTIONS.md#datarace)          |  n/a   |  Spots potential dataraces |    no    |  no   | | ||||||
|  |  | ||||||
| ## Configurable rules | ## Configurable rules | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ List of all available rules. | |||||||
|   - [context-as-argument](#context-as-argument) |   - [context-as-argument](#context-as-argument) | ||||||
|   - [context-keys-type](#context-keys-type) |   - [context-keys-type](#context-keys-type) | ||||||
|   - [cyclomatic](#cyclomatic) |   - [cyclomatic](#cyclomatic) | ||||||
|  |   - [datarace](#datarace) | ||||||
|   - [deep-exit](#deep-exit) |   - [deep-exit](#deep-exit) | ||||||
|   - [defer](#defer) |   - [defer](#defer) | ||||||
|   - [dot-imports](#dot-imports) |   - [dot-imports](#dot-imports) | ||||||
| @@ -217,6 +218,11 @@ Example: | |||||||
| [rule.cyclomatic] | [rule.cyclomatic] | ||||||
|   arguments =[3] |   arguments =[3] | ||||||
| ``` | ``` | ||||||
|  | ## datarace | ||||||
|  |  | ||||||
|  | _Description_: This rule spots potential dataraces caused by go-routines capturing (by-reference) particular identifiers of the function from which go-routines are created. The rule is able to spot two of such cases: go-routines capturing named return values, and capturing `for-range` values. | ||||||
|  |  | ||||||
|  | _Configuration_: N/A | ||||||
|  |  | ||||||
| ## deep-exit | ## deep-exit | ||||||
|  |  | ||||||
|   | |||||||
| @@ -85,6 +85,7 @@ var allRules = append([]lint.Rule{ | |||||||
| 	&rule.BannedCharsRule{}, | 	&rule.BannedCharsRule{}, | ||||||
| 	&rule.OptimizeOperandsOrderRule{}, | 	&rule.OptimizeOperandsOrderRule{}, | ||||||
| 	&rule.UseAnyRule{}, | 	&rule.UseAnyRule{}, | ||||||
|  | 	&rule.DataRaceRule{}, | ||||||
| }, defaultRules...) | }, defaultRules...) | ||||||
|  |  | ||||||
| var allFormatters = []lint.Formatter{ | var allFormatters = []lint.Formatter{ | ||||||
|   | |||||||
							
								
								
									
										142
									
								
								rule/datarace.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								rule/datarace.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | |||||||
|  | package rule | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  |  | ||||||
|  | 	"github.com/mgechev/revive/lint" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // DataRaceRule lints assignments to value method-receivers. | ||||||
|  | type DataRaceRule struct{} | ||||||
|  |  | ||||||
|  | // Apply applies the rule to given file. | ||||||
|  | func (*DataRaceRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { | ||||||
|  | 	var failures []lint.Failure | ||||||
|  | 	onFailure := func(failure lint.Failure) { | ||||||
|  | 		failures = append(failures, failure) | ||||||
|  | 	} | ||||||
|  | 	w := lintDataRaces{onFailure: onFailure} | ||||||
|  |  | ||||||
|  | 	ast.Walk(w, file.AST) | ||||||
|  |  | ||||||
|  | 	return failures | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Name returns the rule name. | ||||||
|  | func (*DataRaceRule) Name() string { | ||||||
|  | 	return "datarace" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type lintDataRaces struct { | ||||||
|  | 	onFailure func(failure lint.Failure) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w lintDataRaces) Visit(n ast.Node) ast.Visitor { | ||||||
|  | 	node, ok := n.(*ast.FuncDecl) | ||||||
|  | 	if !ok { | ||||||
|  | 		return w // not function declaration | ||||||
|  | 	} | ||||||
|  | 	if node.Body == nil { | ||||||
|  | 		return nil // empty body | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	results := node.Type.Results | ||||||
|  |  | ||||||
|  | 	returnIDs := map[*ast.Object]struct{}{} | ||||||
|  | 	if results != nil { | ||||||
|  | 		returnIDs = w.ExtractReturnIDs(results.List) | ||||||
|  | 	} | ||||||
|  | 	fl := &lintFunctionForDataRaces{onFailure: w.onFailure, returnIDs: returnIDs, rangeIDs: map[*ast.Object]struct{}{}} | ||||||
|  | 	ast.Walk(fl, node.Body) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w lintDataRaces) ExtractReturnIDs(fields []*ast.Field) map[*ast.Object]struct{} { | ||||||
|  | 	r := map[*ast.Object]struct{}{} | ||||||
|  | 	for _, f := range fields { | ||||||
|  | 		for _, id := range f.Names { | ||||||
|  | 			r[id.Obj] = struct{}{} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type lintFunctionForDataRaces struct { | ||||||
|  | 	_         struct{} | ||||||
|  | 	onFailure func(failure lint.Failure) | ||||||
|  | 	returnIDs map[*ast.Object]struct{} | ||||||
|  | 	rangeIDs  map[*ast.Object]struct{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w lintFunctionForDataRaces) Visit(node ast.Node) ast.Visitor { | ||||||
|  | 	switch n := node.(type) { | ||||||
|  | 	case *ast.RangeStmt: | ||||||
|  | 		if n.Body == nil { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		getIds := func(exprs ...ast.Expr) []*ast.Ident { | ||||||
|  | 			r := []*ast.Ident{} | ||||||
|  | 			for _, expr := range exprs { | ||||||
|  | 				if id, ok := expr.(*ast.Ident); ok { | ||||||
|  | 					r = append(r, id) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return r | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		ids := getIds(n.Key, n.Value) | ||||||
|  | 		for _, id := range ids { | ||||||
|  | 			w.rangeIDs[id.Obj] = struct{}{} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		ast.Walk(w, n.Body) | ||||||
|  |  | ||||||
|  | 		for _, id := range ids { | ||||||
|  | 			delete(w.rangeIDs, id.Obj) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return nil // do not visit the body of the range, it has been already visited | ||||||
|  | 	case *ast.GoStmt: | ||||||
|  | 		f := n.Call.Fun | ||||||
|  | 		funcLit, ok := f.(*ast.FuncLit) | ||||||
|  | 		if !ok { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		selectIDs := func(n ast.Node) bool { | ||||||
|  | 			_, ok := n.(*ast.Ident) | ||||||
|  | 			return ok | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		ids := pick(funcLit.Body, selectIDs, nil) | ||||||
|  | 		for _, id := range ids { | ||||||
|  | 			id := id.(*ast.Ident) | ||||||
|  | 			_, isRangeID := w.rangeIDs[id.Obj] | ||||||
|  | 			_, isReturnID := w.returnIDs[id.Obj] | ||||||
|  |  | ||||||
|  | 			switch { | ||||||
|  | 			case isRangeID: | ||||||
|  | 				w.onFailure(lint.Failure{ | ||||||
|  | 					Confidence: 1, | ||||||
|  | 					Node:       id, | ||||||
|  | 					Category:   "logic", | ||||||
|  | 					Failure:    fmt.Sprintf("datarace: range value %s is captured (by-reference) in goroutine", id.Name), | ||||||
|  | 				}) | ||||||
|  | 			case isReturnID: | ||||||
|  | 				w.onFailure(lint.Failure{ | ||||||
|  | 					Confidence: 0.8, | ||||||
|  | 					Node:       id, | ||||||
|  | 					Category:   "logic", | ||||||
|  | 					Failure:    fmt.Sprintf("potential datarace: return value %s is captured (by-reference) in goroutine", id.Name), | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return w | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								test/datarace_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/datarace_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | package test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/mgechev/revive/rule" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestDatarace(t *testing.T) { | ||||||
|  | 	testRule(t, "datarace", &rule.DataRaceRule{}) | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								testdata/datarace.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								testdata/datarace.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | package fixtures | ||||||
|  |  | ||||||
|  | func datarace() (r int, c char) { | ||||||
|  | 	for _, p := range []int{1, 2} { | ||||||
|  | 		go func() { | ||||||
|  | 			print(r) // MATCH /potential datarace: return value r is captured (by-reference) in goroutine/ | ||||||
|  | 			print(p) // MATCH /datarace: range value p is captured (by-reference) in goroutine/ | ||||||
|  | 		}() | ||||||
|  | 		for i, p1 := range []int{1, 2} { | ||||||
|  | 			a := p1 | ||||||
|  | 			go func() { | ||||||
|  | 				print(r)  // MATCH /potential datarace: return value r is captured (by-reference) in goroutine/ | ||||||
|  | 				print(p)  // MATCH /datarace: range value p is captured (by-reference) in goroutine/ | ||||||
|  | 				print(p1) // MATCH /datarace: range value p1 is captured (by-reference) in goroutine/ | ||||||
|  | 				print(a) | ||||||
|  | 				print(i) // MATCH /datarace: range value i is captured (by-reference) in goroutine/ | ||||||
|  | 			}() | ||||||
|  | 			print(i) | ||||||
|  | 			print(p) | ||||||
|  | 			go func() { | ||||||
|  | 				_ = c // MATCH /potential datarace: return value c is captured (by-reference) in goroutine/ | ||||||
|  | 			}() | ||||||
|  | 		} | ||||||
|  | 		print(p1) | ||||||
|  | 	} | ||||||
|  | 	go func() { | ||||||
|  | 		print(r) // MATCH /potential datarace: return value r is captured (by-reference) in goroutine/ | ||||||
|  | 	}() | ||||||
|  | 	print(r) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user