| // Copyright 2013 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. |
| |
| package ssa |
| |
| // This file implements the CREATE phase of SSA construction. |
| // See builder.go for explanation. |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/token" |
| "go/types" |
| "os" |
| "sync" |
| |
| "golang.org/x/tools/internal/versions" |
| ) |
| |
| // NewProgram returns a new SSA Program. |
| // |
| // mode controls diagnostics and checking during SSA construction. |
| // |
| // To construct an SSA program: |
| // |
| // - Call NewProgram to create an empty Program. |
| // - Call CreatePackage providing typed syntax for each package |
| // you want to build, and call it with types but not |
| // syntax for each of those package's direct dependencies. |
| // - Call [Package.Build] on each syntax package you wish to build, |
| // or [Program.Build] to build all of them. |
| // |
| // See the Example tests for simple examples. |
| func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { |
| return &Program{ |
| Fset: fset, |
| imported: make(map[string]*Package), |
| packages: make(map[*types.Package]*Package), |
| mode: mode, |
| canon: newCanonizer(), |
| ctxt: types.NewContext(), |
| } |
| } |
| |
| // memberFromObject populates package pkg with a member for the |
| // typechecker object obj. |
| // |
| // For objects from Go source code, syntax is the associated syntax |
| // tree (for funcs and vars only) and goversion defines the |
| // appropriate interpretation; they will be used during the build |
| // phase. |
| func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion string) { |
| name := obj.Name() |
| switch obj := obj.(type) { |
| case *types.Builtin: |
| if pkg.Pkg != types.Unsafe { |
| panic("unexpected builtin object: " + obj.String()) |
| } |
| |
| case *types.TypeName: |
| if name != "_" { |
| pkg.Members[name] = &Type{ |
| object: obj, |
| pkg: pkg, |
| } |
| } |
| |
| case *types.Const: |
| c := &NamedConst{ |
| object: obj, |
| Value: NewConst(obj.Val(), obj.Type()), |
| pkg: pkg, |
| } |
| pkg.objects[obj] = c |
| if name != "_" { |
| pkg.Members[name] = c |
| } |
| |
| case *types.Var: |
| g := &Global{ |
| Pkg: pkg, |
| name: name, |
| object: obj, |
| typ: types.NewPointer(obj.Type()), // address |
| pos: obj.Pos(), |
| } |
| pkg.objects[obj] = g |
| if name != "_" { |
| pkg.Members[name] = g |
| } |
| |
| case *types.Func: |
| sig := obj.Type().(*types.Signature) |
| if sig.Recv() == nil && name == "init" { |
| pkg.ninit++ |
| name = fmt.Sprintf("init#%d", pkg.ninit) |
| } |
| fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion) |
| fn.Pkg = pkg |
| pkg.created = append(pkg.created, fn) |
| pkg.objects[obj] = fn |
| if name != "_" && sig.Recv() == nil { |
| pkg.Members[name] = fn // package-level function |
| } |
| |
| default: // (incl. *types.Package) |
| panic("unexpected Object type: " + obj.String()) |
| } |
| } |
| |
| // createFunction creates a function or method. It supports both |
| // CreatePackage (with or without syntax) and the on-demand creation |
| // of methods in non-created packages based on their types.Func. |
| func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string) *Function { |
| sig := obj.Type().(*types.Signature) |
| |
| // Collect type parameters. |
| var tparams *types.TypeParamList |
| if rtparams := sig.RecvTypeParams(); rtparams.Len() > 0 { |
| tparams = rtparams // method of generic type |
| } else if sigparams := sig.TypeParams(); sigparams.Len() > 0 { |
| tparams = sigparams // generic function |
| } |
| |
| /* declared function/method (from syntax or export data) */ |
| fn := &Function{ |
| name: name, |
| object: obj, |
| Signature: sig, |
| build: (*builder).buildFromSyntax, |
| syntax: syntax, |
| info: info, |
| goversion: goversion, |
| pos: obj.Pos(), |
| Pkg: nil, // may be set by caller |
| Prog: prog, |
| typeparams: tparams, |
| } |
| if fn.syntax == nil { |
| fn.Synthetic = "from type information" |
| fn.build = (*builder).buildParamsOnly |
| } |
| if tparams.Len() > 0 { |
| fn.generic = new(generic) |
| } |
| return fn |
| } |
| |
| // membersFromDecl populates package pkg with members for each |
| // typechecker object (var, func, const or type) associated with the |
| // specified decl. |
| func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) { |
| switch decl := decl.(type) { |
| case *ast.GenDecl: // import, const, type or var |
| switch decl.Tok { |
| case token.CONST: |
| for _, spec := range decl.Specs { |
| for _, id := range spec.(*ast.ValueSpec).Names { |
| memberFromObject(pkg, pkg.info.Defs[id], nil, "") |
| } |
| } |
| |
| case token.VAR: |
| for _, spec := range decl.Specs { |
| for _, rhs := range spec.(*ast.ValueSpec).Values { |
| pkg.initVersion[rhs] = goversion |
| } |
| for _, id := range spec.(*ast.ValueSpec).Names { |
| memberFromObject(pkg, pkg.info.Defs[id], spec, goversion) |
| } |
| } |
| |
| case token.TYPE: |
| for _, spec := range decl.Specs { |
| id := spec.(*ast.TypeSpec).Name |
| memberFromObject(pkg, pkg.info.Defs[id], nil, "") |
| } |
| } |
| |
| case *ast.FuncDecl: |
| id := decl.Name |
| memberFromObject(pkg, pkg.info.Defs[id], decl, goversion) |
| } |
| } |
| |
| // CreatePackage creates and returns an SSA Package from the |
| // specified type-checked, error-free file ASTs, and populates its |
| // Members mapping. |
| // |
| // importable determines whether this package should be returned by a |
| // subsequent call to ImportedPackage(pkg.Path()). |
| // |
| // The real work of building SSA form for each function is not done |
| // until a subsequent call to Package.Build. |
| func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package { |
| if pkg == nil { |
| panic("nil pkg") // otherwise pkg.Scope below returns types.Universe! |
| } |
| p := &Package{ |
| Prog: prog, |
| Members: make(map[string]Member), |
| objects: make(map[types.Object]Member), |
| Pkg: pkg, |
| syntax: info != nil, |
| // transient values (cleared after Package.Build) |
| info: info, |
| files: files, |
| initVersion: make(map[ast.Expr]string), |
| } |
| |
| /* synthesized package initializer */ |
| p.init = &Function{ |
| name: "init", |
| Signature: new(types.Signature), |
| Synthetic: "package initializer", |
| Pkg: p, |
| Prog: prog, |
| build: (*builder).buildPackageInit, |
| info: p.info, |
| goversion: "", // See Package.build for details. |
| } |
| p.Members[p.init.name] = p.init |
| p.created = append(p.created, p.init) |
| |
| // Allocate all package members: vars, funcs, consts and types. |
| if len(files) > 0 { |
| // Go source package. |
| for _, file := range files { |
| goversion := versions.Lang(versions.FileVersion(p.info, file)) |
| for _, decl := range file.Decls { |
| membersFromDecl(p, decl, goversion) |
| } |
| } |
| } else { |
| // GC-compiled binary package (or "unsafe") |
| // No code. |
| // No position information. |
| scope := p.Pkg.Scope() |
| for _, name := range scope.Names() { |
| obj := scope.Lookup(name) |
| memberFromObject(p, obj, nil, "") |
| if obj, ok := obj.(*types.TypeName); ok { |
| // No Unalias: aliases should not duplicate methods. |
| if named, ok := obj.Type().(*types.Named); ok { |
| for i, n := 0, named.NumMethods(); i < n; i++ { |
| memberFromObject(p, named.Method(i), nil, "") |
| } |
| } |
| } |
| } |
| } |
| |
| if prog.mode&BareInits == 0 { |
| // Add initializer guard variable. |
| initguard := &Global{ |
| Pkg: p, |
| name: "init$guard", |
| typ: types.NewPointer(tBool), |
| } |
| p.Members[initguard.Name()] = initguard |
| } |
| |
| if prog.mode&GlobalDebug != 0 { |
| p.SetDebugMode(true) |
| } |
| |
| if prog.mode&PrintPackages != 0 { |
| printMu.Lock() |
| p.WriteTo(os.Stdout) |
| printMu.Unlock() |
| } |
| |
| if importable { |
| prog.imported[p.Pkg.Path()] = p |
| } |
| prog.packages[p.Pkg] = p |
| |
| return p |
| } |
| |
| // printMu serializes printing of Packages/Functions to stdout. |
| var printMu sync.Mutex |
| |
| // AllPackages returns a new slice containing all packages created by |
| // prog.CreatePackage in unspecified order. |
| func (prog *Program) AllPackages() []*Package { |
| pkgs := make([]*Package, 0, len(prog.packages)) |
| for _, pkg := range prog.packages { |
| pkgs = append(pkgs, pkg) |
| } |
| return pkgs |
| } |
| |
| // ImportedPackage returns the importable Package whose PkgPath |
| // is path, or nil if no such Package has been created. |
| // |
| // A parameter to CreatePackage determines whether a package should be |
| // considered importable. For example, no import declaration can resolve |
| // to the ad-hoc main package created by 'go build foo.go'. |
| // |
| // TODO(adonovan): rethink this function and the "importable" concept; |
| // most packages are importable. This function assumes that all |
| // types.Package.Path values are unique within the ssa.Program, which is |
| // false---yet this function remains very convenient. |
| // Clients should use (*Program).Package instead where possible. |
| // SSA doesn't really need a string-keyed map of packages. |
| // |
| // Furthermore, the graph of packages may contain multiple variants |
| // (e.g. "p" vs "p as compiled for q.test"), and each has a different |
| // view of its dependencies. |
| func (prog *Program) ImportedPackage(path string) *Package { |
| return prog.imported[path] |
| } |