Skip to content

Commit 4774c62

Browse files
author
Charith Ellawala
committed
Fix issues #1 and #2
1 parent 44bf2ee commit 4774c62

3 files changed

Lines changed: 46 additions & 19 deletions

File tree

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Duration Check
33

44
A Go linter to detect cases where two `time.Duration` values are being multiplied in possibly erroneous ways.
55

6-
For example, consider the following snippet:
6+
For example, consider the following (highly contrived) function:
77

88
```go
99
func waitFor(someDuration time.Duration) {
@@ -12,11 +12,13 @@ func waitFor(someDuration time.Duration) {
1212
}
1313
```
1414

15-
Although the above code would compile without any errors, the behaviour is most likely to be incorrect. A caller would
16-
reasonably expect `waitFor(5 * time.Seconds)` to wait for ~5 seconds but they would end up waiting for ~1,388,889 hours.
15+
Although the above code would compile without any errors, its runtime behaviour would almost certainly be incorrect.
16+
A caller would reasonably expect `waitFor(5 * time.Seconds)` to wait for ~5 seconds but they would actually end up
17+
waiting for ~1,388,889 hours.
1718

18-
A majority of these problems would be spotted almost immediately but some could still slip through unnoticed. Hopefully
19-
this linter will help catch those rare cases before they cause a production issue.
19+
The above example is just for illustration purposes only. The problem is glaringly obvious in such a simple function
20+
and even the greenest Gopher would discover the issue immediately. However, imagine a much more complicated function
21+
with many more lines and it is not inconceivable that such logic errors could go unnoticed.
2022

2123
See the [test cases](testdata/src/a/a.go) for more examples of the types of errors detected by the linter.
2224

durationcheck.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,27 @@ func isDuration(x types.Type) bool {
8080
// isUnacceptableExpr returns true if the argument is not an acceptable time.Duration expression
8181
func isUnacceptableExpr(pass *analysis.Pass, expr ast.Expr) bool {
8282
switch e := expr.(type) {
83-
case *ast.BasicLit: // constants are acceptable
83+
case *ast.BasicLit:
8484
return false
85-
case *ast.CallExpr: // explicit casting of constants such as `time.Duration(10)` is acceptable
85+
case *ast.CallExpr:
8686
return !isAcceptableCast(pass, e)
87+
case *ast.BinaryExpr:
88+
return !isAcceptableNestedExpr(pass, e)
89+
case *ast.UnaryExpr:
90+
return !isAcceptableNestedExpr(pass, e)
8791
}
8892
return true
8993
}
9094

91-
// isAcceptableCast returns true if the argument is a constant expression cast to time.Duration
95+
// isAcceptableCast returns true if the argument is an acceptable expression cast to time.Duration
9296
func isAcceptableCast(pass *analysis.Pass, e *ast.CallExpr) bool {
9397
// check that there's a single argument
9498
if len(e.Args) != 1 {
9599
return false
96100
}
97101

98102
// check that the argument is acceptable
99-
if !isAcceptableCastArg(pass, e.Args[0]) {
103+
if !isAcceptableNestedExpr(pass, e.Args[0]) {
100104
return false
101105
}
102106

@@ -106,6 +110,10 @@ func isAcceptableCast(pass *analysis.Pass, e *ast.CallExpr) bool {
106110
return false
107111
}
108112

113+
return isDurationCast(selector)
114+
}
115+
116+
func isDurationCast(selector *ast.SelectorExpr) bool {
109117
pkg, ok := selector.X.(*ast.Ident)
110118
if !ok {
111119
return false
@@ -118,16 +126,22 @@ func isAcceptableCast(pass *analysis.Pass, e *ast.CallExpr) bool {
118126
return selector.Sel.Name == "Duration"
119127
}
120128

121-
func isAcceptableCastArg(pass *analysis.Pass, n ast.Expr) bool {
129+
func isAcceptableNestedExpr(pass *analysis.Pass, n ast.Expr) bool {
122130
switch e := n.(type) {
123131
case *ast.BasicLit:
124132
return true
125133
case *ast.BinaryExpr:
126-
return isAcceptableCastArg(pass, e.X) && isAcceptableCastArg(pass, e.Y)
127-
default:
128-
argType := pass.TypesInfo.TypeOf(n)
129-
return !isDuration(argType)
134+
return isAcceptableNestedExpr(pass, e.X) && isAcceptableNestedExpr(pass, e.Y)
135+
case *ast.UnaryExpr:
136+
return isAcceptableNestedExpr(pass, e.X)
137+
case *ast.Ident:
138+
t := pass.TypesInfo.TypeOf(e)
139+
return !isDuration(t)
140+
case *ast.CallExpr:
141+
t := pass.TypesInfo.TypeOf(e)
142+
return !isDuration(t)
130143
}
144+
return false
131145
}
132146

133147
func formatNode(node ast.Node) string {
@@ -140,8 +154,8 @@ func formatNode(node ast.Node) string {
140154
return buf.String()
141155
}
142156

143-
func printAST(node ast.Node) {
144-
fmt.Printf(">>> %s\n", formatNode(node))
157+
func printAST(msg string, node ast.Node) {
158+
fmt.Printf(">>> %s:\n%s\n\n\n", msg, formatNode(node))
145159
ast.Fprint(os.Stdout, nil, node, nil)
146160
fmt.Println("--------------")
147161
}

testdata/src/a/a.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import (
66

77
const timeout = 10 * time.Second
88

9-
func multiplyTwoDurations() {
10-
x := 30 * time.Second
9+
func validCases() {
1110
y := 10
1211

1312
_ = time.Second * 30
@@ -20,6 +19,14 @@ func multiplyTwoDurations() {
2019

2120
_ = time.Second * time.Duration(10+20*5)
2221

22+
_ = 2 * 24 * time.Hour
23+
24+
_ = time.Hour * 2 * 24
25+
26+
_ = -1 * time.Hour
27+
28+
_ = time.Hour * -1
29+
2330
_ = time.Duration(y) * time.Second
2431

2532
_ = time.Second * time.Duration(y)
@@ -29,13 +36,17 @@ func multiplyTwoDurations() {
2936
_ = time.Millisecond * time.Duration(someDurationMillis())
3037

3138
_ = timeout / time.Millisecond
39+
}
3240

33-
_ = timeout * time.Millisecond // want `Multiplication of durations`
41+
func invalidCases() {
42+
x := 30 * time.Second
3443

3544
_ = x * time.Second // want `Multiplication of durations`
3645

3746
_ = time.Second * x // want `Multiplication of durations`
3847

48+
_ = timeout * time.Millisecond // want `Multiplication of durations`
49+
3950
_ = someDuration() * time.Second // want `Multiplication of durations`
4051

4152
_ = time.Millisecond * someDuration() // want `Multiplication of durations`

0 commit comments

Comments
 (0)