blob: b4feb42cb3a24b6e0b836bb58c77d12e41e5f685 [file] [log] [blame]
// 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 ssautil // import "golang.org/x/tools/go/ssa/ssautil"
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/ssa"
_ "unsafe" // for linkname hack
)
// This file defines utilities for visiting the SSA representation of
// a Program.
//
// TODO(adonovan): test coverage.
// AllFunctions finds and returns the set of functions potentially
// needed by program prog, as determined by a simple linker-style
// reachability algorithm starting from the members and method-sets of
// each package. The result may include anonymous functions and
// synthetic wrappers.
//
// Precondition: all packages are built.
//
// TODO(adonovan): this function is underspecified. It doesn't
// actually work like a linker, which computes reachability from main
// using something like go/callgraph/cha (without materializing the
// call graph). In fact, it treats all public functions and all
// methods of public non-parameterized types as roots, even though
// they may be unreachable--but only in packages created from syntax.
//
// I think we should deprecate AllFunctions function in favor of two
// clearly defined ones:
//
// 1. The first would efficiently compute CHA reachability from a set
// of main packages, making it suitable for a whole-program
// analysis context with InstantiateGenerics, in conjunction with
// Program.Build.
//
// 2. The second would return only the set of functions corresponding
// to source Func{Decl,Lit} syntax, like SrcFunctions in
// go/analysis/passes/buildssa; this is suitable for
// package-at-a-time (or handful of packages) context.
// ssa.Package could easily expose it as a field.
//
// We could add them unexported for now and use them via the linkname hack.
func AllFunctions(prog *ssa.Program) map[*ssa.Function]bool {
seen := make(map[*ssa.Function]bool)
var function func(fn *ssa.Function)
function = func(fn *ssa.Function) {
if !seen[fn] {
seen[fn] = true
var buf [10]*ssa.Value // avoid alloc in common case
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
for _, op := range instr.Operands(buf[:0]) {
if fn, ok := (*op).(*ssa.Function); ok {
function(fn)
}
}
}
}
}
}
// TODO(adonovan): opt: provide a way to share a builder
// across a sequence of MethodValue calls.
methodsOf := func(T types.Type) {
if !types.IsInterface(T) {
mset := prog.MethodSets.MethodSet(T)
for i := 0; i < mset.Len(); i++ {
function(prog.MethodValue(mset.At(i)))
}
}
}
// Historically, Program.RuntimeTypes used to include the type
// of any exported member of a package loaded from syntax that
// has a non-parameterized type, plus all types
// reachable from that type using reflection, even though
// these runtime types may not be required for them.
//
// Rather than break existing programs that rely on
// AllFunctions visiting extra methods that are unreferenced
// by IR and unreachable via reflection, we moved the logic
// here, unprincipled though it is.
// (See doc comment for better ideas.)
//
// Nonetheless, after the move, we no longer visit every
// method of any type recursively reachable from T, only the
// methods of T and *T themselves, and we only apply this to
// named types T, and not to the type of every exported
// package member.
exportedTypeHack := func(t *ssa.Type) {
if isSyntactic(t.Package()) &&
ast.IsExported(t.Name()) &&
!types.IsInterface(t.Type()) {
// Consider only named types.
// (Ignore aliases and unsafe.Pointer.)
if named, ok := t.Type().(*types.Named); ok {
if named.TypeParams() == nil {
methodsOf(named) // T
methodsOf(types.NewPointer(named)) // *T
}
}
}
}
for _, pkg := range prog.AllPackages() {
for _, mem := range pkg.Members {
switch mem := mem.(type) {
case *ssa.Function:
// Visit all package-level declared functions.
function(mem)
case *ssa.Type:
exportedTypeHack(mem)
}
}
}
// Visit all methods of types for which runtime types were
// materialized, as they are reachable through reflection.
for _, T := range prog.RuntimeTypes() {
methodsOf(T)
}
return seen
}
// MainPackages returns the subset of the specified packages
// named "main" that define a main function.
// The result may include synthetic "testmain" packages.
func MainPackages(pkgs []*ssa.Package) []*ssa.Package {
var mains []*ssa.Package
for _, pkg := range pkgs {
if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil {
mains = append(mains, pkg)
}
}
return mains
}
// TODO(adonovan): propose a principled API for this. One possibility
// is a new field, Package.SrcFunctions []*Function, which would
// contain the list of SrcFunctions described in point 2 of the
// AllFunctions doc comment, or nil if the package is not from syntax.
// But perhaps overloading nil vs empty slice is too subtle.
//
//go:linkname isSyntactic golang.org/x/tools/go/ssa.isSyntactic
func isSyntactic(pkg *ssa.Package) bool