Skip to content

Commit bf12eb7

Browse files
ashurbekovzgopherbot
authored andcommitted
gopls/internal/analysis/modernize: fix slicedelete triggers on slice identifiers with side effects
Add a check that the expression defining the slice has no side effects to trigger slicedelete. This is a necessary condition to ensure that the change does not change the program behavior. Fixes golang/go#72955 Change-Id: Ic326baa37e0b621fa7ba204bbfeb61c3e7daea47 GitHub-Last-Rev: 54e9082 GitHub-Pull-Request: #567 Reviewed-on: https://go-review.googlesource.com/c/tools/+/659295 Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Alan Donovan <adonovan@google.com>
1 parent ec542a7 commit bf12eb7

File tree

6 files changed

+78
-18
lines changed

6 files changed

+78
-18
lines changed

gopls/internal/analysis/modernize/modernize.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,41 @@ func enabledCategory(filter, category string) bool {
187187
}
188188
return exclude
189189
}
190+
191+
// noEffects reports whether the expression has no side effects, i.e., it
192+
// does not modify the memory state. This function is conservative: it may
193+
// return false even when the expression has no effect.
194+
func noEffects(info *types.Info, expr ast.Expr) bool {
195+
noEffects := true
196+
ast.Inspect(expr, func(n ast.Node) bool {
197+
switch v := n.(type) {
198+
case nil, *ast.Ident, *ast.BasicLit, *ast.BinaryExpr, *ast.ParenExpr,
199+
*ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.TypeAssertExpr,
200+
*ast.StarExpr, *ast.CompositeLit, *ast.ArrayType, *ast.StructType,
201+
*ast.MapType, *ast.InterfaceType, *ast.KeyValueExpr:
202+
// No effect
203+
case *ast.UnaryExpr:
204+
// Channel send <-ch has effects
205+
if v.Op == token.ARROW {
206+
noEffects = false
207+
}
208+
case *ast.CallExpr:
209+
// Type conversion has no effects
210+
if !info.Types[v].IsType() {
211+
// TODO(adonovan): Add a case for built-in functions without side
212+
// effects (by using callsPureBuiltin from tools/internal/refactor/inline)
213+
214+
noEffects = false
215+
}
216+
case *ast.FuncLit:
217+
// A FuncLit has no effects, but do not descend into it.
218+
return false
219+
default:
220+
// All other expressions have effects
221+
noEffects = false
222+
}
223+
224+
return noEffects
225+
})
226+
return noEffects
227+
}

gopls/internal/analysis/modernize/slices.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ func appendclipped(pass *analysis.Pass) {
210210
// x[:len(x):len(x)] (nonempty) res=x
211211
// x[:k:k] (nonempty)
212212
// slices.Clip(x) (nonempty) res=x
213+
//
214+
// TODO(adonovan): Add a check that the expression x has no side effects in
215+
// case x[:len(x):len(x)] -> x. Now the program behavior may change.
213216
func clippedSlice(info *types.Info, e ast.Expr) (res ast.Expr, empty bool) {
214217
switch e := e.(type) {
215218
case *ast.SliceExpr:

gopls/internal/analysis/modernize/slicescontains.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ import (
4646
// It may change cardinality of effects of the "needle" expression.
4747
// (Mostly this appears to be a desirable optimization, avoiding
4848
// redundantly repeated evaluation.)
49+
//
50+
// TODO(adonovan): Add a check that needle/predicate expression from
51+
// if-statement has no effects. Now the program behavior may change.
4952
func slicescontains(pass *analysis.Pass) {
5053
// Skip the analyzer in packages where its
5154
// fixes would create an import cycle.

gopls/internal/analysis/modernize/slicesdelete.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func slicesdelete(pass *analysis.Pass) {
9494
slice2, ok2 := call.Args[1].(*ast.SliceExpr)
9595
if ok1 && slice1.Low == nil && !slice1.Slice3 &&
9696
ok2 && slice2.High == nil && !slice2.Slice3 &&
97-
equalSyntax(slice1.X, slice2.X) &&
97+
equalSyntax(slice1.X, slice2.X) && noEffects(info, slice1.X) &&
9898
increasingSliceIndices(info, slice1.High, slice2.Low) {
9999
// Have append(s[:a], s[b:]...) where we can verify a < b.
100100
report(file, call, slice1, slice2)

gopls/internal/analysis/modernize/testdata/src/slicesdelete/slicesdelete.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package slicesdelete
22

33
var g struct{ f []int }
44

5+
func h() []int { return []int{} }
6+
7+
var ch chan []int
8+
59
func slicesdelete(test, other []byte, i int) {
610
const k = 1
711
_ = append(test[:i], test[i+1:]...) // want "Replace append with slices.Delete"
@@ -26,6 +30,10 @@ func slicesdelete(test, other []byte, i int) {
2630

2731
_ = append(g.f[:i], g.f[i+k:]...) // want "Replace append with slices.Delete"
2832

33+
_ = append(h()[:i], h()[i+1:]...) // potentially has side effects
34+
35+
_ = append((<-ch)[:i], (<-ch)[i+1:]...) // has side effects
36+
2937
_ = append(test[:3], test[i+1:]...) // cannot verify a < b
3038

3139
_ = append(test[:i-4], test[i-1:]...) // want "Replace append with slices.Delete"

gopls/internal/analysis/modernize/testdata/src/slicesdelete/slicesdelete.go.golden

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,43 @@ import "slices"
44

55
var g struct{ f []int }
66

7+
func h() []int { return []int{} }
8+
9+
var ch chan []int
10+
711
func slicesdelete(test, other []byte, i int) {
8-
const k = 1
9-
_ = slices.Delete(test, i, i+1) // want "Replace append with slices.Delete"
12+
const k = 1
13+
_ = slices.Delete(test, i, i+1) // want "Replace append with slices.Delete"
14+
15+
_ = slices.Delete(test, i+1, i+2) // want "Replace append with slices.Delete"
16+
17+
_ = append(test[:i+1], test[i+1:]...) // not deleting any slice elements
1018

11-
_ = slices.Delete(test, i+1, i+2) // want "Replace append with slices.Delete"
19+
_ = append(test[:i], test[i-1:]...) // not deleting any slice elements
1220

13-
_ = append(test[:i+1], test[i+1:]...) // not deleting any slice elements
21+
_ = slices.Delete(test, i-1, i) // want "Replace append with slices.Delete"
1422

15-
_ = append(test[:i], test[i-1:]...) // not deleting any slice elements
23+
_ = slices.Delete(test, i-2, i+1) // want "Replace append with slices.Delete"
1624

17-
_ = slices.Delete(test, i-1, i) // want "Replace append with slices.Delete"
25+
_ = append(test[:i-2], other[i+1:]...) // different slices "test" and "other"
1826

19-
_ = slices.Delete(test, i-2, i+1) // want "Replace append with slices.Delete"
27+
_ = append(test[:i-2], other[i+1+k:]...) // cannot verify a < b
2028

21-
_ = append(test[:i-2], other[i+1:]...) // different slices "test" and "other"
29+
_ = append(test[:i-2], test[11:]...) // cannot verify a < b
2230

23-
_ = append(test[:i-2], other[i+1+k:]...) // cannot verify a < b
31+
_ = slices.Delete(test, 1, 3) // want "Replace append with slices.Delete"
2432

25-
_ = append(test[:i-2], test[11:]...) // cannot verify a < b
33+
_ = slices.Delete(g.f, i, i+k) // want "Replace append with slices.Delete"
2634

27-
_ = slices.Delete(test, 1, 3) // want "Replace append with slices.Delete"
35+
_ = append(h()[:i], h()[i+1:]...) // potentially has side effects
2836

29-
_ = slices.Delete(g.f, i, i+k) // want "Replace append with slices.Delete"
37+
_ = append((<-ch)[:i], (<-ch)[i+1:]...) // has side effects
3038

31-
_ = append(test[:3], test[i+1:]...) // cannot verify a < b
39+
_ = append(test[:3], test[i+1:]...) // cannot verify a < b
3240

33-
_ = slices.Delete(test, i-4, i-1) // want "Replace append with slices.Delete"
41+
_ = slices.Delete(test, i-4, i-1) // want "Replace append with slices.Delete"
3442

35-
_ = slices.Delete(test, 1+2, 3+4) // want "Replace append with slices.Delete"
43+
_ = slices.Delete(test, 1+2, 3+4) // want "Replace append with slices.Delete"
3644

37-
_ = append(test[:1+2], test[i-1:]...) // cannot verify a < b
38-
}
45+
_ = append(test[:1+2], test[i-1:]...) // cannot verify a < b
46+
}

0 commit comments

Comments
 (0)