1 // Copyright 2014 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.
7 // TODO(adonovan): new queries
8 // - show all statements that may update the selected lvalue
9 // (local, global, field, etc).
10 // - show all places where an object of type T is created
11 // (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
26 "golang.org/x/tools/go/ast/astutil"
27 "golang.org/x/tools/go/buildutil"
28 "golang.org/x/tools/go/loader"
29 "golang.org/x/tools/go/pointer"
30 "golang.org/x/tools/go/ssa"
33 type printfFunc func(pos interface{}, format string, args ...interface{})
35 // A QueryResult is an item of output. Each query produces a stream of
36 // query results, calling Query.Output for each one.
37 type QueryResult interface {
38 // JSON returns the QueryResult in JSON form.
39 JSON(fset *token.FileSet) []byte
41 // PrintPlain prints the QueryResult in plain text form.
42 // The implementation calls printfFunc to print each line of output.
43 PrintPlain(printf printfFunc)
46 // A QueryPos represents the position provided as input to a query:
47 // a textual extent in the program's source code, the AST node it
48 // corresponds to, and the package to which it belongs.
49 // Instances are created by parseQueryPos.
50 type queryPos struct {
52 start, end token.Pos // source extent of query
53 path []ast.Node // AST path from query node to root of ast.File
54 exact bool // 2nd result of PathEnclosingInterval
55 info *loader.PackageInfo // type info for the queried package (nil for fastQueryPos)
58 // TypeString prints type T relative to the query position.
59 func (qpos *queryPos) typeString(T types.Type) string {
60 return types.TypeString(T, types.RelativeTo(qpos.info.Pkg))
63 // ObjectString prints object obj relative to the query position.
64 func (qpos *queryPos) objectString(obj types.Object) string {
65 return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg))
68 // A Query specifies a single guru query.
70 Pos string // query position
71 Build *build.Context // package loading configuration
73 // pointer analysis options
74 Scope []string // main packages in (*loader.Config).FromArgs syntax
75 PTALog io.Writer // (optional) pointer-analysis log file
76 Reflection bool // model reflection soundly (currently slow).
78 // result-printing function, safe for concurrent use
79 Output func(*token.FileSet, QueryResult)
82 // Run runs an guru query and populates its Fset and Result.
83 func Run(mode string, q *Query) error {
110 return fmt.Errorf("invalid mode: %q", mode)
114 func setPTAScope(lconf *loader.Config, scope []string) error {
115 pkgs := buildutil.ExpandPatterns(lconf.Build, scope)
117 return fmt.Errorf("no packages specified for pointer analysis scope")
119 // The value of each entry in pkgs is true,
120 // giving ImportWithTests (not Import) semantics.
121 lconf.ImportPkgs = pkgs
125 // Create a pointer.Config whose scope is the initial packages of lprog
126 // and their dependencies.
127 func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) {
128 // For each initial package (specified on the command line),
129 // if it has a main function, analyze that,
130 // otherwise analyze its tests, if any.
131 var mains []*ssa.Package
132 for _, info := range lprog.InitialPackages() {
133 p := prog.Package(info.Pkg)
135 // Add package to the pointer analysis scope.
136 if p.Pkg.Name() == "main" && p.Func("main") != nil {
137 mains = append(mains, p)
138 } else if main := prog.CreateTestMainPackage(p); main != nil {
139 mains = append(mains, main)
143 return nil, fmt.Errorf("analysis scope has no main and no tests")
145 return &pointer.Config{
147 Reflection: reflection,
152 // importQueryPackage finds the package P containing the
153 // query position and tells conf to import it.
154 // It returns the package's path.
155 func importQueryPackage(pos string, conf *loader.Config) (string, error) {
156 fqpos, err := fastQueryPos(conf.Build, pos)
158 return "", err // bad query
160 filename := fqpos.fset.File(fqpos.start).Name()
162 _, importPath, err := guessImportPath(filename, conf.Build)
164 // Can't find GOPATH dir.
165 // Treat the query file as its own package.
166 importPath = "command-line-arguments"
167 conf.CreateFromFilenames(importPath, filename)
169 // Check that it's possible to load the queried package.
170 // (e.g. guru tests contain different 'package' decls in same dir.)
171 // Keep consistent with logic in loader/util.go!
173 cfg2.CgoEnabled = false
174 bp, err := cfg2.Import(importPath, "", 0)
176 return "", err // no files for package
179 switch pkgContainsFile(bp, filename) {
181 conf.ImportWithTests(importPath)
183 conf.ImportWithTests(importPath)
184 importPath += "_test" // for TypeCheckFuncBodies
186 conf.Import(importPath)
188 // This happens for ad-hoc packages like
189 // $GOROOT/src/net/http/triv.go.
190 return "", fmt.Errorf("package %q doesn't contain file %s",
191 importPath, filename)
195 conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
197 return importPath, nil
200 // pkgContainsFile reports whether file was among the packages Go
201 // files, Test files, eXternal test files, or not found.
202 func pkgContainsFile(bp *build.Package, filename string) byte {
203 for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
204 for _, file := range files {
205 if sameFile(filepath.Join(bp.Dir, file), filename) {
210 return 0 // not found
213 // ParseQueryPos parses the source query position pos and returns the
214 // AST node of the loaded program lprog that it identifies.
215 // If needExact, it must identify a single AST subtree;
216 // this is appropriate for queries that allow fairly arbitrary syntax,
219 func parseQueryPos(lprog *loader.Program, pos string, needExact bool) (*queryPos, error) {
220 filename, startOffset, endOffset, err := parsePos(pos)
225 // Find the named file among those in the loaded program.
227 lprog.Fset.Iterate(func(f *token.File) bool {
228 if sameFile(filename, f.Name()) {
232 return true // continue
235 return nil, fmt.Errorf("file %s not found in loaded program", filename)
238 start, end, err := fileOffsetToPos(file, startOffset, endOffset)
242 info, path, exact := lprog.PathEnclosingInterval(start, end)
244 return nil, fmt.Errorf("no syntax here")
246 if needExact && !exact {
247 return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
249 return &queryPos{lprog.Fset, start, end, path, exact, info}, nil
252 // ---------- Utilities ----------
254 // loadWithSoftErrors calls lconf.Load, suppressing "soft" errors. (See Go issue 16530.)
255 // TODO(adonovan): Once the loader has an option to allow soft errors,
256 // replace calls to loadWithSoftErrors with loader calls with that parameter.
257 func loadWithSoftErrors(lconf *loader.Config) (*loader.Program, error) {
258 lconf.AllowErrors = true
260 // Ideally we would just return conf.Load() here, but go/types
261 // reports certain "soft" errors that gc does not (Go issue 14596).
262 // As a workaround, we set AllowErrors=true and then duplicate
263 // the loader's error checking but allow soft errors.
264 // It would be nice if the loader API permitted "AllowErrors: soft".
265 prog, err := lconf.Load()
270 // Report hard errors in indirectly imported packages.
271 for _, info := range prog.AllPackages {
272 if containsHardErrors(info.Errors) {
273 errpkgs = append(errpkgs, info.Pkg.Path())
275 // Enable SSA construction for packages containing only soft errors.
276 info.TransitivelyErrorFree = true
281 if len(errpkgs) > 3 {
282 more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
283 errpkgs = errpkgs[:3]
285 return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
286 strings.Join(errpkgs, ", "), more)
291 func containsHardErrors(errors []error) bool {
292 for _, err := range errors {
293 if err, ok := err.(types.Error); ok && err.Soft {
301 // allowErrors causes type errors to be silently ignored.
302 // (Not suitable if SSA construction follows.)
303 func allowErrors(lconf *loader.Config) {
304 ctxt := *lconf.Build // copy
305 ctxt.CgoEnabled = false
307 lconf.AllowErrors = true
308 // AllErrors makes the parser always return an AST instead of
309 // bailing out after 10 errors and returning an empty ast.File.
310 lconf.ParserMode = parser.AllErrors
311 lconf.TypeChecker.Error = func(err error) {}
314 // ptrAnalysis runs the pointer analysis and returns its result.
315 func ptrAnalysis(conf *pointer.Config) *pointer.Result {
316 result, err := pointer.Analyze(conf)
318 panic(err) // pointer analysis internal error
323 func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
325 // deref returns a pointer's element type; otherwise it returns typ.
326 func deref(typ types.Type) types.Type {
327 if p, ok := typ.Underlying().(*types.Pointer); ok {
333 // fprintf prints to w a message of the form "location: message\n"
334 // where location is derived from pos.
336 // pos must be one of:
337 // - a token.Pos, denoting a position
338 // - an ast.Node, denoting an interval
339 // - anything with a Pos() method:
340 // ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc.
341 // - a QueryPos, denoting the extent of the user's query.
342 // - nil, meaning no position at all.
344 // The output format is is compatible with the 'gnu'
345 // compilation-error-regexp in Emacs' compilation mode.
347 func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) {
348 var start, end token.Pos
349 switch pos := pos.(type) {
357 // The Pos of most PkgName objects does not coincide with an identifier,
358 // so we suppress the usual start+len(name) heuristic for types.Objects.
363 end = start + token.Pos(len(pos.Name())) // heuristic
375 panic(fmt.Sprintf("invalid pos: %T", pos))
378 if sp := fset.Position(start); start == end {
379 // (prints "-: " for token.NoPos)
380 fmt.Fprintf(w, "%s: ", sp)
382 ep := fset.Position(end)
383 // The -1 below is a concession to Emacs's broken use of
384 // inclusive (not half-open) intervals.
385 // Other editors may not want it.
386 // TODO(adonovan): add an -editor=vim|emacs|acme|auto
387 // flag; auto uses EMACS=t / VIM=... / etc env vars.
388 fmt.Fprintf(w, "%s:%d.%d-%d.%d: ",
389 sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1)
391 fmt.Fprintf(w, format, args...)
392 io.WriteString(w, "\n")
395 func toJSON(x interface{}) []byte {
396 b, err := json.MarshalIndent(x, "", "\t")
398 log.Fatalf("JSON error: %v", err)