internal/lsp: pass options by reference instead of by value

We were previously behaving as though the slice/map values in the
options struct could be modified directly. The options should be cloned
before modification. Also, convert any usage of source.Options to
*source.Options.

Fixes golang/go#39592

Change-Id: Ib39f668bca0fa1038162206bd7793fd2049af576
Reviewed-on: https://go-review.googlesource.com/c/tools/+/254558
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/internal/hooks/analysis.go b/gopls/internal/hooks/analysis.go
index 4eb042d..1ee1557 100644
--- a/gopls/internal/hooks/analysis.go
+++ b/gopls/internal/hooks/analysis.go
@@ -34,10 +34,6 @@
 	// Always add hooks for all available analyzers, but disable them if the
 	// user does not have staticcheck enabled (they may enable it later on).
 	for _, a := range analyzers {
-		addStaticcheckAnalyzer(options, a)
+		options.AddStaticcheckAnalyzer(a)
 	}
 }
-
-func addStaticcheckAnalyzer(options *source.Options, a *analysis.Analyzer) {
-	options.StaticcheckAnalyzers[a.Name] = source.Analyzer{Analyzer: a, Enabled: true}
-}
diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
index da23186..5e54c8b 100644
--- a/internal/lsp/cache/cache.go
+++ b/internal/lsp/cache/cache.go
@@ -115,10 +115,14 @@
 
 func (c *Cache) NewSession(ctx context.Context) *Session {
 	index := atomic.AddInt64(&sessionIndex, 1)
+	options := source.DefaultOptions().Clone()
+	if c.options != nil {
+		c.options(options)
+	}
 	s := &Session{
 		cache:       c,
 		id:          strconv.FormatInt(index, 10),
-		options:     source.DefaultOptions(),
+		options:     options,
 		overlays:    make(map[span.URI]*overlay),
 		gocmdRunner: &gocommand.Runner{},
 	}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 141fc27..070bc5f 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -27,7 +27,7 @@
 	cache *Cache
 	id    string
 
-	options source.Options
+	options *source.Options
 
 	viewMu  sync.Mutex
 	views   []*View
@@ -117,11 +117,11 @@
 func (s *Session) ID() string     { return s.id }
 func (s *Session) String() string { return s.id }
 
-func (s *Session) Options() source.Options {
+func (s *Session) Options() *source.Options {
 	return s.options
 }
 
-func (s *Session) SetOptions(options source.Options) {
+func (s *Session) SetOptions(options *source.Options) {
 	s.options = options
 }
 
@@ -140,7 +140,7 @@
 	return s.cache
 }
 
-func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) (source.View, source.Snapshot, func(), error) {
+func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options *source.Options) (source.View, source.Snapshot, func(), error) {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 	view, snapshot, release, err := s.createView(ctx, name, folder, options, 0)
@@ -153,7 +153,7 @@
 	return view, snapshot, release, nil
 }
 
-func (s *Session) createView(ctx context.Context, name string, folder span.URI, options source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
+func (s *Session) createView(ctx context.Context, name string, folder span.URI, options *source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
 	index := atomic.AddInt64(&viewIndex, 1)
 	// We want a true background context and not a detached context here
 	// the spans need to be unrelated and no tag values should pollute it.
@@ -197,7 +197,7 @@
 	}
 
 	if v.session.cache.options != nil {
-		v.session.cache.options(&v.options)
+		v.session.cache.options(v.options)
 	}
 
 	// Set the module-specific information.
@@ -253,7 +253,7 @@
 //
 // It assumes that the caller has not yet created the view, and therefore does
 // not lock any of the internal data structures before accessing them.
-func (v *View) findWorkspaceModules(ctx context.Context, options source.Options) error {
+func (v *View) findWorkspaceModules(ctx context.Context, options *source.Options) error {
 	// If the user is intentionally limiting their workspace scope, add their
 	// folder to the roots and return early.
 	if !options.ExpandWorkspaceToModule {
@@ -431,7 +431,7 @@
 	return nil
 }
 
-func (s *Session) updateView(ctx context.Context, view *View, options source.Options) (*View, error) {
+func (s *Session) updateView(ctx context.Context, view *View, options *source.Options) (*View, error) {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 	i, err := s.dropView(ctx, view)
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 4ec760f..2ed8d62 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -40,7 +40,7 @@
 	id      string
 
 	optionsMu sync.Mutex
-	options   source.Options
+	options   *source.Options
 
 	// mu protects most mutable state of the view.
 	mu sync.Mutex
@@ -278,13 +278,13 @@
 	return v.folder
 }
 
-func (v *View) Options() source.Options {
+func (v *View) Options() *source.Options {
 	v.optionsMu.Lock()
 	defer v.optionsMu.Unlock()
 	return v.options
 }
 
-func minorOptionsChange(a, b source.Options) bool {
+func minorOptionsChange(a, b *source.Options) bool {
 	// Check if any of the settings that modify our understanding of files have been changed
 	mapEnv := func(env []string) map[string]string {
 		m := make(map[string]string, len(env))
@@ -315,7 +315,7 @@
 	return true
 }
 
-func (v *View) SetOptions(ctx context.Context, options source.Options) (source.View, error) {
+func (v *View) SetOptions(ctx context.Context, options *source.Options) (source.View, error) {
 	// no need to rebuild the view if the options were not materially changed
 	v.optionsMu.Lock()
 	if minorOptionsChange(v.options, options) {
@@ -804,7 +804,7 @@
 	v.initializeOnce = &once
 }
 
-func (v *View) setBuildInformation(ctx context.Context, options source.Options) error {
+func (v *View) setBuildInformation(ctx context.Context, options *source.Options) error {
 	if err := checkPathCase(v.Folder().Filename()); err != nil {
 		return errors.Errorf("invalid workspace configuration: %w", err)
 	}
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index 35fea61..02b5ca7 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -211,9 +211,9 @@
 	case strings.HasPrefix(app.Remote, "internal@"):
 		internalMu.Lock()
 		defer internalMu.Unlock()
-		opts := source.DefaultOptions()
+		opts := source.DefaultOptions().Clone()
 		if app.options != nil {
-			app.options(&opts)
+			app.options(opts)
 		}
 		key := fmt.Sprintf("%s %v", app.wd, opts)
 		if c := internalConnections[key]; c != nil {
@@ -271,9 +271,9 @@
 	params.Capabilities.Workspace.Configuration = true
 
 	// Make sure to respect configured options when sending initialize request.
-	opts := source.DefaultOptions()
+	opts := source.DefaultOptions().Clone()
 	if options != nil {
-		options(&opts)
+		options(opts)
 	}
 	params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
 		ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index 9f12051..9ce8dca 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -104,7 +104,7 @@
 	}, nil
 }
 
-func toProtocolCompletionItems(candidates []completion.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
+func toProtocolCompletionItems(candidates []completion.CompletionItem, rng protocol.Range, options *source.Options) []protocol.CompletionItem {
 	var (
 		items                  = make([]protocol.CompletionItem, 0, len(candidates))
 		numDeepCompletionsSeen int
diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go
index 7c6df27..55e4e8f 100644
--- a/internal/lsp/completion_test.go
+++ b/internal/lsp/completion_test.go
@@ -123,8 +123,8 @@
 		t.Fatal(err)
 	}
 	original := view.Options()
-	modified := original
-	options(&modified)
+	modified := view.Options().Clone()
+	options(modified)
 	view, err = view.SetOptions(r.ctx, modified)
 	if err != nil {
 		t.Error(err)
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index 397e6b6..af868e7 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -39,7 +39,7 @@
 	options := s.session.Options()
 	defer func() { s.session.SetOptions(options) }()
 
-	if err := s.handleOptionResults(ctx, source.SetOptions(&options, params.InitializationOptions)); err != nil {
+	if err := s.handleOptionResults(ctx, source.SetOptions(options, params.InitializationOptions)); err != nil {
 		return nil, err
 	}
 	options.ForClientCapabilities(params.Capabilities)
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index eccc56b..b829001 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -50,8 +50,8 @@
 
 		cache := cache.New(ctx, nil)
 		session := cache.NewSession(ctx)
-		options := source.DefaultOptions()
-		tests.DefaultOptions(&options)
+		options := source.DefaultOptions().Clone()
+		tests.DefaultOptions(options)
 		session.SetOptions(options)
 		options.Env = datum.Config.Env
 		view, _, release, err := session.NewView(ctx, datum.Config.Dir, span.URIFromPath(datum.Config.Dir), options)
@@ -64,7 +64,7 @@
 
 		// Enable type error analyses for tests.
 		// TODO(golang/go#38212): Delete this once they are enabled by default.
-		tests.EnableAllAnalyzers(view, &options)
+		tests.EnableAllAnalyzers(view, options)
 		view.SetOptions(ctx, options)
 
 		// Only run the -modfile specific tests in module mode with Go 1.14 or above.
diff --git a/internal/lsp/mod/hover.go b/internal/lsp/mod/hover.go
index 8401c61..63d9a69 100644
--- a/internal/lsp/mod/hover.go
+++ b/internal/lsp/mod/hover.go
@@ -104,7 +104,7 @@
 	}, nil
 }
 
-func formatExplanation(text string, req *modfile.Require, options source.Options, isPrivate bool) string {
+func formatExplanation(text string, req *modfile.Require, options *source.Options, isPrivate bool) string {
 	text = strings.TrimSuffix(text, "\n")
 	splt := strings.Split(text, "\n")
 	length := len(splt)
diff --git a/internal/lsp/mod/mod_test.go b/internal/lsp/mod/mod_test.go
index 1103cd8..c9d3e63 100644
--- a/internal/lsp/mod/mod_test.go
+++ b/internal/lsp/mod/mod_test.go
@@ -28,8 +28,8 @@
 	ctx := tests.Context(t)
 	cache := cache.New(ctx, nil)
 	session := cache.NewSession(ctx)
-	options := source.DefaultOptions()
-	tests.DefaultOptions(&options)
+	options := source.DefaultOptions().Clone()
+	tests.DefaultOptions(options)
 	options.TempModfile = true
 	options.Env = []string{"GOPACKAGESDRIVER=off", "GOROOT="}
 
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 9f1b091..5558d7c 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -221,7 +221,7 @@
 		// meant to provide diagnostics, but rather only suggested fixes.
 		// Skip these types of errors in diagnostics; we will use their
 		// suggested fixes when providing code actions.
-		if isConvenienceAnalyzer(e.Category) {
+		if isConvenienceAnalyzer(snapshot.View().Options(), e.Category) {
 			continue
 		}
 		// This is a bit of a hack, but clients > 3.15 will be able to grey out unnecessary code.
@@ -312,7 +312,7 @@
 	return false
 }
 
-func isConvenienceAnalyzer(category string) bool {
+func isConvenienceAnalyzer(o *Options, category string) bool {
 	for _, a := range DefaultOptions().ConvenienceAnalyzers {
 		if category == a.Analyzer.Name {
 			return true
diff --git a/internal/lsp/source/gc_annotations.go b/internal/lsp/source/gc_annotations.go
index 4eb4faf..2d85026 100644
--- a/internal/lsp/source/gc_annotations.go
+++ b/internal/lsp/source/gc_annotations.go
@@ -57,7 +57,7 @@
 		if x == nil {
 			continue
 		}
-		v = filterDiagnostics(v, &opts)
+		v = filterDiagnostics(v, opts)
 		reports[x.VersionedFileIdentity()] = v
 	}
 	return reports, parseError
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index 7e4a10c..b2cba20 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -373,7 +373,7 @@
 	return &HoverInformation{source: obj, comment: decl.Doc}
 }
 
-func FormatHover(h *HoverInformation, options Options) (string, error) {
+func FormatHover(h *HoverInformation, options *Options) (string, error) {
 	signature := h.Signature
 	if signature != "" && options.PreferredContentFormat == protocol.Markdown {
 		signature = fmt.Sprintf("```go\n%s\n```", signature)
@@ -403,7 +403,7 @@
 	return "", errors.Errorf("no hover for %v", h.source)
 }
 
-func formatLink(h *HoverInformation, options Options) string {
+func formatLink(h *HoverInformation, options *Options) string {
 	if !options.LinksInHover || options.LinkTarget == "" || h.Link == "" {
 		return ""
 	}
@@ -418,14 +418,14 @@
 	}
 }
 
-func formatDoc(doc string, options Options) string {
+func formatDoc(doc string, options *Options) string {
 	if options.PreferredContentFormat == protocol.Markdown {
 		return CommentToMarkdown(doc)
 	}
 	return doc
 }
 
-func formatHover(options Options, x ...string) string {
+func formatHover(options *Options, x ...string) string {
 	var b strings.Builder
 	for i, el := range x {
 		if el != "" {
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index c70c2a5..ffcfb1d 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -9,8 +9,10 @@
 	"fmt"
 	"regexp"
 	"strings"
+	"sync"
 	"time"
 
+	"golang.org/x/tools/go/analysis"
 	"golang.org/x/tools/go/analysis/passes/asmdecl"
 	"golang.org/x/tools/go/analysis/passes/assign"
 	"golang.org/x/tools/go/analysis/passes/atomic"
@@ -52,79 +54,87 @@
 	errors "golang.org/x/xerrors"
 )
 
+var (
+	optionsOnce    sync.Once
+	defaultOptions *Options
+)
+
 //go:generate go run golang.org/x/tools/cmd/stringer -output enums_string.go -type ImportShortcut,HoverKind,Matcher,SymbolMatcher,SymbolStyle
 //go:generate go run golang.org/x/tools/internal/lsp/source/genopts -output options_json.go
 
 // DefaultOptions is the options that are used for Gopls execution independent
 // of any externally provided configuration (LSP initialization, command
 // invokation, etc.).
-func DefaultOptions() Options {
-	var commands []string
-	for _, c := range Commands {
-		commands = append(commands, c.Name)
-	}
-	return Options{
-		ClientOptions: ClientOptions{
-			InsertTextFormat:                  protocol.PlainTextTextFormat,
-			PreferredContentFormat:            protocol.Markdown,
-			ConfigurationSupported:            true,
-			DynamicConfigurationSupported:     true,
-			DynamicWatchedFilesSupported:      true,
-			LineFoldingOnly:                   false,
-			HierarchicalDocumentSymbolSupport: true,
-		},
-		ServerOptions: ServerOptions{
-			SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
-				Go: {
-					protocol.SourceFixAll:          true,
-					protocol.SourceOrganizeImports: true,
-					protocol.QuickFix:              true,
-					protocol.RefactorRewrite:       true,
-					protocol.RefactorExtract:       true,
-				},
-				Mod: {
-					protocol.SourceOrganizeImports: true,
-				},
-				Sum: {},
+func DefaultOptions() *Options {
+	optionsOnce.Do(func() {
+		var commands []string
+		for _, c := range Commands {
+			commands = append(commands, c.Name)
+		}
+		defaultOptions = &Options{
+			ClientOptions: ClientOptions{
+				InsertTextFormat:                  protocol.PlainTextTextFormat,
+				PreferredContentFormat:            protocol.Markdown,
+				ConfigurationSupported:            true,
+				DynamicConfigurationSupported:     true,
+				DynamicWatchedFilesSupported:      true,
+				LineFoldingOnly:                   false,
+				HierarchicalDocumentSymbolSupport: true,
 			},
-			SupportedCommands: commands,
-		},
-		UserOptions: UserOptions{
-			HoverKind:  FullDocumentation,
-			LinkTarget: "pkg.go.dev",
-		},
-		DebuggingOptions: DebuggingOptions{
-			CompletionBudget:   100 * time.Millisecond,
-			LiteralCompletions: true,
-		},
-		ExperimentalOptions: ExperimentalOptions{
-			TempModfile:             true,
-			ExpandWorkspaceToModule: true,
-			Codelens: map[string]bool{
-				CommandGenerate.Name:          true,
-				CommandRegenerateCgo.Name:     true,
-				CommandTidy.Name:              true,
-				CommandToggleDetails.Name:     false,
-				CommandUpgradeDependency.Name: true,
-				CommandVendor.Name:            true,
+			ServerOptions: ServerOptions{
+				SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
+					Go: {
+						protocol.SourceFixAll:          true,
+						protocol.SourceOrganizeImports: true,
+						protocol.QuickFix:              true,
+						protocol.RefactorRewrite:       true,
+						protocol.RefactorExtract:       true,
+					},
+					Mod: {
+						protocol.SourceOrganizeImports: true,
+					},
+					Sum: {},
+				},
+				SupportedCommands: commands,
 			},
-			LinksInHover:            true,
-			CompleteUnimported:      true,
-			CompletionDocumentation: true,
-			DeepCompletion:          true,
-			Matcher:                 Fuzzy,
-			SymbolMatcher:           SymbolFuzzy,
-		},
-		Hooks: Hooks{
-			ComputeEdits:         myers.ComputeEdits,
-			URLRegexp:            urlRegexp(),
-			DefaultAnalyzers:     defaultAnalyzers(),
-			TypeErrorAnalyzers:   typeErrorAnalyzers(),
-			ConvenienceAnalyzers: convenienceAnalyzers(),
-			StaticcheckAnalyzers: map[string]Analyzer{},
-			GoDiff:               true,
-		},
-	}
+			UserOptions: UserOptions{
+				HoverKind:  FullDocumentation,
+				LinkTarget: "pkg.go.dev",
+			},
+			DebuggingOptions: DebuggingOptions{
+				CompletionBudget:   100 * time.Millisecond,
+				LiteralCompletions: true,
+			},
+			ExperimentalOptions: ExperimentalOptions{
+				TempModfile:             true,
+				ExpandWorkspaceToModule: true,
+				Codelens: map[string]bool{
+					CommandGenerate.Name:          true,
+					CommandRegenerateCgo.Name:     true,
+					CommandTidy.Name:              true,
+					CommandToggleDetails.Name:     false,
+					CommandUpgradeDependency.Name: true,
+					CommandVendor.Name:            true,
+				},
+				LinksInHover:            true,
+				CompleteUnimported:      true,
+				CompletionDocumentation: true,
+				DeepCompletion:          true,
+				Matcher:                 Fuzzy,
+				SymbolMatcher:           SymbolFuzzy,
+			},
+			Hooks: Hooks{
+				ComputeEdits:         myers.ComputeEdits,
+				URLRegexp:            urlRegexp(),
+				DefaultAnalyzers:     defaultAnalyzers(),
+				TypeErrorAnalyzers:   typeErrorAnalyzers(),
+				ConvenienceAnalyzers: convenienceAnalyzers(),
+				StaticcheckAnalyzers: map[string]Analyzer{},
+				GoDiff:               true,
+			},
+		}
+	})
+	return defaultOptions
 }
 
 // Options holds various configuration that affects Gopls execution, organized
@@ -206,11 +216,11 @@
 	GoDiff               bool
 	ComputeEdits         diff.ComputeEdits
 	URLRegexp            *regexp.Regexp
+	GofumptFormat        func(ctx context.Context, src []byte) ([]byte, error)
 	DefaultAnalyzers     map[string]Analyzer
 	TypeErrorAnalyzers   map[string]Analyzer
 	ConvenienceAnalyzers map[string]Analyzer
 	StaticcheckAnalyzers map[string]Analyzer
-	GofumptFormat        func(ctx context.Context, src []byte) ([]byte, error)
 }
 
 // ExperimentalOptions defines configuration for features under active
@@ -475,6 +485,59 @@
 	o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
 }
 
+func (o *Options) Clone() *Options {
+	result := &Options{
+		ClientOptions:       o.ClientOptions,
+		DebuggingOptions:    o.DebuggingOptions,
+		ExperimentalOptions: o.ExperimentalOptions,
+		Hooks: Hooks{
+			GoDiff:        o.Hooks.GoDiff,
+			ComputeEdits:  o.Hooks.ComputeEdits,
+			GofumptFormat: o.GofumptFormat,
+			URLRegexp:     o.URLRegexp,
+		},
+		ServerOptions: o.ServerOptions,
+		UserOptions:   o.UserOptions,
+	}
+	// Fully clone any slice or map fields. Only Hooks, ExperimentalOptions,
+	// and UserOptions can be modified.
+	copyStringMap := func(src map[string]bool) map[string]bool {
+		dst := make(map[string]bool)
+		for k, v := range src {
+			dst[k] = v
+		}
+		return dst
+	}
+	result.Analyses = copyStringMap(o.Analyses)
+	result.Annotations = copyStringMap(o.Annotations)
+	result.Codelens = copyStringMap(o.Codelens)
+
+	copySlice := func(src []string) []string {
+		dst := make([]string, len(src))
+		copy(dst, src)
+		return dst
+	}
+	result.Env = copySlice(o.Env)
+	result.BuildFlags = copySlice(o.BuildFlags)
+
+	copyAnalyzerMap := func(src map[string]Analyzer) map[string]Analyzer {
+		dst := make(map[string]Analyzer)
+		for k, v := range src {
+			dst[k] = v
+		}
+		return dst
+	}
+	result.DefaultAnalyzers = copyAnalyzerMap(o.DefaultAnalyzers)
+	result.TypeErrorAnalyzers = copyAnalyzerMap(o.TypeErrorAnalyzers)
+	result.ConvenienceAnalyzers = copyAnalyzerMap(o.ConvenienceAnalyzers)
+	result.StaticcheckAnalyzers = copyAnalyzerMap(o.StaticcheckAnalyzers)
+	return result
+}
+
+func (options *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer) {
+	options.StaticcheckAnalyzers[a.Name] = Analyzer{Analyzer: a, Enabled: true}
+}
+
 func (o *Options) set(name string, value interface{}) OptionResult {
 	result := OptionResult{Name: name, Value: value}
 	switch name {
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 60081a0..ce790bb 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -52,8 +52,8 @@
 
 		cache := cache.New(ctx, nil)
 		session := cache.NewSession(ctx)
-		options := source.DefaultOptions()
-		tests.DefaultOptions(&options)
+		options := source.DefaultOptions().Clone()
+		tests.DefaultOptions(options)
 		options.Env = datum.Config.Env
 		view, _, release, err := session.NewView(ctx, "source_test", span.URIFromPath(datum.Config.Dir), options)
 		release()
@@ -64,7 +64,7 @@
 
 		// Enable type error analyses for tests.
 		// TODO(golang/go#38212): Delete this once they are enabled by default.
-		tests.EnableAllAnalyzers(view, &options)
+		tests.EnableAllAnalyzers(view, options)
 		view.SetOptions(ctx, options)
 		var modifications []source.FileModification
 		for filename, content := range datum.Config.Overlay {
@@ -295,8 +295,8 @@
 		t.Fatal(err)
 	}
 	original := r.view.Options()
-	modified := original
-	options(&modified)
+	modified := original.Clone()
+	options(modified)
 	newView, err := r.view.SetOptions(r.ctx, modified)
 	if newView != r.view {
 		t.Fatalf("options change unexpectedly created new view")
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 20ee4b5..0ab43fd 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -153,13 +153,13 @@
 	RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error
 
 	// Options returns a copy of the Options for this view.
-	Options() Options
+	Options() *Options
 
 	// SetOptions sets the options of this view to new values.
 	// Calling this may cause the view to be invalidated and a replacement view
 	// added to the session. If so the new view will be returned, otherwise the
 	// original one will be.
-	SetOptions(context.Context, Options) (View, error)
+	SetOptions(context.Context, *Options) (View, error)
 
 	// Snapshot returns the current snapshot for the view.
 	Snapshot(ctx context.Context) (Snapshot, func())
@@ -222,7 +222,7 @@
 // A session may have many active views at any given time.
 type Session interface {
 	// NewView creates a new View, returning it and its first snapshot.
-	NewView(ctx context.Context, name string, folder span.URI, options Options) (View, Snapshot, func(), error)
+	NewView(ctx context.Context, name string, folder span.URI, options *Options) (View, Snapshot, func(), error)
 
 	// Cache returns the cache that created this session, for debugging only.
 	Cache() interface{}
@@ -250,10 +250,10 @@
 	Overlays() []Overlay
 
 	// Options returns a copy of the SessionOptions for this session.
-	Options() Options
+	Options() *Options
 
 	// SetOptions sets the options of this session to new values.
-	SetOptions(Options)
+	SetOptions(*Options)
 }
 
 // Overlay is the type for a file held in memory on a session.
diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go
index 82b7b07..6d77d92 100644
--- a/internal/lsp/workspace.go
+++ b/internal/lsp/workspace.go
@@ -33,9 +33,8 @@
 	if state < serverInitialized {
 		return nil, nil, func() {}, errors.Errorf("addView called before server initialized")
 	}
-
-	options := s.session.Options()
-	if err := s.fetchConfig(ctx, name, uri, &options); err != nil {
+	options := s.session.Options().Clone()
+	if err := s.fetchConfig(ctx, name, uri, options); err != nil {
 		return nil, nil, func() {}, err
 	}
 	return s.session.NewView(ctx, name, uri, options)
@@ -44,8 +43,8 @@
 func (s *Server) didChangeConfiguration(ctx context.Context, changed interface{}) error {
 	// go through all the views getting the config
 	for _, view := range s.session.Views() {
-		options := view.Options()
-		if err := s.fetchConfig(ctx, view.Name(), view.Folder(), &options); err != nil {
+		options := s.session.Options().Clone()
+		if err := s.fetchConfig(ctx, view.Name(), view.Folder(), options); err != nil {
 			return err
 		}
 		view, err := view.SetOptions(ctx, options)