Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go/doc: New trashes the AST, even in PreserveAST mode #66453

Open
adonovan opened this issue Mar 21, 2024 · 2 comments
Open

go/doc: New trashes the AST, even in PreserveAST mode #66453

adonovan opened this issue Mar 21, 2024 · 2 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.

Comments

@adonovan
Copy link
Member

The doc.New function mutates the AST, even in PreserveAST mode. See https://go.dev/play/p/YSn-iNSBXsv for a minimal repro. This leads to crashes in gopls (e.g. #66449).

Here's one culprit; there may be others:

// fileExports removes unexported declarations from src in place.
func (r *reader) fileExports(src *ast.File) {
	j := 0
	for _, d := range src.Decls {
		if r.filterDecl(d) {
			src.Decls[j] = d
			j++
		}
	}
	src.Decls = src.Decls[0:j]
}
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/573575 mentions this issue: gopls/internal/golang: work around bug in go/doc

@adonovan
Copy link
Member Author

I had a quick crack at a fix, but it's more fiddly than I first thought. It's easy to shallow-copy the ast.File before calling (*reader).fileExports, and to change fileExports not to share the slice's backing array. The challenge is filterDecl: it mutates the underlying array of GenDecl.Specs; we can't shallow-copy the GenDecl too, because then its identity would have changed in Value.Decl, which has observable consequences (e.g. its type info would appear to be missing). Perhaps we need to implement the filtering later, when building up the index, rather than by AST mutation; but this too has observable consequences, and may affect syntax printing. I'm not sure there is a satisfactory solution.

In any case gopls now has a workaround. I've posted my test case below in case it helps:

func TestIssue66453(t *testing.T) {
	fset := token.NewFileSet()
	f, _ := parser.ParseFile(fset, "a.go", "package p; var v int", 0)
	pkg := &ast.Package{Name: "foo", Files: map[string]*ast.File{"a.go": f}}

	dump := func() string {
		var buf strings.Builder
		ast.Fprint(&buf, fset, f, nil)
		return buf.String()
	}

	before := dump()
	defer func() {
		if t.Failed() {
			t.Logf("before: %s", before)
		}
	}()

	if len(f.Decls) != 1 {
		t.Errorf("len(f.Decls) = %d, want 1 (v)", len(f.Decls))
	}

	// New(PreserveAST) must not mutate the tree.
	_ = New(pkg, "foo", PreserveAST)

	after := dump()
	defer func() {
		if t.Failed() {
			t.Logf("after: %s", after)
		}
	}()

	if after != before {
		t.Error("AST was modified")
	}
}

There is a lesson here: don't create APIs that may or may not mutate their arguments; pick a lane, ideally the second one.

@thanm thanm added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Mar 26, 2024
gopherbot pushed a commit to golang/tools that referenced this issue Mar 29, 2024
The associated bug was a crash in package doc rendering due to
invalid ASTs resulting from mutation caused by doc.New, even
in PreserveAST mode. (This bug has been latent since 2018.)
The mutation occurs when filtering out unexported symbols.

This change is a workaround: we enable AllDecls mode to
suppress filtering, and perform the filtering ourselves.

Also, add a regression test for the panic, and check
the new filtering behavior. This required some test
refactoring.

Fixes golang/go#66449
Updates golang/go#66453

Change-Id: I434c97e08930728d4894b90c1c7e010e7a922209
Reviewed-on: https://go-review.googlesource.com/c/tools/+/573575
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

3 participants