github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/oracle/oracle.go (about) 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. 4 5 // +build go1.5 6 7 // Package oracle contains the implementation of the oracle tool whose 8 // command-line is provided by golang.org/x/tools/cmd/oracle. 9 // 10 // http://golang.org/s/oracle-design 11 // http://golang.org/s/oracle-user-manual 12 // 13 package oracle // import "golang.org/x/tools/oracle" 14 15 // This file defines oracle.Query, the entry point for the oracle tool. 16 // The actual executable is defined in cmd/oracle. 17 18 // TODO(adonovan): new queries 19 // - show all statements that may update the selected lvalue 20 // (local, global, field, etc). 21 // - show all places where an object of type T is created 22 // (&T{}, var t T, new(T), new(struct{array [3]T}), etc. 23 24 import ( 25 "fmt" 26 "go/ast" 27 "go/build" 28 "go/parser" 29 "go/token" 30 "go/types" 31 "io" 32 "path/filepath" 33 34 "golang.org/x/tools/go/ast/astutil" 35 "golang.org/x/tools/go/loader" 36 "golang.org/x/tools/go/pointer" 37 "golang.org/x/tools/go/ssa" 38 "golang.org/x/tools/oracle/serial" 39 ) 40 41 type printfFunc func(pos interface{}, format string, args ...interface{}) 42 43 // queryResult is the interface of each query-specific result type. 44 type queryResult interface { 45 toSerial(res *serial.Result, fset *token.FileSet) 46 display(printf printfFunc) 47 } 48 49 // A QueryPos represents the position provided as input to a query: 50 // a textual extent in the program's source code, the AST node it 51 // corresponds to, and the package to which it belongs. 52 // Instances are created by parseQueryPos. 53 type queryPos struct { 54 fset *token.FileSet 55 start, end token.Pos // source extent of query 56 path []ast.Node // AST path from query node to root of ast.File 57 exact bool // 2nd result of PathEnclosingInterval 58 info *loader.PackageInfo // type info for the queried package (nil for fastQueryPos) 59 } 60 61 // TypeString prints type T relative to the query position. 62 func (qpos *queryPos) typeString(T types.Type) string { 63 return types.TypeString(T, types.RelativeTo(qpos.info.Pkg)) 64 } 65 66 // ObjectString prints object obj relative to the query position. 67 func (qpos *queryPos) objectString(obj types.Object) string { 68 return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg)) 69 } 70 71 // SelectionString prints selection sel relative to the query position. 72 func (qpos *queryPos) selectionString(sel *types.Selection) string { 73 return types.SelectionString(sel, types.RelativeTo(qpos.info.Pkg)) 74 } 75 76 // A Query specifies a single oracle query. 77 type Query struct { 78 Mode string // query mode ("callers", etc) 79 Pos string // query position 80 Build *build.Context // package loading configuration 81 82 // pointer analysis options 83 Scope []string // main packages in (*loader.Config).FromArgs syntax 84 PTALog io.Writer // (optional) pointer-analysis log file 85 Reflection bool // model reflection soundly (currently slow). 86 87 // Populated during Run() 88 Fset *token.FileSet 89 result queryResult 90 } 91 92 // Serial returns an instance of serial.Result, which implements the 93 // {xml,json}.Marshaler interfaces so that query results can be 94 // serialized as JSON or XML. 95 // 96 func (q *Query) Serial() *serial.Result { 97 resj := &serial.Result{Mode: q.Mode} 98 q.result.toSerial(resj, q.Fset) 99 return resj 100 } 101 102 // WriteTo writes the oracle query result res to out in a compiler diagnostic format. 103 func (q *Query) WriteTo(out io.Writer) { 104 printf := func(pos interface{}, format string, args ...interface{}) { 105 fprintf(out, q.Fset, pos, format, args...) 106 } 107 q.result.display(printf) 108 } 109 110 // Run runs an oracle query and populates its Fset and Result. 111 func Run(q *Query) error { 112 switch q.Mode { 113 case "callees": 114 return callees(q) 115 case "callers": 116 return callers(q) 117 case "callstack": 118 return callstack(q) 119 case "peers": 120 return peers(q) 121 case "pointsto": 122 return pointsto(q) 123 case "whicherrs": 124 return whicherrs(q) 125 case "definition": 126 return definition(q) 127 case "describe": 128 return describe(q) 129 case "freevars": 130 return freevars(q) 131 case "implements": 132 return implements(q) 133 case "referrers": 134 return referrers(q) 135 case "what": 136 return what(q) 137 default: 138 return fmt.Errorf("invalid mode: %q", q.Mode) 139 } 140 } 141 142 func setPTAScope(lconf *loader.Config, scope []string) error { 143 if len(scope) == 0 { 144 return fmt.Errorf("no packages specified for pointer analysis scope") 145 } 146 147 // Determine initial packages for PTA. 148 args, err := lconf.FromArgs(scope, true) 149 if err != nil { 150 return err 151 } 152 if len(args) > 0 { 153 return fmt.Errorf("surplus arguments: %q", args) 154 } 155 return nil 156 } 157 158 // Create a pointer.Config whose scope is the initial packages of lprog 159 // and their dependencies. 160 func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) { 161 // TODO(adonovan): the body of this function is essentially 162 // duplicated in all go/pointer clients. Refactor. 163 164 // For each initial package (specified on the command line), 165 // if it has a main function, analyze that, 166 // otherwise analyze its tests, if any. 167 var testPkgs, mains []*ssa.Package 168 for _, info := range lprog.InitialPackages() { 169 initialPkg := prog.Package(info.Pkg) 170 171 // Add package to the pointer analysis scope. 172 if initialPkg.Func("main") != nil { 173 mains = append(mains, initialPkg) 174 } else { 175 testPkgs = append(testPkgs, initialPkg) 176 } 177 } 178 if testPkgs != nil { 179 if p := prog.CreateTestMainPackage(testPkgs...); p != nil { 180 mains = append(mains, p) 181 } 182 } 183 if mains == nil { 184 return nil, fmt.Errorf("analysis scope has no main and no tests") 185 } 186 return &pointer.Config{ 187 Log: ptaLog, 188 Reflection: reflection, 189 Mains: mains, 190 }, nil 191 } 192 193 // importQueryPackage finds the package P containing the 194 // query position and tells conf to import it. 195 // It returns the package's path. 196 func importQueryPackage(pos string, conf *loader.Config) (string, error) { 197 fqpos, err := fastQueryPos(pos) 198 if err != nil { 199 return "", err // bad query 200 } 201 filename := fqpos.fset.File(fqpos.start).Name() 202 203 // This will not work for ad-hoc packages 204 // such as $GOROOT/src/net/http/triv.go. 205 // TODO(adonovan): ensure we report a clear error. 206 _, importPath, err := guessImportPath(filename, conf.Build) 207 if err != nil { 208 return "", err // can't find GOPATH dir 209 } 210 if importPath == "" { 211 return "", fmt.Errorf("can't guess import path from %s", filename) 212 } 213 214 // Check that it's possible to load the queried package. 215 // (e.g. oracle tests contain different 'package' decls in same dir.) 216 // Keep consistent with logic in loader/util.go! 217 cfg2 := *conf.Build 218 cfg2.CgoEnabled = false 219 bp, err := cfg2.Import(importPath, "", 0) 220 if err != nil { 221 return "", err // no files for package 222 } 223 224 switch pkgContainsFile(bp, filename) { 225 case 'T': 226 conf.ImportWithTests(importPath) 227 case 'X': 228 conf.ImportWithTests(importPath) 229 importPath += "_test" // for TypeCheckFuncBodies 230 case 'G': 231 conf.Import(importPath) 232 default: 233 return "", fmt.Errorf("package %q doesn't contain file %s", 234 importPath, filename) 235 } 236 237 conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath } 238 239 return importPath, nil 240 } 241 242 // pkgContainsFile reports whether file was among the packages Go 243 // files, Test files, eXternal test files, or not found. 244 func pkgContainsFile(bp *build.Package, filename string) byte { 245 for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} { 246 for _, file := range files { 247 if sameFile(filepath.Join(bp.Dir, file), filename) { 248 return "GTX"[i] 249 } 250 } 251 } 252 return 0 // not found 253 } 254 255 // ParseQueryPos parses the source query position pos and returns the 256 // AST node of the loaded program lprog that it identifies. 257 // If needExact, it must identify a single AST subtree; 258 // this is appropriate for queries that allow fairly arbitrary syntax, 259 // e.g. "describe". 260 // 261 func parseQueryPos(lprog *loader.Program, posFlag string, needExact bool) (*queryPos, error) { 262 filename, startOffset, endOffset, err := parsePosFlag(posFlag) 263 if err != nil { 264 return nil, err 265 } 266 start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset) 267 if err != nil { 268 return nil, err 269 } 270 info, path, exact := lprog.PathEnclosingInterval(start, end) 271 if path == nil { 272 return nil, fmt.Errorf("no syntax here") 273 } 274 if needExact && !exact { 275 return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) 276 } 277 return &queryPos{lprog.Fset, start, end, path, exact, info}, nil 278 } 279 280 // ---------- Utilities ---------- 281 282 // allowErrors causes type errors to be silently ignored. 283 // (Not suitable if SSA construction follows.) 284 func allowErrors(lconf *loader.Config) { 285 ctxt := *lconf.Build // copy 286 ctxt.CgoEnabled = false 287 lconf.Build = &ctxt 288 lconf.AllowErrors = true 289 // AllErrors makes the parser always return an AST instead of 290 // bailing out after 10 errors and returning an empty ast.File. 291 lconf.ParserMode = parser.AllErrors 292 lconf.TypeChecker.Error = func(err error) {} 293 } 294 295 // ptrAnalysis runs the pointer analysis and returns its result. 296 func ptrAnalysis(conf *pointer.Config) *pointer.Result { 297 result, err := pointer.Analyze(conf) 298 if err != nil { 299 panic(err) // pointer analysis internal error 300 } 301 return result 302 } 303 304 func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) } 305 306 // deref returns a pointer's element type; otherwise it returns typ. 307 func deref(typ types.Type) types.Type { 308 if p, ok := typ.Underlying().(*types.Pointer); ok { 309 return p.Elem() 310 } 311 return typ 312 } 313 314 // fprintf prints to w a message of the form "location: message\n" 315 // where location is derived from pos. 316 // 317 // pos must be one of: 318 // - a token.Pos, denoting a position 319 // - an ast.Node, denoting an interval 320 // - anything with a Pos() method: 321 // ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc. 322 // - a QueryPos, denoting the extent of the user's query. 323 // - nil, meaning no position at all. 324 // 325 // The output format is is compatible with the 'gnu' 326 // compilation-error-regexp in Emacs' compilation mode. 327 // TODO(adonovan): support other editors. 328 // 329 func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) { 330 var start, end token.Pos 331 switch pos := pos.(type) { 332 case ast.Node: 333 start = pos.Pos() 334 end = pos.End() 335 case token.Pos: 336 start = pos 337 end = start 338 case interface { 339 Pos() token.Pos 340 }: 341 start = pos.Pos() 342 end = start 343 case *queryPos: 344 start = pos.start 345 end = pos.end 346 case nil: 347 // no-op 348 default: 349 panic(fmt.Sprintf("invalid pos: %T", pos)) 350 } 351 352 if sp := fset.Position(start); start == end { 353 // (prints "-: " for token.NoPos) 354 fmt.Fprintf(w, "%s: ", sp) 355 } else { 356 ep := fset.Position(end) 357 // The -1 below is a concession to Emacs's broken use of 358 // inclusive (not half-open) intervals. 359 // Other editors may not want it. 360 // TODO(adonovan): add an -editor=vim|emacs|acme|auto 361 // flag; auto uses EMACS=t / VIM=... / etc env vars. 362 fmt.Fprintf(w, "%s:%d.%d-%d.%d: ", 363 sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1) 364 } 365 fmt.Fprintf(w, format, args...) 366 io.WriteString(w, "\n") 367 }