mirror of
https://github.com/golang/go
synced 2024-11-18 16:04:44 -07:00
internal/lsp: fix infinite recursion while fixing AST
We were recursing infinitely in cases like this: switch true { case true: go foo.F<> } There were three things that came together to cause this: 1. We recently starting recursively fixing broken go/defer statements. 2. In this case we were failing to swap in the correct ast.Node in for the *ast.BadStmt because we were only looking for *ast.BlockStmt (and *ast.CaseStmt has no block). 3. After 2), we weren't returning an error so the fix() code thought it should recurse. Fix 2) by using reflection to swap AST nodes in a generic way. Perhaps a bit overkill in this case, but I happened to have already written this for an upcoming change, so I just pulled it in to fix this bug. Fix 3) by returning an error if we fail to swap the AST nodes. Fixes golang/go#34353. Change-Id: I17ff1afd52ae165c0ba9de5820dcec4cb7d756cb Reviewed-on: https://go-review.googlesource.com/c/tools/+/196137 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
7460b8e10b
commit
f68e2b6f23
65
internal/lsp/cache/parse.go
vendored
65
internal/lsp/cache/parse.go
vendored
@ -402,15 +402,11 @@ FindTo:
|
||||
case *ast.GoStmt:
|
||||
stmt.Call = call
|
||||
}
|
||||
switch parent := parent.(type) {
|
||||
case *ast.BlockStmt:
|
||||
for i, s := range parent.List {
|
||||
if s == bad {
|
||||
parent.List[i] = stmt
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !replaceNode(parent, bad, stmt) {
|
||||
return errors.Errorf("couldn't replace %T in %T", stmt, parent)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -444,3 +440,56 @@ func offsetPositions(expr ast.Expr, offset token.Pos) {
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// replaceNode updates parent's child oldChild to be newChild. It
|
||||
// retuns whether it replaced successfully.
|
||||
func replaceNode(parent, oldChild, newChild ast.Node) bool {
|
||||
if parent == nil || oldChild == nil || newChild == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
parentVal := reflect.ValueOf(parent).Elem()
|
||||
if parentVal.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
|
||||
newChildVal := reflect.ValueOf(newChild)
|
||||
|
||||
tryReplace := func(v reflect.Value) bool {
|
||||
if !v.CanSet() || !v.CanInterface() {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the existing value is oldChild, we found our child. Make
|
||||
// sure our newChild is assignable and then make the swap.
|
||||
if v.Interface() == oldChild && newChildVal.Type().AssignableTo(v.Type()) {
|
||||
v.Set(newChildVal)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Loop over parent's struct fields.
|
||||
for i := 0; i < parentVal.NumField(); i++ {
|
||||
f := parentVal.Field(i)
|
||||
|
||||
switch f.Kind() {
|
||||
// Check interface and pointer fields.
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
if tryReplace(f) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Search through any slice fields.
|
||||
case reflect.Slice:
|
||||
for i := 0; i < f.Len(); i++ {
|
||||
if tryReplace(f.Index(i)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
7
internal/lsp/testdata/badstmt/badstmt.go.in
vendored
7
internal/lsp/testdata/badstmt/badstmt.go.in
vendored
@ -10,6 +10,13 @@ func _() {
|
||||
defer foo.F //@complete(" //", Foo)
|
||||
}
|
||||
|
||||
func _() {
|
||||
switch true {
|
||||
case true:
|
||||
go foo.F //@complete(" //", Foo)
|
||||
}
|
||||
}
|
||||
|
||||
func _() {
|
||||
defer func() {
|
||||
foo.F //@complete(" //", Foo),snippet(" //", Foo, "Foo()", "Foo()")
|
||||
|
@ -30,7 +30,7 @@ import (
|
||||
// We hardcode the expected number of test cases to ensure that all tests
|
||||
// are being executed. If a test is added, this number must be changed.
|
||||
const (
|
||||
ExpectedCompletionsCount = 164
|
||||
ExpectedCompletionsCount = 165
|
||||
ExpectedCompletionSnippetCount = 35
|
||||
ExpectedDiagnosticsCount = 21
|
||||
ExpectedFormatCount = 6
|
||||
|
Loading…
Reference in New Issue
Block a user