1
0
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:
Muir Manders 2019-09-17 21:20:55 -07:00 committed by Rebecca Stambler
parent 7460b8e10b
commit f68e2b6f23
3 changed files with 65 additions and 9 deletions

View File

@ -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
}

View File

@ -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()")

View File

@ -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