Skip to content

Commit 6a05055

Browse files
committed
go/ast: fix bug handling the result of yield in Preorder
Once yield returns false, ast.Preorder must not call yield on any more nodes. Even after the function passed to ast.Inspect returns false, it may be invoked again with a non-nil node. Therefore, we must explicitly truncate the inspection. For #66339 Change-Id: I2b01e4e96a2d7aca785467c15ab59da13208c161 Reviewed-on: https://go-review.googlesource.com/c/go/+/585520 Reviewed-by: Alan Donovan <adonovan@google.com> Reviewed-by: Robert Griesemer <gri@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent db7bb27 commit 6a05055

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

src/go/ast/walk.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,9 @@ func Preorder(root Node) iter.Seq[Node] {
381381
return func(yield func(Node) bool) {
382382
ok := true
383383
Inspect(root, func(n Node) bool {
384-
if n != nil && !yield(n) {
385-
ok = false
384+
if n != nil {
385+
// yield must not be called once ok is false.
386+
ok = ok && yield(n)
386387
}
387388
return ok
388389
})

src/go/ast/walk_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package ast_test
6+
7+
import (
8+
"go/ast"
9+
"go/parser"
10+
"go/token"
11+
"testing"
12+
)
13+
14+
func TestPreorderBreak(t *testing.T) {
15+
// This test checks that Preorder correctly handles a break statement while
16+
// in the middle of walking a node. Previously, incorrect handling of the
17+
// boolean returned by the yield function resulted in the iterator calling
18+
// yield for sibling nodes even after yield had returned false. With that
19+
// bug, this test failed with a runtime panic.
20+
src := "package p\ntype T struct {\n\tF int `json:\"f\"` // a field\n}\n"
21+
22+
fset := token.NewFileSet()
23+
f, err := parser.ParseFile(fset, "", src, 0)
24+
if err != nil {
25+
panic(err)
26+
}
27+
28+
for n := range ast.Preorder(f) {
29+
if id, ok := n.(*ast.Ident); ok && id.Name == "F" {
30+
break
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)