-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
visit.go
157 lines (141 loc) · 5.06 KB
/
visit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 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