From 814f676cf4f80400d8595349409e16ecb12990ef Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Tue, 19 Mar 2024 13:18:47 -0400 Subject: [PATCH] internal/gcimporter: add tool to inspect export data This CL adds an unexported command for inspecting "unified" export data produced by the compiler, and checking that it can be roundtripped through "indexed" format as used by gopls. Change-Id: Idc8eea5c8d7287d7f10af7bd15c7a52e6987a4bf Reviewed-on: https://go-review.googlesource.com/c/tools/+/572795 Reviewed-by: Robert Findley Auto-Submit: Alan Donovan LUCI-TryBot-Result: Go LUCI --- internal/gcimporter/main.go | 117 ++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 internal/gcimporter/main.go diff --git a/internal/gcimporter/main.go b/internal/gcimporter/main.go new file mode 100644 index 000000000..4a4ddd284 --- /dev/null +++ b/internal/gcimporter/main.go @@ -0,0 +1,117 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +// The gcimporter command reads the compiler's export data for the +// named packages and prints the decoded type information. +// +// It is provided for debugging export data problems. +package main + +import ( + "bytes" + "flag" + "fmt" + "go/token" + "go/types" + "log" + "os" + "sort" + + "golang.org/x/tools/go/gcexportdata" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/gcimporter" +) + +func main() { + flag.Parse() + cfg := &packages.Config{ + Fset: token.NewFileSet(), + // Don't request NeedTypes: we want to be certain that + // we loaded the types ourselves, from export data. + Mode: packages.NeedName | packages.NeedExportFile, + } + pkgs, err := packages.Load(cfg, flag.Args()...) + if err != nil { + log.Fatal(err) + } + if packages.PrintErrors(pkgs) > 0 { + os.Exit(1) + } + + for _, pkg := range pkgs { + // Read types from compiler's unified export data file. + // This Package may included non-exported functions if they + // are called by inlinable exported functions. + var tpkg1 *types.Package + { + export, err := os.ReadFile(pkg.ExportFile) + if err != nil { + log.Fatalf("can't read %q export data: %v", pkg.PkgPath, err) + } + r, err := gcexportdata.NewReader(bytes.NewReader(export)) + if err != nil { + log.Fatalf("reading export data %s: %v", pkg.ExportFile, err) + } + tpkg1, err = gcexportdata.Read(r, cfg.Fset, make(map[string]*types.Package), pkg.PkgPath) + if err != nil { + log.Fatalf("decoding export data: %v", err) + } + } + fmt.Println("# Read from compiler's unified export data:") + printPackage(tpkg1) + + // Now reexport as indexed (deep) export data, and reimport. + // The Package will contain only exported symbols. + var tpkg2 *types.Package + { + var out bytes.Buffer + if err := gcimporter.IExportData(&out, cfg.Fset, tpkg1); err != nil { + log.Fatal(err) + } + var err error + _, tpkg2, err = gcimporter.IImportData(cfg.Fset, make(map[string]*types.Package), out.Bytes(), tpkg1.Path()) + if err != nil { + log.Fatal(err) + } + } + fmt.Println("# After round-tripping through indexed export data:") + printPackage(tpkg2) + } +} + +func printPackage(pkg *types.Package) { + fmt.Printf("package %s %q\n", pkg.Name(), pkg.Path()) + + if !pkg.Complete() { + fmt.Printf("\thas incomplete exported type info\n") + } + + // imports + var lines []string + for _, imp := range pkg.Imports() { + lines = append(lines, fmt.Sprintf("\timport %q", imp.Path())) + } + sort.Strings(lines) + for _, line := range lines { + fmt.Println(line) + } + + // types of package members + qual := types.RelativeTo(pkg) + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + fmt.Printf("\t%s\n", types.ObjectString(obj, qual)) + if _, ok := obj.(*types.TypeName); ok { + for _, meth := range typeutil.IntuitiveMethodSet(obj.Type(), nil) { + fmt.Printf("\t%s\n", types.SelectionString(meth, qual)) + } + } + } + + fmt.Println() +}