gopls/internal/golang: fix crash in ExtractToNewFile
The crash in ExtractToFile is caused by a known
bug in the parser (#57490): when a declaration at EOF
is not correctly terminated, the parser error recovery
will add the missing closing token, which makes the end
position of that node one more than EOF.
Example:
func foo() { var x // FuncDecl.End() = EOF + 1
Then in ExtractToFile, end - fileStart is out of bounds.
In CL 559436, we added a workaround so that methods
that depend on offsets/positions will adjust the values
if needed to avoid this out of bounds issue.
If we use safetoken.Offsets to calculate the bounds
instead of directly using
pgf.Src[start-fileStart : end-fileStart]
then the offsets are adjusted for us. Instead of
reporting a bug for the out of bounds error,
ExtractToFile will fail to format the extracted
source and return a more relevant error to the user.
Fixes golang/go#70553
Change-Id: I2ef7f2d5e880e3e73e04da3f68c689887571d973
Reviewed-on: https://go-review.googlesource.com/c/tools/+/798422
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Madeline Kalil <mkalil@google.com>
diff --git a/gopls/internal/golang/extracttofile.go b/gopls/internal/golang/extracttofile.go
index 9bd3d5b..054220f 100644
--- a/gopls/internal/golang/extracttofile.go
+++ b/gopls/internal/golang/extracttofile.go
@@ -98,8 +98,6 @@
if !ok {
return nil, fmt.Errorf("invalid selection")
}
- pgf.CheckPos(start) // #70553
- // Inv: start is valid wrt pgf.Tok.
// select trailing empty lines
offset, err := safetoken.Offset(pgf.Tok, end)
@@ -109,21 +107,10 @@
rest := pgf.Src[offset:]
spaces := len(rest) - len(bytes.TrimLeft(rest, " \t\n"))
end += token.Pos(spaces)
- pgf.CheckPos(end) // #70553
- if !(start <= end) {
- bug.Reportf("start: not before end")
+ src, err := pgf.PosText(start, end)
+ if err != nil {
+ return nil, err
}
- // Inv: end is valid wrt pgf.Tok; env >= start.
- fileStart := pgf.File.FileStart
- pgf.CheckPos(fileStart) // #70553
- if !(0 <= start-fileStart) {
- bug.Reportf("start: out of bounds")
- }
- if !(int(end-fileStart) <= len(pgf.Src)) {
- bug.Reportf("end: out of bounds")
- }
- // Inv: 0 <= start-fileStart <= end-fileStart <= len(Src).
- src := pgf.Src[start-fileStart : end-fileStart]
replaceRange, err := pgf.PosRange(start, end)
if err != nil {
@@ -180,7 +167,7 @@
newFileContent, err := format.Source(buf.Bytes())
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to format extracted source: %w", err)
}
return []protocol.DocumentChange{
diff --git a/gopls/internal/test/marker/testdata/codeaction/extracttofile.txt b/gopls/internal/test/marker/testdata/codeaction/extracttofile.txt
index 5577b5e..950bde1 100644
--- a/gopls/internal/test/marker/testdata/codeaction/extracttofile.txt
+++ b/gopls/internal/test/marker/testdata/codeaction/extracttofile.txt
@@ -2,6 +2,7 @@
-- flags --
-ignore_extra_diags
+-errors_ok
-- go.mod --
module golang.org/lsptests/extracttofile
@@ -349,3 +350,10 @@
-// docs
-const C = "" //@codeaction("const", "refactor.extract.toNewFile", edit=copyrightandbuildconstraint)
+//@codeaction("const", "refactor.extract.toNewFile", edit=copyrightandbuildconstraint)
+
+-- parsererror.go --
+package main
+
+func foo() { //@codeaction("foo", "refactor.extract.toNewFile", err=re"failed to format extracted source")
+ for i := struct
+