internal/lsp: make sure that `gofmt -s` analyses don't modify AST

The code for `gofmt -s` directly modifies the AST, since the ASTs are
not long-lived. Some of this code made it into our analysis
implementations, causing very strange bugs to manifest. Added a
regression test for this specific case.

Fixes golang/go#38267

Change-Id: I235620adcbf2bbc7027c6d83ff2c7fe74729062e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/227299
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
(cherry picked from commit 1fd976651f4da6cc8c239c50463fb2d82641fa77)
Reviewed-on: https://go-review.googlesource.com/c/tools/+/227354
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/analysis/simplifycompositelit/simplifycompositelit.go b/internal/lsp/analysis/simplifycompositelit/simplifycompositelit.go
index 68b69ee..eae53db 100644
--- a/internal/lsp/analysis/simplifycompositelit/simplifycompositelit.go
+++ b/internal/lsp/analysis/simplifycompositelit/simplifycompositelit.go
@@ -62,33 +62,28 @@
 			ktyp = reflect.ValueOf(keyType)
 		}
 		typ := reflect.ValueOf(eltType)
-		for i, x := range outer.Elts {
-			px := &outer.Elts[i]
+		for _, x := range outer.Elts {
 			// look at value of indexed/named elements
 			if t, ok := x.(*ast.KeyValueExpr); ok {
 				if keyType != nil {
-					simplifyLiteral(pass, ktyp, keyType, t.Key, &t.Key)
+					simplifyLiteral(pass, ktyp, keyType, t.Key)
 				}
 				x = t.Value
-				px = &t.Value
 			}
-			simplifyLiteral(pass, typ, eltType, x, px)
+			simplifyLiteral(pass, typ, eltType, x)
 		}
 	})
 	return nil, nil
 }
 
-func simplifyLiteral(pass *analysis.Pass, typ reflect.Value, astType, x ast.Expr, px *ast.Expr) {
+func simplifyLiteral(pass *analysis.Pass, typ reflect.Value, astType, x ast.Expr) {
 	// if the element is a composite literal and its literal type
 	// matches the outer literal's element type exactly, the inner
 	// literal type may be omitted
 	if inner, ok := x.(*ast.CompositeLit); ok && match(typ, reflect.ValueOf(inner.Type)) {
-		old := inner.Type
-		inner.Type = nil
 		var b bytes.Buffer
-		printer.Fprint(&b, pass.Fset, old)
-		createDiagnostic(pass, old.Pos(), old.End(), b.String())
-		inner.Type = old
+		printer.Fprint(&b, pass.Fset, inner.Type)
+		createDiagnostic(pass, inner.Type.Pos(), inner.Type.End(), b.String())
 	}
 	// if the outer literal's element type is a pointer type *T
 	// and the element is & of a composite literal of type T,
@@ -97,15 +92,10 @@
 		if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND {
 			if inner, ok := addr.X.(*ast.CompositeLit); ok {
 				if match(reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) {
-					old := inner.Type
-					inner.Type = nil // drop T
-					*px = inner      // drop &
 					var b bytes.Buffer
-					printer.Fprint(&b, pass.Fset, old)
-					// Do old.Pos()-1 to account for the &.
-					createDiagnostic(pass, old.Pos()-1, old.End(), "&"+b.String())
-					inner.Type = old
-					*px = inner // add &
+					printer.Fprint(&b, pass.Fset, inner.Type)
+					// Account for the & by subtracting 1 from typ.Pos().
+					createDiagnostic(pass, inner.Type.Pos()-1, inner.Type.End(), "&"+b.String())
 				}
 			}
 		}
diff --git a/internal/lsp/analysis/simplifyrange/simplifyrange.go b/internal/lsp/analysis/simplifyrange/simplifyrange.go
index e53f50d..c9cb387 100644
--- a/internal/lsp/analysis/simplifyrange/simplifyrange.go
+++ b/internal/lsp/analysis/simplifyrange/simplifyrange.go
@@ -45,24 +45,26 @@
 		(*ast.RangeStmt)(nil),
 	}
 	inspect.Preorder(nodeFilter, func(n ast.Node) {
-		stmt := n.(*ast.RangeStmt)
-		end := newlineIndex(pass.Fset, stmt)
-		var old ast.Expr
+		var copy *ast.RangeStmt
+		if stmt, ok := n.(*ast.RangeStmt); ok {
+			x := *stmt
+			copy = &x
+		}
+		if copy == nil {
+			return
+		}
+		end := newlineIndex(pass.Fset, copy)
+
 		// Range statements of the form: for i, _ := range x {}
-		if isBlank(stmt.Value) {
-			old = stmt.Value
-			defer func() {
-				stmt.Value = old
-			}()
-			stmt.Value = nil
+		var old ast.Expr
+		if isBlank(copy.Value) {
+			old = copy.Value
+			copy.Value = nil
 		}
 		// Range statements of the form: for _ := range x {}
-		if isBlank(stmt.Key) && stmt.Value == nil {
-			old = stmt.Key
-			defer func() {
-				stmt.Key = old
-			}()
-			stmt.Key = nil
+		if isBlank(copy.Key) && copy.Value == nil {
+			old = copy.Key
+			copy.Key = nil
 		}
 		// Return early if neither if condition is met.
 		if old == nil {
@@ -72,7 +74,7 @@
 			Pos:            old.Pos(),
 			End:            old.End(),
 			Message:        "simplify range expression",
-			SuggestedFixes: suggestedFixes(pass.Fset, stmt, end),
+			SuggestedFixes: suggestedFixes(pass.Fset, copy, end),
 		})
 	})
 	return nil, nil
diff --git a/internal/lsp/analysis/simplifyslice/simplifyslice.go b/internal/lsp/analysis/simplifyslice/simplifyslice.go
index 01a6bc3..da1728e 100644
--- a/internal/lsp/analysis/simplifyslice/simplifyslice.go
+++ b/internal/lsp/analysis/simplifyslice/simplifyslice.go
@@ -74,23 +74,21 @@
 		if !ok || arg.Obj != s.Obj {
 			return
 		}
-		old := expr.High
 		var b bytes.Buffer
-		printer.Fprint(&b, pass.Fset, old)
+		printer.Fprint(&b, pass.Fset, expr.High)
 		pass.Report(analysis.Diagnostic{
-			Pos:     old.Pos(),
-			End:     old.End(),
+			Pos:     expr.High.Pos(),
+			End:     expr.High.End(),
 			Message: fmt.Sprintf("unneeded: %s", b.String()),
 			SuggestedFixes: []analysis.SuggestedFix{{
 				Message: fmt.Sprintf("Remove '%s'", b.String()),
 				TextEdits: []analysis.TextEdit{{
-					Pos:     old.Pos(),
-					End:     old.End(),
+					Pos:     expr.High.Pos(),
+					End:     expr.High.End(),
 					NewText: []byte{},
 				}},
 			}},
 		})
-		expr.High = old
 	})
 	return nil, nil
 }
diff --git a/internal/lsp/regtest/diagnostics_test.go b/internal/lsp/regtest/diagnostics_test.go
index 4739e04..b4a5867 100644
--- a/internal/lsp/regtest/diagnostics_test.go
+++ b/internal/lsp/regtest/diagnostics_test.go
@@ -194,3 +194,50 @@
 		})
 	})
 }
+
+const testPackage = `
+-- go.mod --
+module mod.com
+
+go 1.12
+-- lib.go --
+package lib
+
+func Hello(x string) {
+	_ = x
+}
+-- lib_test.go --
+package lib
+
+import "testing"
+
+type testStruct struct{
+	name string
+}
+
+func TestHello(t *testing.T) {
+	testStructs := []*testStruct{
+		&testStruct{"hello"},
+		&testStruct{"goodbye"},
+	}
+	for y := range testStructs {
+		_ = y
+	}
+}
+`
+
+func Test_Issue38267(t *testing.T) {
+	runner.Run(t, testPackage, func(env *Env) {
+		env.OpenFile("lib_test.go")
+		env.Await(
+			DiagnosticAt("lib_test.go", 10, 2),
+			DiagnosticAt("lib_test.go", 11, 2),
+		)
+		env.OpenFile("lib.go")
+		env.RegexpReplace("lib.go", "_ = x", "var y int")
+		env.Await(
+			env.DiagnosticAtRegexp("lib.go", "y int"),
+			EmptyDiagnostics("lib_test.go"),
+		)
+	})
+}