create.go (9294B)
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 package ir 6 7 // This file implements the CREATE phase of IR construction. 8 // See builder.go for explanation. 9 10 import ( 11 "fmt" 12 "go/ast" 13 "go/token" 14 "go/types" 15 "os" 16 "sync" 17 18 "honnef.co/go/tools/internal/xtools-internal/versions" 19 ) 20 21 // NewProgram returns a new IR Program. 22 // 23 // mode controls diagnostics and checking during IR construction. 24 // 25 // To construct an SSA program: 26 // 27 // - Call NewProgram to create an empty Program. 28 // - Call CreatePackage providing typed syntax for each package 29 // you want to build, and call it with types but not 30 // syntax for each of those package's direct dependencies. 31 // - Call [Package.Build] on each syntax package you wish to build, 32 // or [Program.Build] to build all of them. 33 // 34 // See the Example tests for simple examples. 35 func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { 36 return &Program{ 37 Fset: fset, 38 imported: make(map[string]*Package), 39 packages: make(map[*types.Package]*Package), 40 mode: mode, 41 canon: newCanonizer(), 42 ctxt: types.NewContext(), 43 } 44 } 45 46 // memberFromObject populates package pkg with a member for the 47 // typechecker object obj. 48 // 49 // For objects from Go source code, syntax is the associated syntax tree 50 // (for funcs and vars only) and goversion defines the appropriate 51 // interpretation; they will be used during the build phase. 52 func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion string) { 53 name := obj.Name() 54 switch obj := obj.(type) { 55 case *types.Builtin: 56 if pkg.Pkg != types.Unsafe { 57 panic("unexpected builtin object: " + obj.String()) 58 } 59 60 case *types.TypeName: 61 if name != "_" { 62 pkg.Members[name] = &Type{ 63 object: obj, 64 pkg: pkg, 65 } 66 } 67 68 case *types.Const: 69 c := &NamedConst{ 70 object: obj, 71 Value: NewConst(obj.Val(), obj.Type(), syntax), 72 pkg: pkg, 73 } 74 pkg.values[obj] = c 75 if name != "_" { 76 pkg.Members[name] = c 77 } 78 79 case *types.Var: 80 g := &Global{ 81 Pkg: pkg, 82 name: name, 83 object: obj, 84 typ: types.NewPointer(obj.Type()), // address 85 } 86 g.source = syntax 87 pkg.values[obj] = g 88 if name != "_" { 89 pkg.Members[name] = g 90 } 91 92 case *types.Func: 93 sig := obj.Type().(*types.Signature) 94 if sig.Recv() == nil && name == "init" { 95 pkg.ninit++ 96 name = fmt.Sprintf("init#%d", pkg.ninit) 97 } 98 fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion) 99 fn.Pkg = pkg 100 pkg.created = append(pkg.created, fn) 101 pkg.values[obj] = fn 102 pkg.Functions = append(pkg.Functions, fn) 103 if name != "_" && sig.Recv() == nil { 104 pkg.Members[name] = fn // package-level function 105 } 106 107 default: // (incl. *types.Package) 108 panic("unexpected Object type: " + obj.String()) 109 } 110 } 111 112 // createFunction creates a function or method. It supports both 113 // CreatePackage (with or without syntax) and the on-demand creation 114 // of methods in non-created packages based on their types.Func. 115 func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string) *Function { 116 sig := obj.Type().(*types.Signature) 117 118 /* declared function/method (from syntax or export data) */ 119 fn := &Function{ 120 name: name, 121 object: obj, 122 Signature: sig, 123 build: (*builder).buildFromSyntax, 124 info: info, 125 goversion: goversion, 126 pos: obj.Pos(), 127 syntax: syntax, 128 Pkg: nil, // may be set by caller 129 Prog: prog, 130 recvtypeparams: sig.RecvTypeParams(), 131 typeparams: sig.TypeParams(), 132 } 133 if syntax == nil { 134 fn.Synthetic = "from type information" 135 fn.build = (*builder).buildParamsOnly 136 } 137 if fn.hasTypeParams() { 138 fn.generic = new(generic) 139 } 140 return fn 141 } 142 143 // membersFromDecl populates package pkg with members for each 144 // typechecker object (var, func, const or type) associated with the 145 // specified decl. 146 func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) { 147 switch decl := decl.(type) { 148 case *ast.GenDecl: // import, const, type or var 149 switch decl.Tok { 150 case token.CONST: 151 for _, spec := range decl.Specs { 152 for _, id := range spec.(*ast.ValueSpec).Names { 153 memberFromObject(pkg, pkg.info.Defs[id], nil, "") 154 } 155 } 156 157 case token.VAR: 158 for _, spec := range decl.Specs { 159 for _, rhs := range spec.(*ast.ValueSpec).Values { 160 pkg.initVersion[rhs] = goversion 161 } 162 for _, id := range spec.(*ast.ValueSpec).Names { 163 memberFromObject(pkg, pkg.info.Defs[id], spec, goversion) 164 } 165 } 166 167 case token.TYPE: 168 for _, spec := range decl.Specs { 169 id := spec.(*ast.TypeSpec).Name 170 memberFromObject(pkg, pkg.info.Defs[id], nil, "") 171 } 172 } 173 174 case *ast.FuncDecl: 175 id := decl.Name 176 memberFromObject(pkg, pkg.info.Defs[id], decl, goversion) 177 } 178 } 179 180 // CreatePackage creates and returns an IR Package from the 181 // specified type-checked, error-free file ASTs, and populates its 182 // Members mapping. 183 // 184 // importable determines whether this package should be returned by a 185 // subsequent call to ImportedPackage(pkg.Path()). 186 // 187 // The real work of building IR form for each function is not done 188 // until a subsequent call to Package.Build. 189 func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package { 190 if pkg == nil { 191 panic("nil pkg") // otherwise pkg.Scope below returns types.Universe! 192 } 193 p := &Package{ 194 Prog: prog, 195 Members: make(map[string]Member), 196 values: make(map[types.Object]Member), 197 Pkg: pkg, 198 syntax: info != nil, 199 // transient values (cleared after Package.Build) 200 info: info, 201 files: files, 202 initVersion: make(map[ast.Expr]string), 203 } 204 205 /* synthesized package initializer */ 206 p.init = &Function{ 207 name: "init", 208 Signature: new(types.Signature), 209 Synthetic: "package initializer", 210 Pkg: p, 211 Prog: prog, 212 build: (*builder).buildPackageInit, 213 info: p.info, 214 goversion: "", // See Package.build for details. 215 } 216 p.Members[p.init.name] = p.init 217 p.Functions = append(p.Functions, p.init) 218 p.created = append(p.created, p.init) 219 220 // Allocate all package members: vars, funcs, consts and types. 221 if len(files) > 0 { 222 // Go source package. 223 for _, file := range files { 224 goversion := versions.Lang(versions.FileVersion(p.info, file)) 225 for _, decl := range file.Decls { 226 membersFromDecl(p, decl, goversion) 227 } 228 } 229 } else { 230 // GC-compiled binary package (or "unsafe") 231 // No code. 232 // No position information. 233 scope := p.Pkg.Scope() 234 for _, name := range scope.Names() { 235 obj := scope.Lookup(name) 236 memberFromObject(p, obj, nil, "") 237 if obj, ok := obj.(*types.TypeName); ok { 238 // No Unalias: aliases should not duplicate methods. 239 if named, ok := obj.Type().(*types.Named); ok { 240 for i, n := 0, named.NumMethods(); i < n; i++ { 241 memberFromObject(p, named.Method(i), nil, "") 242 } 243 } 244 } 245 } 246 } 247 248 if prog.mode&BareInits == 0 { 249 // Add initializer guard variable. 250 initguard := &Global{ 251 Pkg: p, 252 name: "init$guard", 253 typ: types.NewPointer(tBool), 254 } 255 p.Members[initguard.Name()] = initguard 256 } 257 258 if prog.mode&GlobalDebug != 0 { 259 p.SetDebugMode(true) 260 } 261 262 if prog.mode&PrintPackages != 0 { 263 printMu.Lock() 264 p.WriteTo(os.Stdout) 265 printMu.Unlock() 266 } 267 268 if importable { 269 prog.imported[p.Pkg.Path()] = p 270 } 271 prog.packages[p.Pkg] = p 272 273 return p 274 } 275 276 // printMu serializes printing of Packages/Functions to stdout. 277 var printMu sync.Mutex 278 279 // AllPackages returns a new slice containing all packages created by 280 // prog.CreatePackage in unspecified order. 281 func (prog *Program) AllPackages() []*Package { 282 pkgs := make([]*Package, 0, len(prog.packages)) 283 for _, pkg := range prog.packages { 284 pkgs = append(pkgs, pkg) 285 } 286 return pkgs 287 } 288 289 // ImportedPackage returns the importable Package whose PkgPath 290 // is path, or nil if no such Package has been created. 291 // 292 // A parameter to CreatePackage determines whether a package should be 293 // considered importable. For example, no import declaration can resolve 294 // to the ad-hoc main package created by 'go build foo.go'. 295 // 296 // TODO(adonovan): rethink this function and the "importable" concept; 297 // most packages are importable. This function assumes that all 298 // types.Package.Path values are unique within the ir.Program, which is 299 // false---yet this function remains very convenient. 300 // Clients should use (*Program).Package instead where possible. 301 // IR doesn't really need a string-keyed map of packages. 302 // 303 // Furthermore, the graph of packages may contain multiple variants 304 // (e.g. "p" vs "p as compiled for q.test"), and each has a different 305 // view of its dependencies. 306 func (prog *Program) ImportedPackage(path string) *Package { 307 return prog.imported[path] 308 } 309 310 // SetNoReturn sets the predicate used when building the ir.Program 311 // prog that reports whether a given function cannot return. 312 // This may be used to prune spurious control flow edges 313 // after (e.g.) log.Fatal, improving the precision of analyses. 314 // 315 // A typical implementation is the [ctrlflow.CFGs.NoReturn] method from 316 // [golang.org/x/tools/go/analysis/passes/ctrlflow]. 317 func (prog *Program) SetNoReturn(fn func(*types.Func) bool) { 318 prog.noReturn = fn 319 }