mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00: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:
|
case *ast.GoStmt:
|
||||||
stmt.Call = call
|
stmt.Call = call
|
||||||
}
|
}
|
||||||
switch parent := parent.(type) {
|
|
||||||
case *ast.BlockStmt:
|
if !replaceNode(parent, bad, stmt) {
|
||||||
for i, s := range parent.List {
|
return errors.Errorf("couldn't replace %T in %T", stmt, parent)
|
||||||
if s == bad {
|
|
||||||
parent.List[i] = stmt
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,3 +440,56 @@ func offsetPositions(expr ast.Expr, offset token.Pos) {
|
|||||||
return true
|
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)
|
defer foo.F //@complete(" //", Foo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
switch true {
|
||||||
|
case true:
|
||||||
|
go foo.F //@complete(" //", Foo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
defer func() {
|
defer func() {
|
||||||
foo.F //@complete(" //", Foo),snippet(" //", Foo, "Foo()", "Foo()")
|
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
|
// 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.
|
// are being executed. If a test is added, this number must be changed.
|
||||||
const (
|
const (
|
||||||
ExpectedCompletionsCount = 164
|
ExpectedCompletionsCount = 165
|
||||||
ExpectedCompletionSnippetCount = 35
|
ExpectedCompletionSnippetCount = 35
|
||||||
ExpectedDiagnosticsCount = 21
|
ExpectedDiagnosticsCount = 21
|
||||||
ExpectedFormatCount = 6
|
ExpectedFormatCount = 6
|
||||||
|
Loading…
x
Reference in New Issue
Block a user