github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/cmd/guru/main.go (about)

     1  // Copyright 2013 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  // guru: a tool for answering questions about Go source code.
     6  //
     7  //	http://golang.org/s/using-guru
     8  //
     9  // Run with -help flag or help subcommand for usage information.
    10  package main // import "golang.org/x/tools/cmd/guru"
    11  
    12  import (
    13  	"bufio"
    14  	"flag"
    15  	"fmt"
    16  	"go/build"
    17  	"go/token"
    18  	"io"
    19  	"log"
    20  	"os"
    21  	"path/filepath"
    22  	"runtime"
    23  	"runtime/pprof"
    24  	"strings"
    25  	"sync"
    26  
    27  	"golang.org/x/tools/go/buildutil"
    28  )
    29  
    30  // flags
    31  var (
    32  	modifiedFlag   = flag.Bool("modified", false, "read archive of modified files from standard input")
    33  	scopeFlag      = flag.String("scope", "", "comma-separated list of `packages` the analysis should be limited to")
    34  	ptalogFlag     = flag.String("ptalog", "", "write points-to analysis log to `file`")
    35  	jsonFlag       = flag.Bool("json", false, "emit output in JSON format")
    36  	reflectFlag    = flag.Bool("reflect", false, "analyze reflection soundly (slow)")
    37  	cpuprofileFlag = flag.String("cpuprofile", "", "write CPU profile to `file`")
    38  )
    39  
    40  func init() {
    41  	flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
    42  
    43  	// gccgo does not provide a GOROOT with standard library sources.
    44  	// If we have one in the environment, force gc mode.
    45  	if build.Default.Compiler == "gccgo" {
    46  		if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime.go")); err == nil {
    47  			build.Default.Compiler = "gc"
    48  		}
    49  	}
    50  }
    51  
    52  const useHelp = "Run 'guru -help' for more information.\n"
    53  
    54  const helpMessage = `Go source code guru.
    55  Usage: guru [flags] <mode> <position>
    56  
    57  The mode argument determines the query to perform:
    58  
    59  	callees	  	show possible targets of selected function call
    60  	callers	  	show possible callers of selected function
    61  	callstack 	show path from callgraph root to selected function
    62  	definition	show declaration of selected identifier
    63  	describe  	describe selected syntax: definition, methods, etc
    64  	freevars  	show free variables of selection
    65  	implements	show 'implements' relation for selected type or method
    66  	peers     	show send/receive corresponding to selected channel op
    67  	pointsto	show variables the selected pointer may point to
    68  	referrers 	show all refs to entity denoted by selected identifier
    69  	what		show basic information about the selected syntax node
    70  	whicherrs	show possible values of the selected error variable
    71  
    72  The position argument specifies the filename and byte offset (or range)
    73  of the syntax element to query.  For example:
    74  
    75  	foo.go:#123,#128
    76  	bar.go:#123
    77  
    78  The -json flag causes guru to emit output in JSON format;
    79  	golang.org/x/tools/cmd/guru/serial defines its schema.
    80  	Otherwise, the output is in an editor-friendly format in which
    81  	every line has the form "pos: text", where pos is "-" if unknown.
    82  
    83  The -modified flag causes guru to read an archive from standard input.
    84  	Files in this archive will be used in preference to those in
    85  	the file system.  In this way, a text editor may supply guru
    86  	with the contents of its unsaved buffers.  Each archive entry
    87  	consists of the file name, a newline, the decimal file size,
    88  	another newline, and the contents of the file.
    89  
    90  The -scope flag restricts analysis to the specified packages.
    91  	Its value is a comma-separated list of patterns of these forms:
    92  		golang.org/x/tools/cmd/guru     # a single package
    93  		golang.org/x/tools/...          # all packages beneath dir
    94  		...                             # the entire workspace.
    95  	A pattern preceded by '-' is negative, so the scope
    96  		encoding/...,-encoding/xml
    97  	matches all encoding packages except encoding/xml.
    98  
    99  User manual: http://golang.org/s/using-guru
   100  
   101  Example: describe syntax at offset 530 in this file (an import spec):
   102  
   103    $ guru describe src/golang.org/x/tools/cmd/guru/main.go:#530
   104  `
   105  
   106  func printHelp() {
   107  	fmt.Fprint(os.Stderr, helpMessage)
   108  	fmt.Fprintln(os.Stderr, "\nFlags:")
   109  	flag.PrintDefaults()
   110  }
   111  
   112  func main() {
   113  	log.SetPrefix("guru: ")
   114  	log.SetFlags(0)
   115  
   116  	// Don't print full help unless -help was requested.
   117  	// Just gently remind users that it's there.
   118  	flag.Usage = func() { fmt.Fprint(os.Stderr, useHelp) }
   119  	flag.CommandLine.Init(os.Args[0], flag.ContinueOnError) // hack
   120  	if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
   121  		// (err has already been printed)
   122  		if err == flag.ErrHelp {
   123  			printHelp()
   124  		}
   125  		os.Exit(2)
   126  	}
   127  
   128  	args := flag.Args()
   129  	if len(args) != 2 {
   130  		flag.Usage()
   131  		os.Exit(2)
   132  	}
   133  	mode, posn := args[0], args[1]
   134  
   135  	if mode == "help" {
   136  		printHelp()
   137  		os.Exit(2)
   138  	}
   139  
   140  	// Set up points-to analysis log file.
   141  	var ptalog io.Writer
   142  	if *ptalogFlag != "" {
   143  		if f, err := os.Create(*ptalogFlag); err != nil {
   144  			log.Fatalf("Failed to create PTA log file: %s", err)
   145  		} else {
   146  			buf := bufio.NewWriter(f)
   147  			ptalog = buf
   148  			defer func() {
   149  				if err := buf.Flush(); err != nil {
   150  					log.Printf("flush: %s", err)
   151  				}
   152  				if err := f.Close(); err != nil {
   153  					log.Printf("close: %s", err)
   154  				}
   155  			}()
   156  		}
   157  	}
   158  
   159  	// Profiling support.
   160  	if *cpuprofileFlag != "" {
   161  		f, err := os.Create(*cpuprofileFlag)
   162  		if err != nil {
   163  			log.Fatal(err)
   164  		}
   165  		pprof.StartCPUProfile(f)
   166  		defer pprof.StopCPUProfile()
   167  	}
   168  
   169  	ctxt := &build.Default
   170  
   171  	// If there were modified files,
   172  	// read them from the standard input and
   173  	// overlay them on the build context.
   174  	if *modifiedFlag {
   175  		modified, err := buildutil.ParseOverlayArchive(os.Stdin)
   176  		if err != nil {
   177  			log.Fatal(err)
   178  		}
   179  
   180  		// All I/O done by guru needs to consult the modified map.
   181  		// The ReadFile done by referrers does,
   182  		// but the loader's cgo preprocessing currently does not.
   183  
   184  		if len(modified) > 0 {
   185  			ctxt = buildutil.OverlayContext(ctxt, modified)
   186  		}
   187  	}
   188  
   189  	var outputMu sync.Mutex
   190  	output := func(fset *token.FileSet, qr QueryResult) {
   191  		outputMu.Lock()
   192  		defer outputMu.Unlock()
   193  		if *jsonFlag {
   194  			// JSON output
   195  			fmt.Printf("%s\n", qr.JSON(fset))
   196  		} else {
   197  			// plain output
   198  			printf := func(pos interface{}, format string, args ...interface{}) {
   199  				fprintf(os.Stdout, fset, pos, format, args...)
   200  			}
   201  			qr.PrintPlain(printf)
   202  		}
   203  	}
   204  
   205  	// Avoid corner case of split("").
   206  	var scope []string
   207  	if *scopeFlag != "" {
   208  		scope = strings.Split(*scopeFlag, ",")
   209  	}
   210  
   211  	// Ask the guru.
   212  	query := Query{
   213  		Pos:        posn,
   214  		Build:      ctxt,
   215  		Scope:      scope,
   216  		PTALog:     ptalog,
   217  		Reflection: *reflectFlag,
   218  		Output:     output,
   219  	}
   220  
   221  	if err := Run(mode, &query); err != nil {
   222  		log.Fatal(err)
   223  	}
   224  }