1// Copyright 2021 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
5package typecheck
6
7import (
8	"cmd/compile/internal/base"
9	"cmd/compile/internal/ir"
10	"cmd/compile/internal/types"
11)
12
13// crawlExports crawls the type/object graph rooted at the given list of exported
14// objects. It descends through all parts of types and follows any methods on defined
15// types. Any functions that are found to be potentially callable by importers are
16// marked with ExportInline, so that iexport.go knows to re-export their inline body.
17// Also, any function or global referenced by a function marked by ExportInline() is
18// marked for export (whether its name is exported or not).
19func crawlExports(exports []*ir.Name) {
20	p := crawler{
21		marked:   make(map[*types.Type]bool),
22		embedded: make(map[*types.Type]bool),
23		generic:  make(map[*types.Type]bool),
24	}
25	for _, n := range exports {
26		p.markObject(n)
27	}
28}
29
30type crawler struct {
31	marked   map[*types.Type]bool // types already seen by markType
32	embedded map[*types.Type]bool // types already seen by markEmbed
33	generic  map[*types.Type]bool // types already seen by markGeneric
34}
35
36// markObject visits a reachable object (function, method, global type, or global variable)
37func (p *crawler) markObject(n *ir.Name) {
38	if n.Op() == ir.ONAME && n.Class == ir.PFUNC {
39		p.markInlBody(n)
40	}
41
42	// If a declared type name is reachable, users can embed it in their
43	// own types, which makes even its unexported methods reachable.
44	if n.Op() == ir.OTYPE {
45		p.markEmbed(n.Type())
46	}
47
48	p.markType(n.Type())
49}
50
51// markType recursively visits types reachable from t to identify functions whose
52// inline bodies may be needed. For instantiated generic types, it visits the base
53// generic type, which has the relevant methods.
54func (p *crawler) markType(t *types.Type) {
55	if t.OrigSym() != nil {
56		// Convert to the base generic type.
57		t = t.OrigSym().Def.Type()
58	}
59	if p.marked[t] {
60		return
61	}
62	p.marked[t] = true
63
64	// If this is a defined type, mark all of its associated
65	// methods. Skip interface types because t.Methods contains
66	// only their unexpanded method set (i.e., exclusive of
67	// interface embeddings), and the switch statement below
68	// handles their full method set.
69	if t.Sym() != nil && t.Kind() != types.TINTER {
70		for _, m := range t.Methods().Slice() {
71			if types.IsExported(m.Sym.Name) {
72				p.markObject(m.Nname.(*ir.Name))
73			}
74		}
75	}
76
77	// Recursively mark any types that can be produced given a
78	// value of type t: dereferencing a pointer; indexing or
79	// iterating over an array, slice, or map; receiving from a
80	// channel; accessing a struct field or interface method; or
81	// calling a function.
82	//
83	// Notably, we don't mark function parameter types, because
84	// the user already needs some way to construct values of
85	// those types.
86	switch t.Kind() {
87	case types.TPTR, types.TARRAY, types.TSLICE:
88		p.markType(t.Elem())
89
90	case types.TCHAN:
91		if t.ChanDir().CanRecv() {
92			p.markType(t.Elem())
93		}
94
95	case types.TMAP:
96		p.markType(t.Key())
97		p.markType(t.Elem())
98
99	case types.TSTRUCT:
100		if t.IsFuncArgStruct() {
101			break
102		}
103		for _, f := range t.FieldSlice() {
104			// Mark the type of a unexported field if it is a
105			// fully-instantiated type, since we create and instantiate
106			// the methods of any fully-instantiated type that we see
107			// during import (see end of typecheck.substInstType).
108			if types.IsExported(f.Sym.Name) || f.Embedded != 0 ||
109				isPtrFullyInstantiated(f.Type) {
110				p.markType(f.Type)
111			}
112		}
113
114	case types.TFUNC:
115		for _, f := range t.Results().FieldSlice() {
116			p.markType(f.Type)
117		}
118
119	case types.TINTER:
120		for _, f := range t.AllMethods().Slice() {
121			if types.IsExported(f.Sym.Name) {
122				p.markType(f.Type)
123			}
124		}
125
126	case types.TTYPEPARAM:
127		// No other type that needs to be followed.
128	}
129}
130
131// markEmbed is similar to markType, but handles finding methods that
132// need to be re-exported because t can be embedded in user code
133// (possibly transitively).
134func (p *crawler) markEmbed(t *types.Type) {
135	if t.IsPtr() {
136		// Defined pointer type; not allowed to embed anyway.
137		if t.Sym() != nil {
138			return
139		}
140		t = t.Elem()
141	}
142
143	if t.OrigSym() != nil {
144		// Convert to the base generic type.
145		t = t.OrigSym().Def.Type()
146	}
147
148	if p.embedded[t] {
149		return
150	}
151	p.embedded[t] = true
152
153	// If t is a defined type, then re-export all of its methods. Unlike
154	// in markType, we include even unexported methods here, because we
155	// still need to generate wrappers for them, even if the user can't
156	// refer to them directly.
157	if t.Sym() != nil && t.Kind() != types.TINTER {
158		for _, m := range t.Methods().Slice() {
159			p.markObject(m.Nname.(*ir.Name))
160		}
161	}
162
163	// If t is a struct, recursively visit its embedded fields.
164	if t.IsStruct() {
165		for _, f := range t.FieldSlice() {
166			if f.Embedded != 0 {
167				p.markEmbed(f.Type)
168			}
169		}
170	}
171}
172
173// markGeneric takes an instantiated type or a base generic type t, and
174// marks all the methods of the base generic type of t. If a base generic
175// type is written to export file, even if not explicitly marked for export,
176// all of its methods need to be available for instantiation if needed.
177func (p *crawler) markGeneric(t *types.Type) {
178	if t.IsPtr() {
179		t = t.Elem()
180	}
181	if t.OrigSym() != nil {
182		// Convert to the base generic type.
183		t = t.OrigSym().Def.Type()
184	}
185	if p.generic[t] {
186		return
187	}
188	p.generic[t] = true
189
190	if t.Sym() != nil && t.Kind() != types.TINTER {
191		for _, m := range t.Methods().Slice() {
192			p.markObject(m.Nname.(*ir.Name))
193		}
194	}
195}
196
197// markInlBody marks n's inline body for export and recursively
198// ensures all called functions are marked too.
199func (p *crawler) markInlBody(n *ir.Name) {
200	if n == nil {
201		return
202	}
203	if n.Op() != ir.ONAME || n.Class != ir.PFUNC {
204		base.Fatalf("markInlBody: unexpected %v, %v, %v", n, n.Op(), n.Class)
205	}
206	fn := n.Func
207	if fn == nil {
208		base.Fatalf("markInlBody: missing Func on %v", n)
209	}
210	if !HaveInlineBody(fn) {
211		return
212	}
213
214	if fn.ExportInline() {
215		return
216	}
217	fn.SetExportInline(true)
218
219	ImportedBody(fn)
220
221	var doFlood func(n ir.Node)
222	doFlood = func(n ir.Node) {
223		t := n.Type()
224		if t != nil {
225			if t.HasTParam() || t.IsFullyInstantiated() {
226				p.markGeneric(t)
227			}
228			if base.Debug.Unified == 0 {
229				// If a method of un-exported type is promoted and accessible by
230				// embedding in an exported type, it makes that type reachable.
231				//
232				// Example:
233				//
234				//     type t struct {}
235				//     func (t) M() {}
236				//
237				//     func F() interface{} { return struct{ t }{} }
238				//
239				// We generate the wrapper for "struct{ t }".M, and inline call
240				// to "struct{ t }".M, which makes "t.M" reachable.
241				if t.IsStruct() {
242					for _, f := range t.FieldSlice() {
243						if f.Embedded != 0 {
244							p.markEmbed(f.Type)
245						}
246					}
247				}
248			}
249		}
250
251		switch n.Op() {
252		case ir.OMETHEXPR, ir.ODOTMETH:
253			p.markInlBody(ir.MethodExprName(n))
254		case ir.ONAME:
255			n := n.(*ir.Name)
256			switch n.Class {
257			case ir.PFUNC:
258				p.markInlBody(n)
259				Export(n)
260			case ir.PEXTERN:
261				Export(n)
262			}
263		case ir.OMETHVALUE:
264			// Okay, because we don't yet inline indirect
265			// calls to method values.
266		case ir.OCLOSURE:
267			// VisitList doesn't visit closure bodies, so force a
268			// recursive call to VisitList on the body of the closure.
269			ir.VisitList(n.(*ir.ClosureExpr).Func.Body, doFlood)
270		}
271	}
272
273	// Recursively identify all referenced functions for
274	// reexport. We want to include even non-called functions,
275	// because after inlining they might be callable.
276	ir.VisitList(fn.Inl.Body, doFlood)
277}
278
279// isPtrFullyInstantiated returns true if t is a fully-instantiated type, or it is a
280// pointer to a fully-instantiated type.
281func isPtrFullyInstantiated(t *types.Type) bool {
282	return t.IsPtr() && t.Elem().IsFullyInstantiated() ||
283		t.IsFullyInstantiated()
284}
285