github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/tools/checkescape/checkescape.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package checkescape allows recursive escape analysis for hot paths.
    16  //
    17  // The analysis tracks multiple types of escapes, in two categories. First,
    18  // 'hard' escapes are explicit allocations. Second, 'soft' escapes are
    19  // interface dispatches or dynamic function dispatches; these don't necessarily
    20  // escape but they *may* escape. The analysis is capable of making assertions
    21  // recursively: soft escapes cannot be analyzed in this way, and therefore
    22  // count as escapes for recursive purposes.
    23  //
    24  // The different types of escapes are as follows, with the category in
    25  // parentheses:
    26  //
    27  // 	heap:      A direct allocation is made on the heap (hard).
    28  // 	builtin:   A call is made to a built-in allocation function (hard).
    29  // 	stack:     A stack split as part of a function preamble (soft).
    30  // 	interface: A call is made via an interface which *may* escape (soft).
    31  // 	dynamic:   A dynamic function is dispatched which *may* escape (soft).
    32  //
    33  // To the use the package, annotate a function-level comment with either the
    34  // line "// +checkescape" or "// +checkescape:OPTION[,OPTION]". In the second
    35  // case, the OPTION field is either a type above, or one of:
    36  //
    37  //	local: Escape analysis is limited to local hard escapes only.
    38  //	all: All the escapes are included.
    39  //	hard: All hard escapes are included.
    40  //
    41  // If the "// +checkescape" annotation is provided, this is equivalent to
    42  // provided the local and hard options.
    43  //
    44  // Some examples of this syntax are:
    45  //
    46  // +checkescape:all               - Analyzes for all escapes in this function and all calls.
    47  // +checkescape:local             - Analyzes only for default local hard escapes.
    48  // +checkescape:heap              - Only analyzes for heap escapes.
    49  // +checkescape:interface,dynamic - Only checks for dynamic calls and interface calls.
    50  // +checkescape                   - Does the same as +checkescape:local,hard.
    51  //
    52  // Note that all of the above can be inverted by using +mustescape. The
    53  // +checkescape keyword will ensure failure if the class of escape occurs,
    54  // whereas +mustescape will fail if the given class of escape does not occur.
    55  //
    56  // Local exemptions can be made by a comment of the form "// escapes: reason."
    57  // This must appear on the line of the escape and will also apply to callers of
    58  // the function as well (for non-local escape analysis).
    59  package checkescape
    60  
    61  import (
    62  	"bufio"
    63  	"bytes"
    64  	"fmt"
    65  	"go/ast"
    66  	"go/token"
    67  	"go/types"
    68  	"io"
    69  	"log"
    70  	"path/filepath"
    71  	"strings"
    72  
    73  	"golang.org/x/tools/go/analysis"
    74  	"golang.org/x/tools/go/analysis/passes/buildssa"
    75  	"golang.org/x/tools/go/ssa"
    76  	"github.com/SagerNet/gvisor/tools/nogo/objdump"
    77  )
    78  
    79  const (
    80  	// magic is the magic annotation.
    81  	magic = "// +checkescape"
    82  
    83  	// magicParams is the magic annotation with specific parameters.
    84  	magicParams = magic + ":"
    85  
    86  	// testMagic is the test magic annotation (parameters required).
    87  	testMagic = "// +mustescape:"
    88  
    89  	// exempt is the exemption annotation.
    90  	exempt = "// escapes"
    91  )
    92  
    93  // EscapeReason is an escape reason.
    94  //
    95  // This is a simple enum.
    96  type EscapeReason int
    97  
    98  const (
    99  	allocation EscapeReason = iota
   100  	builtin
   101  	interfaceInvoke
   102  	dynamicCall
   103  	stackSplit
   104  	unknownPackage
   105  	reasonCount // Count for below.
   106  )
   107  
   108  // String returns the string for the EscapeReason.
   109  //
   110  // Note that this also implicitly defines the reverse string -> EscapeReason
   111  // mapping, which is the word before the colon (computed below).
   112  func (e EscapeReason) String() string {
   113  	switch e {
   114  	case interfaceInvoke:
   115  		return "interface: call to potentially allocating function"
   116  	case unknownPackage:
   117  		return "unknown: no package information available"
   118  	case allocation:
   119  		return "heap: explicit allocation"
   120  	case builtin:
   121  		return "builtin: call to potentially allocating builtin"
   122  	case dynamicCall:
   123  		return "dynamic: call to potentially allocating function"
   124  	case stackSplit:
   125  		return "stack: possible split on function entry"
   126  	default:
   127  		panic(fmt.Sprintf("unknown reason: %d", e))
   128  	}
   129  }
   130  
   131  var hardReasons = []EscapeReason{
   132  	allocation,
   133  	builtin,
   134  }
   135  
   136  var softReasons = []EscapeReason{
   137  	interfaceInvoke,
   138  	unknownPackage,
   139  	dynamicCall,
   140  	stackSplit,
   141  }
   142  
   143  var allReasons = append(hardReasons, softReasons...)
   144  
   145  var escapeTypes = func() map[string]EscapeReason {
   146  	result := make(map[string]EscapeReason)
   147  	for _, r := range allReasons {
   148  		parts := strings.Split(r.String(), ":")
   149  		result[parts[0]] = r // Key before ':'.
   150  	}
   151  	return result
   152  }()
   153  
   154  // escapingBuiltins are builtins known to escape.
   155  //
   156  // These are lowered at an earlier stage of compilation to explicit function
   157  // calls, but are not available for recursive analysis.
   158  var escapingBuiltins = []string{
   159  	"append",
   160  	"makemap",
   161  	"newobject",
   162  	"mallocgc",
   163  }
   164  
   165  // packageEscapeFacts is the set of all functions in a package, and whether or
   166  // not they recursively pass escape analysis.
   167  //
   168  // All the type names for receivers are encoded in the full key. The key
   169  // represents the fully qualified package and type name used at link time.
   170  //
   171  // Note that each Escapes object is a summary. Local findings may be reported
   172  // using more detailed information.
   173  type packageEscapeFacts struct {
   174  	Funcs map[string]Escapes
   175  }
   176  
   177  // AFact implements analysis.Fact.AFact.
   178  func (*packageEscapeFacts) AFact() {}
   179  
   180  // Analyzer includes specific results.
   181  var Analyzer = &analysis.Analyzer{
   182  	Name:      "checkescape",
   183  	Doc:       "escape analysis checks based on +checkescape annotations",
   184  	Run:       runSelectEscapes,
   185  	Requires:  []*analysis.Analyzer{buildssa.Analyzer},
   186  	FactTypes: []analysis.Fact{(*packageEscapeFacts)(nil)},
   187  }
   188  
   189  // EscapeAnalyzer includes all local escape results.
   190  var EscapeAnalyzer = &analysis.Analyzer{
   191  	Name:     "checkescape",
   192  	Doc:      "complete local escape analysis results (requires Analyzer facts)",
   193  	Run:      runAllEscapes,
   194  	Requires: []*analysis.Analyzer{buildssa.Analyzer},
   195  }
   196  
   197  // LinePosition is a low-resolution token.Position.
   198  //
   199  // This is used to match against possible exemptions placed in the source.
   200  type LinePosition struct {
   201  	Filename string
   202  	Line     int
   203  }
   204  
   205  // String implements fmt.Stringer.String.
   206  func (e LinePosition) String() string {
   207  	return fmt.Sprintf("%s:%d", e.Filename, e.Line)
   208  }
   209  
   210  // Simplified returns the simplified name.
   211  func (e LinePosition) Simplified() string {
   212  	return fmt.Sprintf("%s:%d", filepath.Base(e.Filename), e.Line)
   213  }
   214  
   215  // CallSite is a single call site.
   216  //
   217  // These can be chained.
   218  type CallSite struct {
   219  	LocalPos token.Pos
   220  	Resolved LinePosition
   221  }
   222  
   223  // IsValid indicates whether the CallSite is valid or not.
   224  func (cs *CallSite) IsValid() bool {
   225  	return cs.LocalPos.IsValid()
   226  }
   227  
   228  // Escapes is a collection of escapes.
   229  //
   230  // We record at most one escape for each reason, but record the number of
   231  // escapes that were omitted.
   232  //
   233  // This object should be used to summarize all escapes for a single line (local
   234  // analysis) or a single function (package facts).
   235  //
   236  // All fields are exported for gob.
   237  type Escapes struct {
   238  	CallSites [reasonCount][]CallSite
   239  	Details   [reasonCount]string
   240  	Omitted   [reasonCount]int
   241  }
   242  
   243  // add is called by Add and Merge.
   244  func (es *Escapes) add(r EscapeReason, detail string, omitted int, callSites ...CallSite) {
   245  	if es.CallSites[r] != nil {
   246  		// We will either be replacing the current escape or dropping
   247  		// the added one. Either way, we increment omitted by the
   248  		// appropriate amount.
   249  		es.Omitted[r]++
   250  		// If the callSites in the other is only a single element, then
   251  		// we will universally favor this. This provides the cleanest
   252  		// set of escapes to summarize, and more importantly: if there
   253  		if len(es.CallSites) == 1 || len(callSites) != 1 {
   254  			return
   255  		}
   256  	}
   257  	es.Details[r] = detail
   258  	es.CallSites[r] = callSites
   259  	es.Omitted[r] += omitted
   260  }
   261  
   262  // Add adds a single escape.
   263  func (es *Escapes) Add(r EscapeReason, detail string, callSites ...CallSite) {
   264  	es.add(r, detail, 0, callSites...)
   265  }
   266  
   267  // IsEmpty returns true iff this Escapes is empty.
   268  func (es *Escapes) IsEmpty() bool {
   269  	for _, cs := range es.CallSites {
   270  		if cs != nil {
   271  			return false
   272  		}
   273  	}
   274  	return true
   275  }
   276  
   277  // Filter filters out all escapes except those matches the given reasons.
   278  //
   279  // If local is set, then non-local escapes will also be filtered.
   280  func (es *Escapes) Filter(reasons []EscapeReason, local bool) {
   281  FilterReasons:
   282  	for r := EscapeReason(0); r < reasonCount; r++ {
   283  		for i := 0; i < len(reasons); i++ {
   284  			if r == reasons[i] {
   285  				continue FilterReasons
   286  			}
   287  		}
   288  		// Zap this reason.
   289  		es.CallSites[r] = nil
   290  		es.Details[r] = ""
   291  		es.Omitted[r] = 0
   292  	}
   293  	if !local {
   294  		return
   295  	}
   296  	for r := EscapeReason(0); r < reasonCount; r++ {
   297  		// Is does meet our local requirement?
   298  		if len(es.CallSites[r]) > 1 {
   299  			es.CallSites[r] = nil
   300  			es.Details[r] = ""
   301  			es.Omitted[r] = 0
   302  		}
   303  	}
   304  }
   305  
   306  // MergeWithCall merges these escapes with another.
   307  //
   308  // If callSite is nil, no call is added.
   309  func (es *Escapes) MergeWithCall(other Escapes, callSite CallSite) {
   310  	for r := EscapeReason(0); r < reasonCount; r++ {
   311  		if other.CallSites[r] != nil {
   312  			// Construct our new call chain.
   313  			newCallSites := other.CallSites[r]
   314  			if callSite.IsValid() {
   315  				newCallSites = append([]CallSite{callSite}, newCallSites...)
   316  			}
   317  			// Add (potentially replacing) the underlying escape.
   318  			es.add(r, other.Details[r], other.Omitted[r], newCallSites...)
   319  		}
   320  	}
   321  }
   322  
   323  // Reportf will call Reportf for each class of escapes.
   324  func (es *Escapes) Reportf(pass *analysis.Pass) {
   325  	var b bytes.Buffer // Reused for all escapes.
   326  	for r := EscapeReason(0); r < reasonCount; r++ {
   327  		if es.CallSites[r] == nil {
   328  			continue
   329  		}
   330  		b.Reset()
   331  		fmt.Fprintf(&b, "%s ", r.String())
   332  		if es.Omitted[r] > 0 {
   333  			fmt.Fprintf(&b, "(%d omitted) ", es.Omitted[r])
   334  		}
   335  		for _, cs := range es.CallSites[r][1:] {
   336  			fmt.Fprintf(&b, "→ %s ", cs.Resolved.String())
   337  		}
   338  		fmt.Fprintf(&b, "→ %s", es.Details[r])
   339  		pass.Reportf(es.CallSites[r][0].LocalPos, b.String())
   340  	}
   341  }
   342  
   343  // MergeAll merges a sequence of escapes.
   344  func MergeAll(others []Escapes) (es Escapes) {
   345  	for _, other := range others {
   346  		es.MergeWithCall(other, CallSite{})
   347  	}
   348  	return
   349  }
   350  
   351  // loadObjdump reads the objdump output.
   352  //
   353  // This records if there is a call any function for every source line. It is
   354  // used only to remove false positives for escape analysis. The call will be
   355  // elided if escape analysis is able to put the object on the heap exclusively.
   356  //
   357  // Note that the map uses <basename.go>:<line> because that is all that is
   358  // provided in the objdump format. Since this is all local, it is sufficient.
   359  func loadObjdump() (map[string][]string, error) {
   360  	// Identify calls by address or name. Note that this is also
   361  	// constructed dynamically below, as we encounted the addresses.
   362  	// This is because some of the functions (duffzero) may have
   363  	// jump targets in the middle of the function itself.
   364  	funcsAllowed := map[string]struct{}{
   365  		"runtime.duffzero":       {},
   366  		"runtime.duffcopy":       {},
   367  		"runtime.racefuncenter":  {},
   368  		"runtime.gcWriteBarrier": {},
   369  		"runtime.retpolineAX":    {},
   370  		"runtime.retpolineBP":    {},
   371  		"runtime.retpolineBX":    {},
   372  		"runtime.retpolineCX":    {},
   373  		"runtime.retpolineDI":    {},
   374  		"runtime.retpolineDX":    {},
   375  		"runtime.retpolineR10":   {},
   376  		"runtime.retpolineR11":   {},
   377  		"runtime.retpolineR12":   {},
   378  		"runtime.retpolineR13":   {},
   379  		"runtime.retpolineR14":   {},
   380  		"runtime.retpolineR15":   {},
   381  		"runtime.retpolineR8":    {},
   382  		"runtime.retpolineR9":    {},
   383  		"runtime.retpolineSI":    {},
   384  		"runtime.stackcheck":     {},
   385  		"runtime.settls":         {},
   386  	}
   387  	addrsAllowed := make(map[string]struct{})
   388  
   389  	// Build the map.
   390  	nextFunc := "" // For funcsAllowed.
   391  	m := make(map[string][]string)
   392  	if err := objdump.Load(func(origR io.Reader) error {
   393  		r := bufio.NewReader(origR)
   394  	NextLine:
   395  		for {
   396  			line, err := r.ReadString('\n')
   397  			if err != nil && err != io.EOF {
   398  				return err
   399  			}
   400  			fields := strings.Fields(line)
   401  
   402  			// Is this an "allowed" function definition?
   403  			if len(fields) >= 2 && fields[0] == "TEXT" {
   404  				nextFunc = strings.TrimSuffix(fields[1], "(SB)")
   405  				if _, ok := funcsAllowed[nextFunc]; !ok {
   406  					nextFunc = "" // Don't record addresses.
   407  				}
   408  			}
   409  			if nextFunc != "" && len(fields) > 2 {
   410  				// Save the given address (in hex form, as it appears).
   411  				addrsAllowed[fields[1]] = struct{}{}
   412  			}
   413  
   414  			// We recognize lines corresponding to actual code (not the
   415  			// symbol name or other metadata) and annotate them if they
   416  			// correspond to an explicit CALL instruction. We assume that
   417  			// the lack of a CALL for a given line is evidence that escape
   418  			// analysis has eliminated an allocation.
   419  			//
   420  			// Lines look like this (including the first space):
   421  			//  gohacks_unsafe.go:33  0xa39                   488b442408              MOVQ 0x8(SP), AX
   422  			if len(fields) >= 5 && line[0] == ' ' {
   423  				if !strings.Contains(fields[3], "CALL") {
   424  					continue
   425  				}
   426  				site := fields[0]
   427  				target := strings.TrimSuffix(fields[4], "(SB)")
   428  
   429  				// Ignore strings containing allowed functions.
   430  				if _, ok := funcsAllowed[target]; ok {
   431  					continue
   432  				}
   433  				if _, ok := addrsAllowed[target]; ok {
   434  					continue
   435  				}
   436  				if len(fields) > 5 {
   437  					// This may be a future relocation. Some
   438  					// objdump versions describe this differently.
   439  					// If it contains any of the functions allowed
   440  					// above as a string, we let it go.
   441  					softTarget := strings.Join(fields[5:], " ")
   442  					for name := range funcsAllowed {
   443  						if strings.Contains(softTarget, name) {
   444  							continue NextLine
   445  						}
   446  					}
   447  				}
   448  
   449  				// Does this exist already?
   450  				existing, ok := m[site]
   451  				if !ok {
   452  					existing = make([]string, 0, 1)
   453  				}
   454  				for _, other := range existing {
   455  					if target == other {
   456  						continue NextLine
   457  					}
   458  				}
   459  				existing = append(existing, target)
   460  				m[site] = existing // Update.
   461  			}
   462  			if err == io.EOF {
   463  				break
   464  			}
   465  		}
   466  		return nil
   467  	}); err != nil {
   468  		return nil, err
   469  	}
   470  
   471  	// Zap any accidental false positives.
   472  	final := make(map[string][]string)
   473  	for site, calls := range m {
   474  		filteredCalls := make([]string, 0, len(calls))
   475  		for _, call := range calls {
   476  			if _, ok := addrsAllowed[call]; ok {
   477  				continue // Omit this call.
   478  			}
   479  			filteredCalls = append(filteredCalls, call)
   480  		}
   481  		final[site] = filteredCalls
   482  	}
   483  
   484  	return final, nil
   485  }
   486  
   487  // poser is a type that implements Pos.
   488  type poser interface {
   489  	Pos() token.Pos
   490  }
   491  
   492  // runSelectEscapes runs with only select escapes.
   493  func runSelectEscapes(pass *analysis.Pass) (interface{}, error) {
   494  	return run(pass, false)
   495  }
   496  
   497  // runAllEscapes runs with all escapes included.
   498  func runAllEscapes(pass *analysis.Pass) (interface{}, error) {
   499  	return run(pass, true)
   500  }
   501  
   502  // findReasons extracts reasons from the function.
   503  func findReasons(pass *analysis.Pass, fdecl *ast.FuncDecl) ([]EscapeReason, bool, map[EscapeReason]bool) {
   504  	// Is there a comment?
   505  	if fdecl.Doc == nil {
   506  		return nil, false, nil
   507  	}
   508  	var (
   509  		reasons     []EscapeReason
   510  		local       bool
   511  		testReasons = make(map[EscapeReason]bool) // reason -> local?
   512  	)
   513  	// Scan all lines.
   514  	found := false
   515  	for _, c := range fdecl.Doc.List {
   516  		// Does the comment contain a +checkescape line?
   517  		if !strings.HasPrefix(c.Text, magic) && !strings.HasPrefix(c.Text, testMagic) {
   518  			continue
   519  		}
   520  		if c.Text == magic {
   521  			// Default: hard reasons, local only.
   522  			reasons = hardReasons
   523  			local = true
   524  		} else if strings.HasPrefix(c.Text, magicParams) {
   525  			// Extract specific reasons.
   526  			types := strings.Split(c.Text[len(magicParams):], ",")
   527  			found = true // For below.
   528  			for i := 0; i < len(types); i++ {
   529  				if types[i] == "local" {
   530  					// Limit search to local escapes.
   531  					local = true
   532  				} else if types[i] == "all" {
   533  					// Append all reasons.
   534  					reasons = append(reasons, allReasons...)
   535  				} else if types[i] == "hard" {
   536  					// Append all hard reasons.
   537  					reasons = append(reasons, hardReasons...)
   538  				} else {
   539  					r, ok := escapeTypes[types[i]]
   540  					if !ok {
   541  						// This is not a valid escape reason.
   542  						pass.Reportf(fdecl.Pos(), "unknown reason: %v", types[i])
   543  						continue
   544  					}
   545  					reasons = append(reasons, r)
   546  				}
   547  			}
   548  		} else if strings.HasPrefix(c.Text, testMagic) {
   549  			types := strings.Split(c.Text[len(testMagic):], ",")
   550  			local := false
   551  			for i := 0; i < len(types); i++ {
   552  				if types[i] == "local" {
   553  					local = true
   554  				} else {
   555  					r, ok := escapeTypes[types[i]]
   556  					if !ok {
   557  						// This is not a valid escape reason.
   558  						pass.Reportf(fdecl.Pos(), "unknown reason: %v", types[i])
   559  						continue
   560  					}
   561  					if v, ok := testReasons[r]; ok && v {
   562  						// Already registered as local.
   563  						continue
   564  					}
   565  					testReasons[r] = local
   566  				}
   567  			}
   568  		}
   569  	}
   570  	if len(reasons) == 0 && found {
   571  		// A magic annotation was provided, but no reasons.
   572  		pass.Reportf(fdecl.Pos(), "no reasons provided")
   573  	}
   574  	return reasons, local, testReasons
   575  }
   576  
   577  // run performs the analysis.
   578  func run(pass *analysis.Pass, localEscapes bool) (interface{}, error) {
   579  	calls, callsErr := loadObjdump()
   580  	if callsErr != nil {
   581  		// Note that if this analysis fails, then we don't actually
   582  		// fail the analyzer itself. We simply report every possible
   583  		// escape. In most cases this will work just fine.
   584  		log.Printf("WARNING: unable to load objdump: %v", callsErr)
   585  	}
   586  	allEscapes := make(map[string][]Escapes)
   587  	mergedEscapes := make(map[string]Escapes)
   588  	linePosition := func(inst, parent poser) LinePosition {
   589  		p := pass.Fset.Position(inst.Pos())
   590  		if (p.Filename == "" || p.Line == 0) && parent != nil {
   591  			p = pass.Fset.Position(parent.Pos())
   592  		}
   593  		return LinePosition{
   594  			Filename: p.Filename,
   595  			Line:     p.Line,
   596  		}
   597  	}
   598  	callSite := func(inst ssa.Instruction) CallSite {
   599  		return CallSite{
   600  			LocalPos: inst.Pos(),
   601  			Resolved: linePosition(inst, inst.Parent()),
   602  		}
   603  	}
   604  	hasCall := func(inst poser) (string, bool) {
   605  		p := linePosition(inst, nil)
   606  		if callsErr != nil {
   607  			// See above: we don't have access to the binary
   608  			// itself, so need to include every possible call.
   609  			return fmt.Sprintf("(possible, unable to load objdump: %v)", callsErr), true
   610  		}
   611  		s, ok := calls[p.Simplified()]
   612  		if !ok {
   613  			return "", false
   614  		}
   615  		// Join all calls together.
   616  		return strings.Join(s, " or "), true
   617  	}
   618  	state := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
   619  
   620  	// Build the exception list.
   621  	exemptions := make(map[LinePosition]string)
   622  	for _, f := range pass.Files {
   623  		for _, cg := range f.Comments {
   624  			for _, c := range cg.List {
   625  				p := pass.Fset.Position(c.Slash)
   626  				if strings.HasPrefix(strings.ToLower(c.Text), exempt) {
   627  					exemptions[LinePosition{
   628  						Filename: p.Filename,
   629  						Line:     p.Line,
   630  					}] = c.Text[len(exempt):]
   631  				}
   632  			}
   633  		}
   634  	}
   635  
   636  	var loadFunc func(*ssa.Function) Escapes // Used below.
   637  	analyzeInstruction := func(inst ssa.Instruction) (es Escapes) {
   638  		cs := callSite(inst)
   639  		if _, ok := exemptions[cs.Resolved]; ok {
   640  			return // No escape.
   641  		}
   642  		switch x := inst.(type) {
   643  		case *ssa.Call:
   644  			if x.Call.IsInvoke() {
   645  				// This is an interface dispatch. There is no
   646  				// way to know if this is actually escaping or
   647  				// not, since we don't know the underlying
   648  				// type.
   649  				call, _ := hasCall(inst)
   650  				es.Add(interfaceInvoke, call, cs)
   651  				return
   652  			}
   653  			switch x := x.Call.Value.(type) {
   654  			case *ssa.Function:
   655  				if x.Pkg == nil {
   656  					// Can't resolve the package.
   657  					es.Add(unknownPackage, "no package", cs)
   658  					return
   659  				}
   660  
   661  				// Is this a local function? If yes, call the
   662  				// function to load the local function. The
   663  				// local escapes are the escapes found in the
   664  				// local function.
   665  				if x.Pkg.Pkg == pass.Pkg {
   666  					es.MergeWithCall(loadFunc(x), cs)
   667  					return
   668  				}
   669  
   670  				// If this package is the atomic package, the implementation
   671  				// may be replaced by instrinsics that don't have analysis.
   672  				if x.Pkg.Pkg.Path() == "sync/atomic" {
   673  					return
   674  				}
   675  
   676  				// Recursively collect information.
   677  				var imp packageEscapeFacts
   678  				if !pass.ImportPackageFact(x.Pkg.Pkg, &imp) {
   679  					// Unable to import the dependency; we must
   680  					// declare these as escaping.
   681  					es.Add(unknownPackage, "no analysis", cs)
   682  					return
   683  				}
   684  
   685  				// The escapes of this instruction are the
   686  				// escapes of the called function directly.
   687  				// Note that this may record many escapes.
   688  				es.MergeWithCall(imp.Funcs[x.RelString(x.Pkg.Pkg)], cs)
   689  				return
   690  			case *ssa.Builtin:
   691  				// Ignore elided escapes.
   692  				if _, has := hasCall(inst); !has {
   693  					return
   694  				}
   695  
   696  				// Check if the builtin is escaping.
   697  				for _, name := range escapingBuiltins {
   698  					if x.Name() == name {
   699  						es.Add(builtin, name, cs)
   700  						return
   701  					}
   702  				}
   703  			default:
   704  				// All dynamic calls are counted as soft
   705  				// escapes. They are similar to interface
   706  				// dispatches. We cannot actually look up what
   707  				// this refers to using static analysis alone.
   708  				call, _ := hasCall(inst)
   709  				es.Add(dynamicCall, call, cs)
   710  			}
   711  		case *ssa.Alloc:
   712  			// Ignore non-heap allocations.
   713  			if !x.Heap {
   714  				return
   715  			}
   716  
   717  			// Ignore elided escapes.
   718  			call, has := hasCall(inst)
   719  			if !has {
   720  				return
   721  			}
   722  
   723  			// This is a real heap allocation.
   724  			es.Add(allocation, call, cs)
   725  		case *ssa.MakeMap:
   726  			es.Add(builtin, "makemap", cs)
   727  		case *ssa.MakeSlice:
   728  			es.Add(builtin, "makeslice", cs)
   729  		case *ssa.MakeClosure:
   730  			es.Add(builtin, "makeclosure", cs)
   731  		case *ssa.MakeChan:
   732  			es.Add(builtin, "makechan", cs)
   733  		}
   734  		return
   735  	}
   736  
   737  	var analyzeBasicBlock func(*ssa.BasicBlock) []Escapes // Recursive.
   738  	analyzeBasicBlock = func(block *ssa.BasicBlock) (rval []Escapes) {
   739  		for _, inst := range block.Instrs {
   740  			if es := analyzeInstruction(inst); !es.IsEmpty() {
   741  				rval = append(rval, es)
   742  			}
   743  		}
   744  		return
   745  	}
   746  
   747  	loadFunc = func(fn *ssa.Function) Escapes {
   748  		// Is this already available?
   749  		name := fn.RelString(pass.Pkg)
   750  		if es, ok := mergedEscapes[name]; ok {
   751  			return es
   752  		}
   753  
   754  		// In the case of a true cycle, we assume that the current
   755  		// function itself has no escapes.
   756  		//
   757  		// When evaluating the function again, the proper escapes will
   758  		// be filled in here.
   759  		allEscapes[name] = nil
   760  		mergedEscapes[name] = Escapes{}
   761  
   762  		// Perform the basic analysis.
   763  		var es []Escapes
   764  		if fn.Recover != nil {
   765  			es = append(es, analyzeBasicBlock(fn.Recover)...)
   766  		}
   767  		for _, block := range fn.Blocks {
   768  			es = append(es, analyzeBasicBlock(block)...)
   769  		}
   770  
   771  		// Check for a stack split.
   772  		if call, has := hasCall(fn); has {
   773  			var ss Escapes
   774  			ss.Add(stackSplit, call, CallSite{
   775  				LocalPos: fn.Pos(),
   776  				Resolved: linePosition(fn, fn.Parent()),
   777  			})
   778  			es = append(es, ss)
   779  		}
   780  
   781  		// Save the result and return.
   782  		//
   783  		// Note that we merge the result when saving to the facts. It
   784  		// doesn't really matter the specific escapes, as long as we
   785  		// have recorded all the appropriate classes of escapes.
   786  		summary := MergeAll(es)
   787  		allEscapes[name] = es
   788  		mergedEscapes[name] = summary
   789  		return summary
   790  	}
   791  
   792  	// Complete all local functions.
   793  	for _, fn := range state.SrcFuncs {
   794  		loadFunc(fn)
   795  	}
   796  
   797  	if !localEscapes {
   798  		// Export all findings for future packages. We only do this in
   799  		// non-local escapes mode, and expect to run this analysis
   800  		// after the SelectAnalysis.
   801  		pass.ExportPackageFact(&packageEscapeFacts{
   802  			Funcs: mergedEscapes,
   803  		})
   804  	}
   805  
   806  	// Scan all functions for violations.
   807  	for _, f := range pass.Files {
   808  		// Scan all declarations.
   809  		for _, decl := range f.Decls {
   810  			// Function declaration?
   811  			fdecl, ok := decl.(*ast.FuncDecl)
   812  			if !ok {
   813  				continue
   814  			}
   815  			var (
   816  				reasons     []EscapeReason
   817  				local       bool
   818  				testReasons map[EscapeReason]bool
   819  			)
   820  			if localEscapes {
   821  				// Find all hard escapes.
   822  				reasons = hardReasons
   823  			} else {
   824  				// Find all declared reasons.
   825  				reasons, local, testReasons = findReasons(pass, fdecl)
   826  			}
   827  
   828  			// Scan for matches.
   829  			fn := pass.TypesInfo.Defs[fdecl.Name].(*types.Func)
   830  			fv := state.Pkg.Prog.FuncValue(fn)
   831  			if fv == nil {
   832  				continue
   833  			}
   834  			name := fv.RelString(pass.Pkg)
   835  			all, allOk := allEscapes[name]
   836  			merged, mergedOk := mergedEscapes[name]
   837  			if !allOk || !mergedOk {
   838  				pass.Reportf(fdecl.Pos(), "internal error: function %s not found.", name)
   839  				continue
   840  			}
   841  
   842  			// Filter reasons and report.
   843  			//
   844  			// For the findings, we use all escapes.
   845  			for _, es := range all {
   846  				es.Filter(reasons, local)
   847  				es.Reportf(pass)
   848  			}
   849  
   850  			// Scan for test (required) matches.
   851  			//
   852  			// For tests we need only the merged escapes.
   853  			testReasonsFound := make(map[EscapeReason]bool)
   854  			for r := EscapeReason(0); r < reasonCount; r++ {
   855  				if merged.CallSites[r] == nil {
   856  					continue
   857  				}
   858  				// Is this local?
   859  				wantLocal, ok := testReasons[r]
   860  				isLocal := len(merged.CallSites[r]) == 1
   861  				testReasonsFound[r] = isLocal
   862  				if !ok {
   863  					continue
   864  				}
   865  				if isLocal == wantLocal {
   866  					delete(testReasons, r)
   867  				}
   868  			}
   869  			for reason, local := range testReasons {
   870  				// We didn't find the escapes we wanted.
   871  				pass.Reportf(fdecl.Pos(), fmt.Sprintf("testescapes not found: reason=%s, local=%t", reason, local))
   872  			}
   873  			if len(testReasons) > 0 {
   874  				// Report for debugging.
   875  				merged.Reportf(pass)
   876  			}
   877  		}
   878  	}
   879  
   880  	return nil, nil
   881  }