From b53d4cbd60a6d7a92fac9b4a58de426c07a5666d Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Wed, 28 Oct 2020 22:47:46 -0400 Subject: [PATCH] internal/lsp/cache: check for symlinks when checking "isSubdirectory" This change copies the logic from the go command's inDir function (https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/search/search.go;drc=3931cc113f3f3e7d484842d6e4f53b7a78311e8e;l=570) to replace gopls's "isSubdirectory" function. This function resolves symlinks, which isSubdirectory did not previously do. The only adjustments are to flip the arguments to match the previous signature of isSubdirectory and to return a boolean instead of a string. Fixes golang/go#38558 Change-Id: I9c64604222ac277eae81a4111eef432ead887e9f Reviewed-on: https://go-review.googlesource.com/c/tools/+/266200 Trust: Rebecca Stambler Run-TryBot: Rebecca Stambler gopls-CI: kokoro TryBot-Result: Go Bot Reviewed-by: Heschi Kreinick --- internal/lsp/cache/snapshot.go | 4 +- internal/lsp/cache/view.go | 77 +++++++++++++++++++++++++++++++-- internal/lsp/cache/workspace.go | 2 +- 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go index b691ab3063..d916de3c35 100644 --- a/internal/lsp/cache/snapshot.go +++ b/internal/lsp/cache/snapshot.go @@ -694,7 +694,7 @@ func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Pac func (s *snapshot) GoModForFile(ctx context.Context, uri span.URI) span.URI { var match span.URI for modURI := range s.workspace.activeModFiles() { - if !isSubdirectory(dirURI(modURI).Filename(), uri.Filename()) { + if !inDir(dirURI(modURI).Filename(), uri.Filename()) { continue } if len(modURI) > len(match) { @@ -1377,7 +1377,7 @@ func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *sn } // If a go.mod in the workspace has been changed, invalidate metadata. if kind := originalFH.Kind(); kind == source.Mod { - return isSubdirectory(filepath.Dir(s.view.rootURI.Filename()), filepath.Dir(originalFH.URI().Filename())) + return inDir(filepath.Dir(s.view.rootURI.Filename()), filepath.Dir(originalFH.URI().Filename())) } // Get the original and current parsed files in order to check package name // and imports. Use the new snapshot to parse to avoid modifying the diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 84a5499efb..f27d8ca1af 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -698,16 +698,85 @@ func validBuildConfiguration(folder span.URI, ws *workspaceInformation, modFiles // The user may have a multiple directories in their GOPATH. // Check if the workspace is within any of them. for _, gp := range filepath.SplitList(ws.gopath) { - if isSubdirectory(filepath.Join(gp, "src"), folder.Filename()) { + if inDir(filepath.Join(gp, "src"), folder.Filename()) { return true } } return false } -func isSubdirectory(root, leaf string) bool { - rel, err := filepath.Rel(root, leaf) - return err == nil && !strings.HasPrefix(rel, "..") +// Copied and slightly adjusted from go/src/cmd/go/internal/search/search.go. +// +// inDir checks whether path is in the file tree rooted at dir. +// If so, InDir returns an equivalent path relative to dir. +// If not, InDir returns an empty string. +// InDir makes some effort to succeed even in the presence of symbolic links. +func inDir(dir, path string) bool { + if rel := inDirLex(path, dir); rel != "" { + return true + } + xpath, err := filepath.EvalSymlinks(path) + if err != nil || xpath == path { + xpath = "" + } else { + if rel := inDirLex(xpath, dir); rel != "" { + return true + } + } + + xdir, err := filepath.EvalSymlinks(dir) + if err == nil && xdir != dir { + if rel := inDirLex(path, xdir); rel != "" { + return true + } + if xpath != "" { + if rel := inDirLex(xpath, xdir); rel != "" { + return true + } + } + } + return false +} + +// Copied from go/src/cmd/go/internal/search/search.go. +// +// inDirLex is like inDir but only checks the lexical form of the file names. +// It does not consider symbolic links. +// TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to +// return the suffix. Most uses of str.HasFilePathPrefix should probably +// be calling InDir instead. +func inDirLex(path, dir string) string { + pv := strings.ToUpper(filepath.VolumeName(path)) + dv := strings.ToUpper(filepath.VolumeName(dir)) + path = path[len(pv):] + dir = dir[len(dv):] + switch { + default: + return "" + case pv != dv: + return "" + case len(path) == len(dir): + if path == dir { + return "." + } + return "" + case dir == "": + return path + case len(path) > len(dir): + if dir[len(dir)-1] == filepath.Separator { + if path[:len(dir)] == dir { + return path[len(dir):] + } + return "" + } + if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir { + if len(path) == len(dir)+1 { + return "." + } + return path[len(dir)+1:] + } + return "" + } } // getGoEnv gets the view's various GO* values. diff --git a/internal/lsp/cache/workspace.go b/internal/lsp/cache/workspace.go index 71d3842329..673843a57a 100644 --- a/internal/lsp/cache/workspace.go +++ b/internal/lsp/cache/workspace.go @@ -258,7 +258,7 @@ func (wm *workspace) invalidate(ctx context.Context, changes map[span.URI]*fileC // Legacy mode only considers a module a workspace root. continue } - if !isSubdirectory(wm.root.Filename(), uri.Filename()) { + if !inDir(wm.root.Filename(), uri.Filename()) { // Otherwise, the module must be contained within the workspace root. continue }